Skip to content

Commit

Permalink
Functionality to call ingress operations after discovering APIVersion (
Browse files Browse the repository at this point in the history
…#1138)

* Figure out ingress API before calling List/Get operations

This commit adds wrapper around the std API's get and list
methods so that we can first figure out which api version
ingresses are part of, and then can call the respective methods.

* Add another method `IngressPath` in `IngressMgr` interface

Apart from getting/listting and creating the ingress resources
we would also need to get the ingress path of provided ingress
resource.
Since that would depend on which APIVersion the ingress resource
is from, its better we have that method as part of this interface.

* Add comments, make consts

* Address review comments

* Address review comment, change order of var init

* Address review comments

* Make ingress name format const

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
viveksinghggits and mergify[bot] committed Nov 24, 2021
1 parent 5fab1bb commit 551d676
Show file tree
Hide file tree
Showing 5 changed files with 361 additions and 1 deletion.
22 changes: 21 additions & 1 deletion pkg/kube/discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
const (
osAppsGroupName = `apps.openshift.io`
osRouteGroupName = `route.openshift.io`

groupVersionFormat = "%s/%s"
)

// IsOSAppsGroupAvailable returns true if the openshift apps group is registered in service discovery.
Expand Down Expand Up @@ -40,6 +42,24 @@ func IsOSRouteGroupAvailable(ctx context.Context, cli discovery.DiscoveryInterfa
return false, nil
}

// IsResAvailableInGroupVersion takes a resource and checks if that exists in the passed group and version
func IsResAvailableInGroupVersion(ctx context.Context, cli discovery.DiscoveryInterface, groupName, version, resource string) (bool, error) {
resList, err := cli.ServerPreferredResources()
if err != nil {
return false, err
}

gv := fmt.Sprintf(groupVersionFormat, groupName, version)
for _, res := range resList {
for _, r := range res.APIResources {
if r.Name == resource && gv == res.GroupVersion {
return true, nil
}
}
}
return false, nil
}

// IsGroupVersionAvailable returns true if given group/version is registered.
func IsGroupVersionAvailable(ctx context.Context, cli discovery.DiscoveryInterface, groupName, version string) (bool, error) {
sgs, err := cli.ServerGroups()
Expand All @@ -49,7 +69,7 @@ func IsGroupVersionAvailable(ctx context.Context, cli discovery.DiscoveryInterfa

for _, g := range sgs.Groups {
for _, v := range g.Versions {
if fmt.Sprintf("%s/%s", groupName, version) == v.GroupVersion {
if fmt.Sprintf(groupVersionFormat, groupName, version) == v.GroupVersion {
return true, nil
}
}
Expand Down
88 changes: 88 additions & 0 deletions pkg/kube/ingress/ingress_extbeta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2019 The Kanister Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ingress

import (
"context"
"fmt"

"github.com/pkg/errors"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
)

// ExtensionsV1beta1 implements ingress.Manager interface
type ExtensionsV1beta1 struct {
kubeCli kubernetes.Interface
}

func NewExtensionsV1beta1(cli kubernetes.Interface) *ExtensionsV1beta1 {
return &ExtensionsV1beta1{
kubeCli: cli,
}
}

// List can be used to list all the ingress resources from `ns` namespace
func (i *ExtensionsV1beta1) List(ctx context.Context, ns string) (runtime.Object, error) {
return i.kubeCli.ExtensionsV1beta1().Ingresses(ns).List(ctx, metav1.ListOptions{})
}

// Get can be used to to get ingress resource with name `name` in `ns` namespace
func (i *ExtensionsV1beta1) Get(ctx context.Context, ns, name string) (runtime.Object, error) {
return i.kubeCli.ExtensionsV1beta1().Ingresses(ns).Get(ctx, name, metav1.GetOptions{})
}

// IngressPath can be used to get the backend path that is specified in the
// ingress resource in `ns` namespace and name `releaseName-ingress`
func (i *ExtensionsV1beta1) IngressPath(ctx context.Context, ns, releaseName string) (string, error) {
obj, err := i.Get(ctx, ns, fmt.Sprintf(ingressNameFormat, releaseName, ingressNameSuffix))
if apierrors.IsNotFound(err) {
// Try the release name if the ingress does not exist.
// This is possible if the user setup OIDC using the localhost IP
// and has port forwarding turned on to access K10.
return releaseName, nil
}
if err != nil {
return "", err
}

ingress := obj.(*extensionsv1beta1.Ingress)
if len(ingress.Spec.Rules) == 0 {
return "", errors.Wrapf(err, "No ingress rules were found")
}
ingressHTTPRule := ingress.Spec.Rules[0].IngressRuleValue.HTTP
if ingressHTTPRule == nil {
return "", errors.Wrapf(err, "A HTTP ingress rule value is missing")
}
ingressPaths := ingressHTTPRule.Paths
if len(ingressPaths) == 0 {
return "", errors.Wrapf(err, "Failed to find HTTP paths in the ingress")
}
ingressPath := ""
for _, path := range ingressPaths {
if path.Backend.ServiceName == gatewaySvcName {
ingressPath = path.Path
break
}
}
if ingressPath == "" {
return "", errors.Wrapf(err, "No path was set for K10's gateway service")
}

return ingressPath, nil
}
88 changes: 88 additions & 0 deletions pkg/kube/ingress/ingress_netbeta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2019 The Kanister Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ingress

import (
"context"
"fmt"

"github.com/pkg/errors"
netv1beta1 "k8s.io/api/networking/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
)

// NetworkingV1beta1 implements ingress.Manager interface
type NetworkingV1beta1 struct {
kubeCli kubernetes.Interface
}

func NewNetworkingV1beta1(cli kubernetes.Interface) *NetworkingV1beta1 {
return &NetworkingV1beta1{
kubeCli: cli,
}
}

// List can be used to list all the ingress resources from `ns` namespace
func (i *NetworkingV1beta1) List(ctx context.Context, ns string) (runtime.Object, error) {
return i.kubeCli.NetworkingV1beta1().Ingresses(ns).List(ctx, metav1.ListOptions{})
}

// Get can be used to to get ingress resource with name `name` in `ns` namespace
func (i *NetworkingV1beta1) Get(ctx context.Context, ns, name string) (runtime.Object, error) {
return i.kubeCli.NetworkingV1beta1().Ingresses(ns).Get(ctx, name, metav1.GetOptions{})
}

// IngressPath can be used to get the backend path that is specified in the
// ingress resource in `ns` namespace and name `releaseName-ingress`
func (i *NetworkingV1beta1) IngressPath(ctx context.Context, ns, releaseName string) (string, error) {
obj, err := i.Get(ctx, ns, fmt.Sprintf(ingressNameFormat, releaseName, ingressNameSuffix))
if apierrors.IsNotFound(err) {
// Try the release name if the ingress does not exist.
// This is possible if the user setup OIDC using the localhost IP
// and has port forwarding turned on to access K10.
return releaseName, nil
}
if err != nil {
return "", err
}

ingress := obj.(*netv1beta1.Ingress)
if len(ingress.Spec.Rules) == 0 {
return "", errors.Wrapf(err, "No ingress rules were found")
}
ingressHTTPRule := ingress.Spec.Rules[0].IngressRuleValue.HTTP
if ingressHTTPRule == nil {
return "", errors.Wrapf(err, "A HTTP ingress rule value is missing")
}
ingressPaths := ingressHTTPRule.Paths
if len(ingressPaths) == 0 {
return "", errors.Wrapf(err, "Failed to find HTTP paths in the ingress")
}
ingressPath := ""
for _, path := range ingressPaths {
if path.Backend.ServiceName == gatewaySvcName {
ingressPath = path.Path
break
}
}
if ingressPath == "" {
return "", errors.Wrapf(err, "No path was set for K10's gateway service")
}

return ingressPath, nil
}
88 changes: 88 additions & 0 deletions pkg/kube/ingress/ingress_netv1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2019 The Kanister Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ingress

import (
"context"
"fmt"

"github.com/pkg/errors"
netv1 "k8s.io/api/networking/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
)

// NetworkingV1 implements ingress.Manager interface
type NetworkingV1 struct {
kubeCli kubernetes.Interface
}

func NewNetworkingV1(cli kubernetes.Interface) *NetworkingV1 {
return &NetworkingV1{
kubeCli: cli,
}
}

// List can be used to list all the ingress resources from `ns` namespace
func (i *NetworkingV1) List(ctx context.Context, ns string) (runtime.Object, error) {
return i.kubeCli.NetworkingV1().Ingresses(ns).List(ctx, metav1.ListOptions{})
}

// Get can be used to to get ingress resource with name `name` in `ns` namespace
func (i *NetworkingV1) Get(ctx context.Context, ns, name string) (runtime.Object, error) {
return i.kubeCli.NetworkingV1().Ingresses(ns).Get(ctx, name, metav1.GetOptions{})
}

// IngressPath can be used to get the backend path that is specified in the
// ingress resource in `ns` namespace and name `releaseName-ingress`
func (i *NetworkingV1) IngressPath(ctx context.Context, ns, releaseName string) (string, error) {
obj, err := i.Get(ctx, ns, fmt.Sprintf(ingressNameFormat, releaseName, ingressNameSuffix))
if apierrors.IsNotFound(err) {
// Try the release name if the ingress does not exist.
// This is possible if the user setup OIDC using the localhost IP
// and has port forwarding turned on to access K10.
return releaseName, nil
}
if err != nil {
return "", err
}

ingress := obj.(*netv1.Ingress)
if len(ingress.Spec.Rules) == 0 {
return "", errors.Wrapf(err, "No ingress rules were found")
}
ingressHTTPRule := ingress.Spec.Rules[0].IngressRuleValue.HTTP
if ingressHTTPRule == nil {
return "", errors.Wrapf(err, "A HTTP ingress rule value is missing")
}
ingressPaths := ingressHTTPRule.Paths
if len(ingressPaths) == 0 {
return "", errors.Wrapf(err, "Failed to find HTTP paths in the ingress")
}
ingressPath := ""
for _, path := range ingressPaths {
if path.Backend.Service.Name == gatewaySvcName {
ingressPath = path.Path
break
}
}
if ingressPath == "" {
return "", errors.Wrapf(err, "No path was set for K10's gateway service")
}

return ingressPath, nil
}
76 changes: 76 additions & 0 deletions pkg/kube/ingress/ingressmgr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2019 The Kanister Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ingress

import (
"context"

"github.com/pkg/errors"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
netv1 "k8s.io/api/networking/v1"
netv1beta1 "k8s.io/api/networking/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"

"github.com/kanisterio/kanister/pkg/kube"
)

const (
ingressRes = "ingresses"
gatewaySvcName = "gateway"
ingressNameSuffix = "ingress"
ingressNameFormat = "%s-%s"
)

// Manager is an abstraction over the behaviour of the ingress resources that
// depends on the APIVersion of the ingress resource
type Manager interface {
// List can be used to list all the ingress resources from `ns` namespace
List(ctx context.Context, ns string) (runtime.Object, error)
// Get can be used to to get ingress resource with name `name` in `ns` namespace
Get(ctx context.Context, ns, name string) (runtime.Object, error)
// IngressPath can be used to get the backend path that is specified in the
// ingress resource in `ns` namespace and name `releaseName-ingress`
IngressPath(ctx context.Context, ns, releaseName string) (string, error)
}

// NewManager can be used to get the Manager based on the APIVersion of the ingress resources on the cluster
// so that, respecitve methods from that APIVersion can be called
func NewManager(ctx context.Context, kubeCli kubernetes.Interface) (Manager, error) {
exists, err := kube.IsResAvailableInGroupVersion(ctx, kubeCli.Discovery(), netv1.GroupName, netv1.SchemeGroupVersion.Version, ingressRes)
if err != nil {
return nil, errors.Errorf("Failed to call discovery APIs: %v", err)
}
if exists {
return NewNetworkingV1(kubeCli), nil
}

exists, err = kube.IsResAvailableInGroupVersion(ctx, kubeCli.Discovery(), extensionsv1beta1.GroupName, extensionsv1beta1.SchemeGroupVersion.Version, ingressRes)
if err != nil {
return nil, errors.Errorf("Failed to call discovery APIs: %v", err)
}
if exists {
return NewExtensionsV1beta1(kubeCli), nil
}

exists, err = kube.IsResAvailableInGroupVersion(ctx, kubeCli.Discovery(), netv1beta1.GroupName, netv1beta1.SchemeGroupVersion.Version, ingressRes)
if err != nil {
return nil, errors.Errorf("Failed to call discovery APIs: %v", err)
}
if exists {
return NewNetworkingV1beta1(kubeCli), nil
}
return nil, errors.New("Ingress resources are not available")
}

0 comments on commit 551d676

Please sign in to comment.