This is an automated email from the ASF dual-hosted git repository.
ricardozanini pushed a commit to branch main
in repository
https://gitbox.apache.org/repos/asf/incubator-kie-kogito-serverless-operator.git
The following commit(s) were added to refs/heads/main by this push:
new 162f3693 kie-issues-308: Operator driven service discovery API Phase2
(#309)
162f3693 is described below
commit 162f3693844de04e94b447b29253eb3ed38057ad
Author: Walter Medvedeo <[email protected]>
AuthorDate: Fri Nov 24 15:03:59 2023 +0100
kie-issues-308: Operator driven service discovery API Phase2 (#309)
* kie-issues-308: Operator driven service discovery API Phase2
- Add the discovery of kubernetes Deployment, StatefulSet, and Ingress
objects to the service discovery API.
* kie-issues-308: Operator driven service discovery API Phase2
- Code review suggestions 1, simplify the Deployment and StatefulSet
discovery
---
controllers/discovery/discovery.go | 93 +++++---
controllers/discovery/discovery_test.go | 218 +++++++++++++++---
controllers/discovery/knative_catalog.go | 31 +--
controllers/discovery/kubernetes_catalog.go | 116 +++++++---
controllers/discovery/kubernetes_constants.go | 29 +++
controllers/discovery/port_utils.go | 57 +++--
controllers/discovery/port_utils_test.go | 77 ++++---
controllers/discovery/queries.go | 110 +++++++---
controllers/discovery/queries_test.go | 244 +++++++++++++--------
controllers/discovery/test_utils.go | 157 ++++++++++---
controllers/discovery/uri_parser.go | 42 ++--
controllers/discovery/uri_parser_test.go | 54 +++--
controllers/discovery/uri_utils.go | 41 ++--
controllers/discovery/uri_utils_test.go | 83 +++----
controllers/profiles/common/app_properties.go | 31 +--
controllers/profiles/common/app_properties_test.go | 31 +--
16 files changed, 965 insertions(+), 449 deletions(-)
diff --git a/controllers/discovery/discovery.go
b/controllers/discovery/discovery.go
index 06c4709f..4b170b9f 100644
--- a/controllers/discovery/discovery.go
+++ b/controllers/discovery/discovery.go
@@ -1,16 +1,21 @@
-// Copyright 2023 Red Hat, Inc. and/or its affiliates
-//
-// 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.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 discovery
@@ -27,8 +32,9 @@ const (
KubernetesScheme = "kubernetes"
OpenshiftScheme = "openshift"
- // PortLabel well known label name to select a particular target port
- PortLabel = "port"
+ // PortQueryParam well known query param to select a particular target
port, for example when a service is being
+ // discovered and there are many ports to select.
+ PortQueryParam = "port"
// KubernetesDNSAddress use this output format with kubernetes services
and pods to resolve to the corresponding
// kubernetes DNS name. see:
https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/
@@ -53,11 +59,11 @@ const (
)
type ResourceUri struct {
- Scheme string
- GVK v1.GroupVersionKind
- Namespace string
- Name string
- CustomLabels map[string]string
+ Scheme string
+ GVK v1.GroupVersionKind
+ Namespace string
+ Name string
+ QueryParams map[string]string
}
// ServiceCatalog is the entry point to resolve resource addresses given a
ResourceUri.
@@ -105,9 +111,9 @@ type ResourceUriBuilder struct {
func NewResourceUriBuilder(scheme string) ResourceUriBuilder {
return ResourceUriBuilder{
uri: &ResourceUri{
- Scheme: scheme,
- GVK: v1.GroupVersionKind{},
- CustomLabels: map[string]string{},
+ Scheme: scheme,
+ GVK: v1.GroupVersionKind{},
+ QueryParams: map[string]string{},
},
}
}
@@ -137,13 +143,13 @@ func (b ResourceUriBuilder) Name(name string)
ResourceUriBuilder {
return b
}
-func (b ResourceUriBuilder) Port(customPort string) ResourceUriBuilder {
+func (b ResourceUriBuilder) WithPort(customPort string) ResourceUriBuilder {
b.uri.SetPort(customPort)
return b
}
-func (b ResourceUriBuilder) WithLabel(labelName string, labelValue string)
ResourceUriBuilder {
- b.uri.CustomLabels[labelName] = labelValue
+func (b ResourceUriBuilder) WithQueryParam(param string, value string)
ResourceUriBuilder {
+ b.uri.AddQueryParam(param, value)
return b
}
@@ -151,26 +157,43 @@ func (b ResourceUriBuilder) Build() *ResourceUri {
return b.uri
}
-func (r *ResourceUri) AddLabel(name string, value string) *ResourceUri {
+func (r *ResourceUri) AddQueryParam(name string, value string) {
if len(value) > 0 {
- r.CustomLabels[name] = value
+ r.QueryParams[name] = value
}
- return r
}
-func (r *ResourceUri) GetLabel(name string) string {
+func (r *ResourceUri) GetQueryParam(name string) string {
if len(name) > 0 {
- return r.CustomLabels[name]
+ return r.QueryParams[name]
}
return ""
}
-func (r *ResourceUri) SetPort(value string) *ResourceUri {
- return r.AddLabel(PortLabel, value)
+func (r *ResourceUri) SetPort(value string) {
+ r.AddQueryParam(PortQueryParam, value)
}
func (r *ResourceUri) GetPort() string {
- return r.GetLabel(PortLabel)
+ return r.GetQueryParam(PortQueryParam)
+}
+
+// GetCustomLabels returns all the query parameters that not considered well
known query parameters, and thus, has no
+// particular semantic during the discovery. These arbitrary parameters are
normally considered as labels, and when
+// present, and the service discovery must give a preference over a set of
resources, they can be used to do a filtering.
+// by labels.
+func (r *ResourceUri) GetCustomLabels() map[string]string {
+ customQueryParams := make(map[string]string)
+ for k, v := range r.QueryParams {
+ if !isWellKnownQueryParam(k) && len(v) > 0 {
+ customQueryParams[k] = v
+ }
+ }
+ return customQueryParams
+}
+
+func isWellKnownQueryParam(k string) bool {
+ return k == PortQueryParam
}
func (r *ResourceUri) String() string {
@@ -184,7 +207,7 @@ func (r *ResourceUri) String() string {
uri = appendWithDelimiter(uri, r.Namespace, "/")
uri = appendWithDelimiter(uri, r.Name, "/")
- return appendWithDelimiter(uri, buildLabelsString(r.CustomLabels, "&"),
"?")
+ return appendWithDelimiter(uri, buildLabelsString(r.QueryParams, "&"),
"?")
}
func appendWithDelimiter(value string, toAppend string, delimiter string)
string {
diff --git a/controllers/discovery/discovery_test.go
b/controllers/discovery/discovery_test.go
index e54b4685..e03fcb01 100644
--- a/controllers/discovery/discovery_test.go
+++ b/controllers/discovery/discovery_test.go
@@ -1,25 +1,32 @@
-// Copyright 2023 Red Hat, Inc. and/or its affiliates
-//
-// 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.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 discovery
import (
"context"
+ "fmt"
"testing"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
+ v1 "k8s.io/api/networking/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
@@ -29,22 +36,24 @@ func Test_NewResourceUriBuilder(t *testing.T) {
Group("apps").
Version("v1").
Namespace(namespace1).
- Name(service1).
- Port("custom-port").
- WithLabel(label1, valueLabel1).Build()
+ Name(service1Name).
+ WithPort("custom-port-value").
+ WithQueryParam(label1, valueLabel1).Build()
assert.Equal(t, "deployments", resourceUri.GVK.Kind)
assert.Equal(t, "apps", resourceUri.GVK.Group)
assert.Equal(t, "v1", resourceUri.GVK.Version)
assert.Equal(t, namespace1, resourceUri.Namespace)
- assert.Equal(t, service1, resourceUri.Name)
- assert.Equal(t, 2, len(resourceUri.CustomLabels))
- assert.Equal(t, "custom-port", resourceUri.CustomLabels["port"])
- assert.Equal(t, valueLabel1, resourceUri.CustomLabels[label1])
+ assert.Equal(t, service1Name, resourceUri.Name)
+ assert.Equal(t, 2, len(resourceUri.QueryParams))
+ assert.Equal(t, "custom-port-value", resourceUri.GetPort())
+ assert.Equal(t, valueLabel1, resourceUri.QueryParams[label1])
+ assert.Equal(t, 1, len(resourceUri.GetCustomLabels()))
+ assert.Equal(t, valueLabel1, resourceUri.GetCustomLabels()[label1])
}
func Test_QueryKubernetesServiceDNSMode(t *testing.T) {
- doTestQueryKubernetesService(t, KubernetesDNSAddress,
"http://service1.namespace1.svc:80")
+ doTestQueryKubernetesService(t, KubernetesDNSAddress,
"http://service1Name.namespace1.svc:80")
}
func Test_QueryKubernetesServiceIPAddressMode(t *testing.T) {
@@ -52,7 +61,7 @@ func Test_QueryKubernetesServiceIPAddressMode(t *testing.T) {
}
func doTestQueryKubernetesService(t *testing.T, outputFormat string,
expectedUri string) {
- service := mockServiceWithPorts(namespace1, service1,
mockServicePort(httpProtocolName, tcp, defaultHttp))
+ service := mockServiceWithPorts(namespace1, service1Name,
mockServicePort(httpProtocol, tcp, defaultHttpPort))
service.Spec.Type = corev1.ServiceTypeNodePort
service.Spec.ClusterIP = "10.1.5.18"
cli := fake.NewClientBuilder().WithRuntimeObjects(service).Build()
@@ -61,7 +70,7 @@ func doTestQueryKubernetesService(t *testing.T, outputFormat
string, expectedUri
Kind("services").
Version("v1").
Namespace(namespace1).
- Name(service1).Build(), outputFormat, expectedUri)
+ Name(service1Name).Build(), outputFormat, expectedUri)
}
func Test_QueryKubernetesPodDNSMode(t *testing.T) {
@@ -73,8 +82,8 @@ func Test_QueryKubernetesPodIPAddressMode(t *testing.T) {
}
func doTestQueryKubernetesPod(t *testing.T, outputFormat string, expectedUri
string) {
- pod := mockPodWithContainers(namespace1, pod1,
- *mockContainerWithPorts("container1",
mockContainerPort(httpProtocolName, tcp, defaultHttp)))
+ pod := mockPodWithContainers(namespace1, pod1Name,
+ *mockContainerWithPorts("container1Name",
mockContainerPort(httpProtocol, tcp, defaultHttpPort)))
pod.Status.PodIP = "10.1.12.13"
cli := fake.NewClientBuilder().WithRuntimeObjects(pod).Build()
ctg := NewServiceCatalog(cli)
@@ -82,7 +91,159 @@ func doTestQueryKubernetesPod(t *testing.T, outputFormat
string, expectedUri str
Kind("pods").
Version("v1").
Namespace(namespace1).
- Name(pod1).Build(), outputFormat, expectedUri)
+ Name(pod1Name).Build(), outputFormat, expectedUri)
+}
+
+func Test_QueryKubernetesDeploymentWithServiceDNSMode(t *testing.T) {
+ doTesQueryKubernetesDeploymentWithService(t, KubernetesDNSAddress,
"http://service1Name.namespace1.svc:80")
+}
+
+func Test_QueryKubernetesDeploymentWithServiceIPAddressMode(t *testing.T) {
+ doTesQueryKubernetesDeploymentWithService(t, KubernetesIPAddress,
"http://10.1.15.16:80")
+}
+
+func doTesQueryKubernetesDeploymentWithService(t *testing.T, outputFormat
string, expectedUri string) {
+ selector := map[string]string{
+ label1: valueLabel1,
+ label2: valueLabel2,
+ }
+
+ deployment := mockDeployment(namespace1, deployment1Name, nil,
&selector)
+
+ service := mockServiceWithPorts(namespace1, service1Name,
mockServicePort(httpProtocol, tcp, defaultHttpPort))
+ service.Spec.Selector = selector
+ service.Spec.ClusterIP = "10.1.15.16"
+ service.Spec.Type = corev1.ServiceTypeNodePort
+
+ cli := fake.NewClientBuilder().WithRuntimeObjects(deployment,
service).Build()
+ ctg := NewServiceCatalog(cli)
+
+ doTestQuery(t, ctg, *NewResourceUriBuilder(KubernetesScheme).
+ Group("apps").
+ Version("v1").
+ Kind("deployments").
+ Namespace(namespace1).
+ Name(deployment1Name).Build(),
+ outputFormat, expectedUri)
+}
+
+func Test_QueryKubernetesDeploymentWithoutServiceDNSMode(t *testing.T) {
+ doTestQueryKubernetesDeploymentWithoutService(t, KubernetesDNSAddress)
+}
+
+func Test_QueryKubernetesDeploymentWithoutServiceIPAddressMode(t *testing.T) {
+ doTestQueryKubernetesDeploymentWithoutService(t, KubernetesIPAddress)
+}
+
+func doTestQueryKubernetesDeploymentWithoutService(t *testing.T, outputFormat
string) {
+ selector := map[string]string{
+ label1: valueLabel1,
+ label2: valueLabel2,
+ }
+
+ deployment := mockDeployment(namespace1, deployment1Name, nil,
&selector)
+ ctg :=
NewServiceCatalog(fake.NewClientBuilder().WithRuntimeObjects(deployment).Build())
+
+ uri := *NewResourceUriBuilder(KubernetesScheme).
+ Group("apps").
+ Version("v1").
+ Kind("deployments").
+ Namespace(namespace1).
+ Name(deployment1Name).Build()
+
+ doTestQueryWithError(t, ctg, uri, outputFormat, fmt.Sprintf("no service
was found for the deployment: %s", uri.Name))
+}
+
+func Test_QueryKubernetesStatefulSetWithServiceDNSMode(t *testing.T) {
+ doTestQueryKubernetesStatefulSetWithService(t, KubernetesDNSAddress,
"http://service1Name.namespace1.svc:80")
+}
+
+func Test_QueryKubernetesStatefulSetWithServiceIPAddressMode(t *testing.T) {
+ doTestQueryKubernetesStatefulSetWithService(t, KubernetesIPAddress,
"http://10.1.18.19:80")
+}
+
+func doTestQueryKubernetesStatefulSetWithService(t *testing.T, outputFormat
string, expectedUri string) {
+ selector := map[string]string{
+ label1: valueLabel1,
+ label2: valueLabel2,
+ }
+
+ statefulSet := mockStatefulSet(namespace1, statefulSet1Name, nil,
&selector)
+
+ service := mockServiceWithPorts(namespace1, service1Name,
mockServicePort(httpProtocol, tcp, defaultHttpPort))
+ service.Spec.Selector = selector
+ service.Spec.ClusterIP = "10.1.18.19"
+ service.Spec.Type = corev1.ServiceTypeNodePort
+
+ cli := fake.NewClientBuilder().WithRuntimeObjects(statefulSet,
service).Build()
+ ctg := NewServiceCatalog(cli)
+
+ doTestQuery(t, ctg, *NewResourceUriBuilder(KubernetesScheme).
+ Group("apps").
+ Version("v1").
+ Kind("statefulsets").
+ Namespace(namespace1).
+ Name(statefulSet1Name).Build(),
+ outputFormat, expectedUri)
+}
+
+func Test_QueryKubernetesStatefulSetWithoutServiceDNSMode(t *testing.T) {
+ doTestQueryKubernetesStatefulSetWithoutService(t, KubernetesDNSAddress)
+}
+
+func Test_QueryKubernetesStatefulSetWithoutServiceIPAddressMode(t *testing.T) {
+ doTestQueryKubernetesStatefulSetWithoutService(t, KubernetesIPAddress)
+}
+
+func doTestQueryKubernetesStatefulSetWithoutService(t *testing.T, outputFormat
string) {
+ selector := map[string]string{
+ label1: valueLabel1,
+ label2: valueLabel2,
+ }
+
+ statefulSet := mockStatefulSet(namespace1, statefulSet1Name, nil,
&selector)
+ ctg :=
NewServiceCatalog(fake.NewClientBuilder().WithRuntimeObjects(statefulSet).Build())
+
+ uri := *NewResourceUriBuilder(KubernetesScheme).
+ Group("apps").
+ Version("v1").
+ Kind("statefulsets").
+ Namespace(namespace1).
+ Name(statefulSet1Name).Build()
+ doTestQueryWithError(t, ctg, uri, outputFormat, fmt.Sprintf("no service
was found for the statefulset: %s", uri.Name))
+}
+
+func Test_QueryKubernetesIngressHostNoTLS(t *testing.T) {
+ doTestQueryKubernetesIngress(t, "myingresshost.com.uy", "", false,
KubernetesIPAddress, "http://myingresshost.com.uy:80")
+}
+
+func Test_QueryKubernetesIngressHostWithTLS(t *testing.T) {
+ doTestQueryKubernetesIngress(t, "myingresshost.com.uy", "", true,
KubernetesIPAddress, "https://myingresshost.com.uy:443")
+}
+
+func Test_QueryKubernetesIngressIPNoTLS(t *testing.T) {
+ doTestQueryKubernetesIngress(t, "", "142.250.184.174", false,
KubernetesIPAddress, "http://142.250.184.174:80")
+}
+
+func Test_QueryKubernetesIngressIPWithTLS(t *testing.T) {
+ doTestQueryKubernetesIngress(t, "", "142.250.184.174", true,
KubernetesIPAddress, "https://142.250.184.174:443")
+}
+
+func doTestQueryKubernetesIngress(t *testing.T, hostName string, ip string,
tls bool, outputFormat string, expectedUri string) {
+ ingress := mockIngress(namespace1, ingress1Name)
+
+ ingress.Status.LoadBalancer.Ingress =
[]v1.IngressLoadBalancerIngress{{Hostname: hostName, IP: ip}}
+ if tls {
+ ingress.Spec.TLS = []v1.IngressTLS{{}}
+ }
+ cli := fake.NewClientBuilder().WithRuntimeObjects(ingress).Build()
+ ctg := NewServiceCatalog(cli)
+ doTestQuery(t, ctg, *NewResourceUriBuilder(KubernetesScheme).
+ Kind("ingresses").
+ Group("networking.k8s.io").
+ Version("v1").
+ Namespace(namespace1).
+ Name(ingress1Name).Build(), outputFormat, expectedUri)
}
func doTestQuery(t *testing.T, ctg ServiceCatalog, resourceUri ResourceUri,
outputFormat, expectedUri string) {
@@ -90,3 +251,8 @@ func doTestQuery(t *testing.T, ctg ServiceCatalog,
resourceUri ResourceUri, outp
assert.NoError(t, err)
assert.Equal(t, expectedUri, uri)
}
+
+func doTestQueryWithError(t *testing.T, ctg ServiceCatalog, resourceUri
ResourceUri, outputFormat string, expectedErrorMessage string) {
+ _, err := ctg.Query(context.TODO(), resourceUri, outputFormat)
+ assert.ErrorContains(t, err, expectedErrorMessage)
+}
diff --git a/controllers/discovery/knative_catalog.go
b/controllers/discovery/knative_catalog.go
index ad7fb011..77760015 100644
--- a/controllers/discovery/knative_catalog.go
+++ b/controllers/discovery/knative_catalog.go
@@ -1,16 +1,21 @@
-// Copyright 2023 Red Hat, Inc. and/or its affiliates
-//
-// 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.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 discovery
diff --git a/controllers/discovery/kubernetes_catalog.go
b/controllers/discovery/kubernetes_catalog.go
index eb526e75..324a798e 100644
--- a/controllers/discovery/kubernetes_catalog.go
+++ b/controllers/discovery/kubernetes_catalog.go
@@ -1,16 +1,21 @@
-// Copyright 2023 Red Hat, Inc. and/or its affiliates
-//
-// 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.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 discovery
@@ -22,8 +27,11 @@ import (
)
const (
- serviceKind = "services"
- podKind = "pods"
+ serviceKind = "services"
+ podKind = "pods"
+ deploymentKind = "deployments"
+ statefulSetKind = "statefulsets"
+ ingressKind = "ingresses"
)
type k8sServiceCatalog struct {
@@ -42,8 +50,14 @@ func (c k8sServiceCatalog) Query(ctx context.Context, uri
ResourceUri, outputFor
return c.resolveServiceQuery(ctx, uri, outputFormat)
case podKind:
return c.resolvePodQuery(ctx, uri, outputFormat)
+ case deploymentKind:
+ return c.resolveDeploymentQuery(ctx, uri, outputFormat)
+ case statefulSetKind:
+ return c.resolveStatefulSetQuery(ctx, uri, outputFormat)
+ case ingressKind:
+ return c.resolveIngressQuery(ctx, uri)
default:
- return "", fmt.Errorf("resolution of kind: %s is not yet
implemented", uri.GVK.Kind)
+ return "", fmt.Errorf("resolution of kind: %s is not
implemented", uri.GVK.Kind)
}
}
@@ -58,21 +72,67 @@ func (c k8sServiceCatalog) resolveServiceQuery(ctx
context.Context, uri Resource
}
func (c k8sServiceCatalog) resolvePodQuery(ctx context.Context, uri
ResourceUri, outputFormat string) (string, error) {
- if pod, service, err := findPodAndReferenceServiceByPodLabels(ctx,
c.Client, uri.Namespace, uri.Name); err != nil {
+ if pod, serviceList, err := findPodAndReferenceServices(ctx, c.Client,
uri.Namespace, uri.Name); err != nil {
return "", err
} else {
- if service != nil {
- if serviceUri, err := resolveServiceUri(service,
uri.GetPort(), outputFormat); err != nil {
- return "", err
- } else {
- return serviceUri, nil
- }
+ if serviceList != nil && len(serviceList.Items) > 0 {
+ referenceService :=
selectBestSuitedServiceByCustomLabels(serviceList, uri.GetCustomLabels())
+ return resolveServiceUri(referenceService,
uri.GetPort(), outputFormat)
} else {
- if podUri, err := resolvePodUri(pod, "", uri.GetPort(),
outputFormat); err != nil {
- return "", err
- } else {
- return podUri, nil
- }
+ return resolvePodUri(pod, "", uri.GetPort(),
outputFormat)
}
}
}
+
+func (c k8sServiceCatalog) resolveDeploymentQuery(ctx context.Context, uri
ResourceUri, outputFormat string) (string, error) {
+ if deployment, err := findDeployment(ctx, c.Client, uri.Namespace,
uri.Name); err != nil {
+ return "", err
+ } else {
+ if serviceList, err := findServicesBySelectorTarget(ctx,
c.Client, uri.Namespace, deployment.Spec.Selector.MatchLabels); err != nil {
+ return "", err
+ } else if len(serviceList.Items) == 0 {
+ return "", fmt.Errorf("no service was found for the
deployment: %s in namespace: %s", uri.Name, uri.Namespace)
+ } else {
+ referenceService :=
selectBestSuitedServiceByCustomLabels(serviceList, uri.GetCustomLabels())
+ return resolveServiceUri(referenceService,
uri.GetPort(), outputFormat)
+ }
+ }
+}
+
+func (c k8sServiceCatalog) resolveStatefulSetQuery(ctx context.Context, uri
ResourceUri, outputFormat string) (string, error) {
+ if statefulSet, err := findStatefulSet(ctx, c.Client, uri.Namespace,
uri.Name); err != nil {
+ return "", err
+ } else {
+ if serviceList, err := findServicesBySelectorTarget(ctx,
c.Client, uri.Namespace, statefulSet.Spec.Selector.MatchLabels); err != nil {
+ return "", err
+ } else if len(serviceList.Items) == 0 {
+ return "", fmt.Errorf("no service was found for the
statefulset: %s in namespace: %s", uri.Name, uri.Namespace)
+ } else {
+ referenceService :=
selectBestSuitedServiceByCustomLabels(serviceList, uri.GetCustomLabels())
+ return resolveServiceUri(referenceService,
uri.GetPort(), outputFormat)
+ }
+ }
+}
+
+func (c k8sServiceCatalog) resolveIngressQuery(ctx context.Context, uri
ResourceUri) (string, error) {
+ if ingress, err := findIngress(ctx, c.Client, uri.Namespace, uri.Name);
err != nil {
+ return "", err
+ } else {
+ // for now stick with the first ip or hostname.
+ loadBalancer := ingress.Status.LoadBalancer.Ingress[0]
+ var scheme = httpProtocol
+ var host string
+ var port = defaultHttpPort
+ if len(loadBalancer.Hostname) > 0 {
+ host = loadBalancer.Hostname
+ } else {
+ host = loadBalancer.IP
+ }
+ // An Ingress does not expose arbitrary ports or protocols
other than HTTP and HTTPS
+ if len(ingress.Spec.TLS) >= 1 {
+ scheme = httpsProtocol
+ port = defaultHttpsPort
+ }
+ return buildURI(scheme, host, port), nil
+ }
+}
diff --git a/controllers/discovery/kubernetes_constants.go
b/controllers/discovery/kubernetes_constants.go
new file mode 100644
index 00000000..78d472d1
--- /dev/null
+++ b/controllers/discovery/kubernetes_constants.go
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 discovery
+
+const (
+ httpProtocol = "http"
+ httpsProtocol = "https"
+ webProtocol = "web"
+ defaultHttpPort = 80
+ defaultHttpsPort = 443
+ defaultAppSecurePort = 8443
+)
diff --git a/controllers/discovery/port_utils.go
b/controllers/discovery/port_utils.go
index 891995c6..207d7c6b 100644
--- a/controllers/discovery/port_utils.go
+++ b/controllers/discovery/port_utils.go
@@ -1,16 +1,21 @@
-// Copyright 2023 Red Hat, Inc. and/or its affiliates
-//
-// 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.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 discovery
@@ -19,16 +24,8 @@ import (
corev1 "k8s.io/api/core/v1"
)
-const (
- httpProtocolName = "http"
- httpsProtocolName = "https"
- webProtocolName = "web"
- securePort = 443
- appSecurePort = 8443
-)
-
func isSecurePort(port int) bool {
- return port == securePort || port == appSecurePort
+ return port == defaultHttpsPort || port == defaultAppSecurePort
}
// findBestSuitedServicePort returns the best suited ServicePort to connect to
a service.
@@ -42,15 +39,15 @@ func findBestSuitedServicePort(service *corev1.Service,
customPort string) *core
}
}
// has ssl port?
- if result, _ := kubernetes.GetServicePortByName(httpsProtocolName,
service); result != nil {
+ if result, _ := kubernetes.GetServicePortByName(httpsProtocol,
service); result != nil {
return result
}
// has http port?
- if result, _ := kubernetes.GetServicePortByName(httpProtocolName,
service); result != nil {
+ if result, _ := kubernetes.GetServicePortByName(httpProtocol, service);
result != nil {
return result
}
// has web port?
- if result, _ := kubernetes.GetServicePortByName(webProtocolName,
service); result != nil {
+ if result, _ := kubernetes.GetServicePortByName(webProtocol, service);
result != nil {
return result
}
// by definition a service must always have at least one port, get the
first port.
@@ -58,7 +55,7 @@ func findBestSuitedServicePort(service *corev1.Service,
customPort string) *core
}
func isSecureServicePort(servicePort *corev1.ServicePort) bool {
- return servicePort.Name == httpsProtocolName ||
isSecurePort(int(servicePort.Port))
+ return servicePort.Name == httpsProtocol ||
isSecurePort(int(servicePort.Port))
}
// findBestSuitedContainerPort returns the best suited PortPort to connect to
a pod, or nil if the pod has no ports at all.
@@ -76,15 +73,15 @@ func findBestSuitedContainerPort(container
*corev1.Container, customPort string)
}
}
// has ssl port?
- if result, _ := kubernetes.GetContainerPortByName(httpsProtocolName,
container); result != nil {
+ if result, _ := kubernetes.GetContainerPortByName(httpsProtocol,
container); result != nil {
return result
}
// has http port?
- if result, _ := kubernetes.GetContainerPortByName(httpProtocolName,
container); result != nil {
+ if result, _ := kubernetes.GetContainerPortByName(httpProtocol,
container); result != nil {
return result
}
// has web port?
- if result, _ := kubernetes.GetContainerPortByName(webProtocolName,
container); result != nil {
+ if result, _ := kubernetes.GetContainerPortByName(webProtocol,
container); result != nil {
return result
}
// when defined, a ContainerPort must always have containerPort
(Required value)
@@ -92,5 +89,5 @@ func findBestSuitedContainerPort(container *corev1.Container,
customPort string)
}
func isSecureContainerPort(containerPort *corev1.ContainerPort) bool {
- return containerPort.Name == httpsProtocolName ||
isSecurePort(int(containerPort.ContainerPort))
+ return containerPort.Name == httpsProtocol ||
isSecurePort(int(containerPort.ContainerPort))
}
diff --git a/controllers/discovery/port_utils_test.go
b/controllers/discovery/port_utils_test.go
index 367dc428..9e01b9bc 100644
--- a/controllers/discovery/port_utils_test.go
+++ b/controllers/discovery/port_utils_test.go
@@ -1,16 +1,21 @@
-// Copyright 2023 Red Hat, Inc. and/or its affiliates
-//
-// 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.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 discovery
@@ -29,44 +34,44 @@ func TestIsSecurePort(t *testing.T) {
}
func TestBestSuitedServicePort_BestIsCustomPort(t *testing.T) {
- service := mockServiceWithPorts(namespace1, service1,
mockServicePort("not-wanted", tcp, 8282),
- mockServicePort(httpsProtocolName, tcp, defaultHttps),
- mockServicePort(customPortName, tcp, defaultHttp))
+ service := mockServiceWithPorts(namespace1, service1Name,
mockServicePort("not-wanted", tcp, 8282),
+ mockServicePort(httpsProtocol, tcp, defaultHttpsPort),
+ mockServicePort(customPortName, tcp, defaultHttpPort))
doTestBestSuitedServicePort(t, service, customPortName,
&service.Spec.Ports[2])
}
func TestBestSuitedServicePort_BestIsHttpsPort(t *testing.T) {
- service := mockServiceWithPorts(namespace1, service1,
mockServicePort("not-wanted", tcp, 8282),
- mockServicePort(httpProtocolName, tcp, defaultHttp),
- mockServicePort(httpsProtocolName, tcp, defaultHttps))
+ service := mockServiceWithPorts(namespace1, service1Name,
mockServicePort("not-wanted", tcp, 8282),
+ mockServicePort(httpProtocol, tcp, defaultHttpPort),
+ mockServicePort(httpsProtocol, tcp, defaultHttpsPort))
doTestBestSuitedServicePort(t, service, "", &service.Spec.Ports[2])
}
func TestBestSuitedServicePort_BestIsHttpPort(t *testing.T) {
- service := mockServiceWithPorts(namespace1, service1,
mockServicePort("not-wanted", tcp, 8282),
- mockServicePort(webProtocolName, tcp, 81),
- mockServicePort(httpProtocolName, tcp, defaultHttp))
+ service := mockServiceWithPorts(namespace1, service1Name,
mockServicePort("not-wanted", tcp, 8282),
+ mockServicePort(webProtocol, tcp, 81),
+ mockServicePort(httpProtocol, tcp, defaultHttpPort))
doTestBestSuitedServicePort(t, service, "", &service.Spec.Ports[2])
}
func TestBestSuitedServicePort_BestWebPort(t *testing.T) {
- service := mockServiceWithPorts(namespace1, service1,
mockServicePort("not-wanted", tcp, 8282),
- mockServicePort(webProtocolName, tcp, 81))
+ service := mockServiceWithPorts(namespace1, service1Name,
mockServicePort("not-wanted", tcp, 8282),
+ mockServicePort(webProtocol, tcp, 81))
doTestBestSuitedServicePort(t, service, "", &service.Spec.Ports[1])
}
func TestBestSuitedServicePort_BestIsFirst(t *testing.T) {
- service := mockServiceWithPorts(namespace1, service1,
mockServicePort("first-port", tcp, 8282),
+ service := mockServiceWithPorts(namespace1, service1Name,
mockServicePort("first-port", tcp, 8282),
mockServicePort("second-port", tcp, 8383))
doTestBestSuitedServicePort(t, service, "", &service.Spec.Ports[0])
}
func TestIsSecureServicePort(t *testing.T) {
- servicePort := mockServicePort(httpsProtocolName, tcp, 443)
+ servicePort := mockServicePort(httpsProtocol, tcp, 443)
assert.True(t, isSecureServicePort(&servicePort))
servicePort = mockServicePort("other-secure-port", tcp, 443)
assert.True(t, isSecureServicePort(&servicePort))
- servicePort = mockServicePort(httpProtocolName, tcp, 80)
+ servicePort = mockServicePort(httpProtocol, tcp, 80)
assert.False(t, isSecureServicePort(&servicePort))
}
@@ -81,28 +86,28 @@ func TestBestSuitedContainerPort_ContainerWithNoPorts(t
*testing.T) {
func TestBestSuitedContainerPort_BestIsCustomPort(t *testing.T) {
container := mockContainerWithPorts("", mockContainerPort("not-wanted",
tcp, 8282),
- mockContainerPort(httpsProtocolName, tcp, defaultHttps),
- mockContainerPort(customPortName, tcp, defaultHttp))
+ mockContainerPort(httpsProtocol, tcp, defaultHttpsPort),
+ mockContainerPort(customPortName, tcp, defaultHttpPort))
doTestBestSuitedContainerPort(t, container, customPortName,
&container.Ports[2])
}
func TestBestSuitedContainerPort_BestIsHttpsPort(t *testing.T) {
container := mockContainerWithPorts("", mockContainerPort("not-wanted",
tcp, 8282),
- mockContainerPort(httpProtocolName, tcp, defaultHttp),
- mockContainerPort(httpsProtocolName, tcp, defaultHttps))
+ mockContainerPort(httpProtocol, tcp, defaultHttpPort),
+ mockContainerPort(httpsProtocol, tcp, defaultHttpsPort))
doTestBestSuitedContainerPort(t, container, "", &container.Ports[2])
}
func TestBestSuitedContainerPort_BestIsHttpPort(t *testing.T) {
container := mockContainerWithPorts("", mockContainerPort("not-wanted",
tcp, 8282),
- mockContainerPort(webProtocolName, tcp, 81),
- mockContainerPort(httpProtocolName, tcp, defaultHttp))
+ mockContainerPort(webProtocol, tcp, 81),
+ mockContainerPort(httpProtocol, tcp, defaultHttpsPort))
doTestBestSuitedContainerPort(t, container, "", &container.Ports[2])
}
func TestBestSuitedContainerPort_BestWebPort(t *testing.T) {
container := mockContainerWithPorts("", mockContainerPort("not-wanted",
tcp, 8282),
- mockContainerPort(webProtocolName, tcp, 81))
+ mockContainerPort(webProtocol, tcp, 81))
doTestBestSuitedContainerPort(t, container, "", &container.Ports[1])
}
@@ -118,10 +123,10 @@ func doTestBestSuitedContainerPort(t *testing.T,
container *corev1.Container, cu
}
func TestIsSecureContainerPort(t *testing.T) {
- containerPort := mockContainerPort(httpsProtocolName, tcp, 443)
+ containerPort := mockContainerPort(httpsProtocol, tcp, 443)
assert.True(t, isSecureContainerPort(&containerPort))
containerPort = mockContainerPort("other-secure-port", tcp, 443)
assert.True(t, isSecureContainerPort(&containerPort))
- containerPort = mockContainerPort(httpProtocolName, tcp, 80)
+ containerPort = mockContainerPort(httpProtocol, tcp, 80)
assert.False(t, isSecureContainerPort(&containerPort))
}
diff --git a/controllers/discovery/queries.go b/controllers/discovery/queries.go
index 5710201d..37a7e1a6 100644
--- a/controllers/discovery/queries.go
+++ b/controllers/discovery/queries.go
@@ -1,16 +1,21 @@
-// Copyright 2023 Red Hat, Inc. and/or its affiliates
-//
-// 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.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 discovery
@@ -24,8 +29,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)
-const podTemplateHashLabel = "pod-template-hash"
-
// findService finds a service by name in the given namespace.
func findService(ctx context.Context, cli client.Client, namespace string,
name string) (*corev1.Service, error) {
service := &corev1.Service{}
@@ -35,13 +38,65 @@ func findService(ctx context.Context, cli client.Client,
namespace string, name
return service, nil
}
-// findServiceByLabels finds a service by a set of matching labels in the
given namespace.
-func findServiceByLabels(ctx context.Context, cli client.Client, namespace
string, labels map[string]string) (*corev1.ServiceList, error) {
+// findServicesBySelectorTarget finds the services for which all the
configured selector labels are present in the
+// selection target map.
+func findServicesBySelectorTarget(ctx context.Context, cli client.Client,
namespace string, selectorTarget map[string]string) (*corev1.ServiceList,
error) {
serviceList := &corev1.ServiceList{}
- if err := cli.List(ctx, serviceList, client.InNamespace(namespace),
client.MatchingLabels(labels)); err != nil {
+ items := make([]corev1.Service, 0)
+ if err := cli.List(ctx, serviceList, client.InNamespace(namespace));
err != nil {
return nil, err
+ } else {
+ for _, service := range serviceList.Items {
+ if len(service.Spec.Selector) > 0 &&
containsSubset(selectorTarget, service.Spec.Selector) {
+ items = append(items, service)
+ }
+ }
+ }
+ return &corev1.ServiceList{Items: items}, nil
+}
+
+// selectBestSuitedServiceByCustomLabels In situations where a previous query
returned many Services, for example, to
+// access a set of pods, or a deployment, we can filter them by a set of
customLabels, to determine which one is the best suited.
+func selectBestSuitedServiceByCustomLabels(serviceList *corev1.ServiceList,
customLabels map[string]string) *corev1.Service {
+ var filteredService *corev1.Service = nil
+ if len(serviceList.Items) > 0 {
+ if len(serviceList.Items) == 1 {
+ filteredService = &serviceList.Items[0]
+ } else {
+ filteredService = &serviceList.Items[0]
+ if len(customLabels) > 0 {
+ if filteredServiceList :=
filterServiceListByLabelsSubset(serviceList, customLabels);
len(filteredServiceList.Items) > 0 {
+ filteredService =
&filteredServiceList.Items[0]
+ }
+ }
+ }
+ }
+ return filteredService
+}
+
+func filterServiceListByLabelsSubset(serviceList *corev1.ServiceList, labels
map[string]string) *corev1.ServiceList {
+ var items = make([]corev1.Service, 0)
+ for _, service := range serviceList.Items {
+ if containsSubset(service.Labels, labels) {
+ items = append(items, service)
+ }
+ }
+ return &corev1.ServiceList{Items: items}
+}
+
+func containsSubset(container map[string]string, subset map[string]string)
bool {
+ if container == nil {
+ return subset == nil
+ } else if subset == nil {
+ return true
+ } else {
+ for k, v := range subset {
+ if cv := container[k]; cv != v {
+ return false
+ }
+ }
}
- return serviceList, nil
+ return true
}
// findPod finds a pod by name in the given namespace.
@@ -53,21 +108,18 @@ func findPod(ctx context.Context, cli client.Client,
namespace string, name stri
return pod, nil
}
-// findPodAndReferenceServiceByPodLabels finds a pod by name in the given
namespace at the same time it piggybacks it's
-// reference service if any. The reference service is determined by using the
same set of labels as the pod.
-func findPodAndReferenceServiceByPodLabels(ctx context.Context, cli
client.Client, namespace string, name string) (*corev1.Pod, *corev1.Service,
error) {
+// findPodAndReferenceServices finds a pod by name in the given namespace, at
the same time it piggybacks potential
+// reference services if any. The reference services are determined by looking
if the corresponding selector labels
+// matches the pod labels.
+func findPodAndReferenceServices(ctx context.Context, cli client.Client,
namespace string, name string) (*corev1.Pod, *corev1.ServiceList, error) {
if pod, err := findPod(ctx, cli, namespace, name); err != nil {
return nil, nil, err
} else {
- queryLabels := pod.Labels
- // pod-template-hash is pod dependent, mustn't be considered.
- delete(queryLabels, podTemplateHashLabel)
- if len(queryLabels) > 0 {
- // check if we have a defined reference service
- if serviceList, err := findServiceByLabels(ctx, cli,
namespace, queryLabels); err != nil {
+ if len(pod.Labels) > 0 {
+ if serviceList, err :=
findServicesBySelectorTarget(ctx, cli, namespace, pod.Labels); err != nil {
return nil, nil, err
} else if len(serviceList.Items) > 0 {
- return pod, &serviceList.Items[0], nil
+ return pod, serviceList, nil
}
}
return pod, nil, nil
diff --git a/controllers/discovery/queries_test.go
b/controllers/discovery/queries_test.go
index 4c130407..b0500b78 100644
--- a/controllers/discovery/queries_test.go
+++ b/controllers/discovery/queries_test.go
@@ -1,16 +1,21 @@
-// Copyright 2023 Red Hat, Inc. and/or its affiliates
-//
-// 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.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 discovery
@@ -22,14 +27,13 @@ import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
networkingV1 "k8s.io/api/networking/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
func Test_findService(t *testing.T) {
service := mockService1(nil)
cli := fake.NewClientBuilder().WithRuntimeObjects(service).Build()
- result, err := findService(context.TODO(), cli, namespace1, service1)
+ result, err := findService(context.TODO(), cli, namespace1,
service1Name)
assert.NoError(t, err)
assert.Equal(t, service, result)
@@ -37,45 +41,108 @@ func Test_findService(t *testing.T) {
func Test_findServiceNotFound(t *testing.T) {
cli := fake.NewClientBuilder().Build()
- _, err := findService(context.TODO(), cli, namespace1, service1)
- assert.ErrorContains(t, err, "\"service1\" not found")
+ _, err := findService(context.TODO(), cli, namespace1, service1Name)
+ assert.ErrorContains(t, err, "\"service1Name\" not found")
}
-func Test_findServiceByLabels(t *testing.T) {
- labels := &map[string]string{
+func Test_findServicesBySelectorTarget(t *testing.T) {
+ selector1Labels := &map[string]string{
+ label1: valueLabel1,
+ label2: valueLabel2,
+ }
+ selector2Labels := &map[string]string{
+ label1: valueLabel1,
+ label3: valueLabel3,
+ }
+ selector3Labels := &map[string]string{
label1: valueLabel1,
label2: valueLabel2,
}
- service := mockService1(labels)
- cli := fake.NewClientBuilder().WithRuntimeObjects(service).Build()
- serviceList, err := findServiceByLabels(context.TODO(), cli,
namespace1, *labels)
+
+ service1 := mockService1(selector1Labels)
+ service2 := mockService2(selector2Labels)
+ service3 := mockService3(selector3Labels)
+ cli := fake.NewClientBuilder().WithRuntimeObjects(service1, service2,
service3).Build()
+ serviceList, err := findServicesBySelectorTarget(context.TODO(), cli,
namespace1, *selector1Labels)
assert.NoError(t, err)
- assert.Len(t, serviceList.Items, 1)
- assert.Equal(t, service, &serviceList.Items[0])
+ assert.Len(t, serviceList.Items, 2)
+ assert.Equal(t, service1, &serviceList.Items[0])
+ assert.Equal(t, service3, &serviceList.Items[1])
}
-func Test_findServiceByLabelsNotFound(t *testing.T) {
- labels := &map[string]string{
+func Test_findServicesBySelectorTargetNotFound(t *testing.T) {
+ selectorLabels := &map[string]string{
label1: valueLabel1,
label2: valueLabel2,
}
queryLabels := map[string]string{
label1: valueLabel1,
}
- service := mockService1(labels)
+ service := mockService1(selectorLabels)
cli := fake.NewClientBuilder().WithRuntimeObjects(service).Build()
- serviceList, err := findServiceByLabels(context.TODO(), cli,
namespace1, queryLabels)
+ serviceList, err := findServicesBySelectorTarget(context.TODO(), cli,
namespace1, queryLabels)
assert.NoError(t, err)
- assert.Len(t, serviceList.Items, 1)
- assert.Equal(t, service, &serviceList.Items[0])
+ assert.Len(t, serviceList.Items, 0)
+}
+
+func Test_selectBestSuitedServiceByCustomLabels(t *testing.T) {
+ service1 := mockService1(nil)
+ service1.Labels = map[string]string{
+ label1: valueLabel1,
+ label2: valueLabel2,
+ }
+ service2 := mockService2(nil)
+ service2.Labels = map[string]string{
+ label1: valueLabel1,
+ label3: valueLabel3,
+ "environment": "dev",
+ }
+ service3 := mockService2(nil)
+ service3.Labels = map[string]string{
+ label2: valueLabel2,
+ label3: valueLabel3,
+ }
+ serviceList := &corev1.ServiceList{
+ Items: []corev1.Service{*service1, *service2, *service3},
+ }
+ bestSuitedService := selectBestSuitedServiceByCustomLabels(serviceList,
map[string]string{"environment": "dev"})
+ assert.Equal(t, service2, bestSuitedService)
+}
+
+func Test_filterServiceListByLabelsSubset(t *testing.T) {
+ service1 := mockService1(nil)
+ service1.Labels = map[string]string{
+ label1: valueLabel1,
+ }
+ service2 := mockService2(nil)
+ service2.Labels = map[string]string{
+ label1: valueLabel1,
+ label3: valueLabel3,
+ }
+ service3 := mockService3(nil)
+ service3.Labels = map[string]string{
+ label1: valueLabel1,
+ label2: valueLabel2,
+ label3: valueLabel3,
+ }
+ serviceList := &corev1.ServiceList{
+ Items: []corev1.Service{*service1, *service2, *service3},
+ }
+ filteredServiceList := filterServiceListByLabelsSubset(serviceList,
map[string]string{
+ label1: valueLabel1,
+ label3: valueLabel3})
+
+ assert.Len(t, filteredServiceList.Items, 2)
+ assert.Equal(t, *service2, filteredServiceList.Items[0])
+ assert.Equal(t, *service3, filteredServiceList.Items[1])
}
func Test_findPod(t *testing.T) {
pod := mockPod1(nil)
cli := fake.NewClientBuilder().WithRuntimeObjects(pod).Build()
- result, err := findPod(context.TODO(), cli, namespace1, pod1)
+ result, err := findPod(context.TODO(), cli, namespace1, pod1Name)
assert.NoError(t, err)
assert.Equal(t, pod, result)
@@ -83,45 +150,51 @@ func Test_findPod(t *testing.T) {
func Test_findPodNotFound(t *testing.T) {
cli := fake.NewClientBuilder().Build()
- _, err := findPod(context.TODO(), cli, namespace1, pod1)
- assert.ErrorContains(t, err, "\"pod1\" not found")
+ _, err := findPod(context.TODO(), cli, namespace1, pod1Name)
+ assert.ErrorContains(t, err, "\"pod1Name\" not found")
}
-func Test_findPodAndReferenceServiceByPodLabelsWithReferenceService(t
*testing.T) {
+func Test_findPodAndReferenceServicesWithReferenceService(t *testing.T) {
podLabels := &map[string]string{
label1: valueLabel1,
label2: valueLabel2,
+ label3: valueLabel3,
}
- service := mockService1(podLabels)
+ selectorLabels := &map[string]string{
+ label1: valueLabel1,
+ label2: valueLabel2,
+ }
+ service := mockService1(selectorLabels)
pod := mockPod1(podLabels)
cli := fake.NewClientBuilder().WithRuntimeObjects(service, pod).Build()
- resultPod, resultService, err :=
findPodAndReferenceServiceByPodLabels(context.TODO(), cli, namespace1, pod1)
+ resultPod, referenceServices, err :=
findPodAndReferenceServices(context.TODO(), cli, namespace1, pod1Name)
assert.NoError(t, err)
assert.Equal(t, pod, resultPod)
- assert.Equal(t, service, resultService)
+ assert.Len(t, referenceServices.Items, 1)
+ assert.Equal(t, *service, referenceServices.Items[0])
}
-func Test_findPodAndReferenceServiceByPodLabelsWithoutReferenceService(t
*testing.T) {
+func Test_findPodAndReferenceServicesWithoutReferenceService(t *testing.T) {
podLabels := &map[string]string{
label1: valueLabel1,
- label2: valueLabel2,
}
- serviceLabels := &map[string]string{
+ selectorLabels := &map[string]string{
label1: valueLabel1,
+ label2: valueLabel2,
}
- service := mockService1(serviceLabels)
+ service := mockService1(selectorLabels)
pod := mockPod1(podLabels)
cli := fake.NewClientBuilder().WithRuntimeObjects(service, pod).Build()
- resultPod, resultService, err :=
findPodAndReferenceServiceByPodLabels(context.TODO(), cli, namespace1, pod1)
+ resultPod, referenceServices, err :=
findPodAndReferenceServices(context.TODO(), cli, namespace1, pod1Name)
assert.NoError(t, err)
assert.Equal(t, pod, resultPod)
- assert.Nil(t, resultService)
+ assert.Nil(t, referenceServices)
}
-func Test_findPodAndReferenceServiceByPodLabelsNotFound(t *testing.T) {
+func Test_findPodAndReferenceServicesNotFound(t *testing.T) {
cli := fake.NewClientBuilder().Build()
- resultPod, resultService, err :=
findPodAndReferenceServiceByPodLabels(context.TODO(), cli, namespace1, pod1)
- assert.ErrorContains(t, err, "\"pod1\" not found")
+ resultPod, resultService, err :=
findPodAndReferenceServices(context.TODO(), cli, namespace1, pod1Name)
+ assert.ErrorContains(t, err, "\"pod1Name\" not found")
assert.Nil(t, resultPod)
assert.Nil(t, resultService)
}
@@ -129,7 +202,7 @@ func Test_findPodAndReferenceServiceByPodLabelsNotFound(t
*testing.T) {
func Test_findDeployment(t *testing.T) {
deployment := mockDeployment1(nil)
cli := fake.NewClientBuilder().WithRuntimeObjects(deployment).Build()
- result, err := findDeployment(context.TODO(), cli, namespace1,
deployment1)
+ result, err := findDeployment(context.TODO(), cli, namespace1,
deployment1Name)
assert.NoError(t, err)
assert.Equal(t, deployment, result)
@@ -137,14 +210,14 @@ func Test_findDeployment(t *testing.T) {
func Test_findDeploymentNotFound(t *testing.T) {
cli := fake.NewClientBuilder().Build()
- _, err := findDeployment(context.TODO(), cli, namespace1, deployment1)
- assert.ErrorContains(t, err, "\"deployment1\" not found")
+ _, err := findDeployment(context.TODO(), cli, namespace1,
deployment1Name)
+ assert.ErrorContains(t, err, "\"deployment1Name\" not found")
}
func Test_findStatefulSet(t *testing.T) {
statefulSet := mockStatefulSet1()
cli := fake.NewClientBuilder().WithRuntimeObjects(statefulSet).Build()
- result, err := findStatefulSet(context.TODO(), cli, namespace1,
statefulSet1)
+ result, err := findStatefulSet(context.TODO(), cli, namespace1,
statefulSet1Name)
assert.NoError(t, err)
assert.Equal(t, statefulSet, result)
@@ -152,14 +225,14 @@ func Test_findStatefulSet(t *testing.T) {
func Test_findStatefulSetNotFound(t *testing.T) {
cli := fake.NewClientBuilder().Build()
- _, err := findStatefulSet(context.TODO(), cli, namespace1, statefulSet1)
- assert.ErrorContains(t, err, "\"statefulSet1\" not found")
+ _, err := findStatefulSet(context.TODO(), cli, namespace1,
statefulSet1Name)
+ assert.ErrorContains(t, err, "\"statefulSet1Name\" not found")
}
func Test_findIngress(t *testing.T) {
ingress := mockIngress1()
cli := fake.NewClientBuilder().WithRuntimeObjects(ingress).Build()
- result, err := findIngress(context.TODO(), cli, namespace1, ingress1)
+ result, err := findIngress(context.TODO(), cli, namespace1,
ingress1Name)
assert.NoError(t, err)
assert.Equal(t, ingress, result)
@@ -167,59 +240,42 @@ func Test_findIngress(t *testing.T) {
func Test_findIngressNotFound(t *testing.T) {
cli := fake.NewClientBuilder().Build()
- _, err := findIngress(context.TODO(), cli, namespace1, ingress1)
- assert.ErrorContains(t, err, "\"ingress1\" not found")
+ _, err := findIngress(context.TODO(), cli, namespace1, ingress1Name)
+ assert.ErrorContains(t, err, "\"ingress1Name\" not found")
}
-func mockService1(labels *map[string]string) *corev1.Service {
- return mockService(namespace1, service1, labels)
+func mockService1(selectorLabels *map[string]string) *corev1.Service {
+ return mockService(namespace1, service1Name, nil, selectorLabels)
+}
+
+func mockService2(selectorLabels *map[string]string) *corev1.Service {
+ return mockService(namespace1, service2Name, nil, selectorLabels)
+}
+
+func mockService3(selectorLabels *map[string]string) *corev1.Service {
+ return mockService(namespace1, service3Name, nil, selectorLabels)
}
func mockPod1(labels *map[string]string) *corev1.Pod {
- return mockPod(namespace1, pod1, labels)
+ return mockPod(namespace1, pod1Name, labels)
+}
+
+func mockPod2(labels *map[string]string) *corev1.Pod {
+ return mockPod(namespace1, pod2Name, labels)
+}
+
+func mockPod3(labels *map[string]string) *corev1.Pod {
+ return mockPod(namespace1, pod3Name, labels)
}
func mockDeployment1(labels *map[string]string) *appsv1.Deployment {
- deployment := &appsv1.Deployment{
- TypeMeta: metav1.TypeMeta{
- Kind: "Deployment",
- APIVersion: "apps/v1",
- },
- ObjectMeta: metav1.ObjectMeta{
- Namespace: namespace1,
- Name: deployment1,
- },
- }
- if labels != nil {
- deployment.ObjectMeta.Labels = *labels
- }
- return deployment
+ return mockDeployment(namespace1, deployment1Name, labels, nil)
}
func mockStatefulSet1() *appsv1.StatefulSet {
- statefulSet := &appsv1.StatefulSet{
- TypeMeta: metav1.TypeMeta{
- Kind: "StatefulSet",
- APIVersion: "apps/v1",
- },
- ObjectMeta: metav1.ObjectMeta{
- Namespace: namespace1,
- Name: statefulSet1,
- },
- }
- return statefulSet
+ return mockStatefulSet(namespace1, statefulSet1Name, nil, nil)
}
func mockIngress1() *networkingV1.Ingress {
- ingress := &networkingV1.Ingress{
- TypeMeta: metav1.TypeMeta{
- Kind: "Ingress",
- APIVersion: "networking.k8s.io/v1",
- },
- ObjectMeta: metav1.ObjectMeta{
- Namespace: namespace1,
- Name: ingress1,
- },
- }
- return ingress
+ return mockIngress(namespace1, ingress1Name)
}
diff --git a/controllers/discovery/test_utils.go
b/controllers/discovery/test_utils.go
index d56bfb05..b84b932b 100644
--- a/controllers/discovery/test_utils.go
+++ b/controllers/discovery/test_utils.go
@@ -1,45 +1,63 @@
-// Copyright 2023 Red Hat, Inc. and/or its affiliates
-//
-// 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.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 discovery
import (
+ "fmt"
+
+ appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
+ networkingV1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/types"
)
const (
- namespace1 = "namespace1"
- service1 = "service1"
- deployment1 = "deployment1"
- statefulSet1 = "statefulSet1"
- pod1 = "pod1"
- container1 = "container1"
- container2 = "container2"
- ingress1 = "ingress1"
- label1 = "label1"
- valueLabel1 = "valueLabel1"
- label2 = "label2"
- valueLabel2 = "valueLabel2"
-
- customPortName = "my-custom-port"
- defaultHttp = 80
- defaultHttps = 443
- tcp = "TCP"
+ namespace1 = "namespace1"
+ service1Name = "service1Name"
+ service2Name = "service2Name"
+ service3Name = "service3Name"
+ deployment1Name = "deployment1Name"
+ statefulSet1Name = "statefulSet1Name"
+ pod1Name = "pod1Name"
+ pod2Name = "pod2Name"
+ pod3Name = "pod3Name"
+ container1Name = "container1Name"
+ container2Name = "container2Name"
+ ingress1Name = "ingress1Name"
+ label1 = "label1"
+ valueLabel1 = "valueLabel1"
+ label2 = "label2"
+ valueLabel2 = "valueLabel2"
+ label3 = "label3"
+ valueLabel3 = "valueLabel3"
+ customPortName = "my-custom-port"
+ tcp = "TCP"
+ uidOwner1 = "uidOwner1"
+ uidOwner2 = "uidOwner2"
+ replicaSet1Name = "replicaSet1Name"
+ replicaSet2Name = "replicaSet2Name"
+ replicaSet3Name = "replicaSet3Name"
)
-func mockService(namespace string, name string, labels *map[string]string)
*corev1.Service {
+func mockService(namespace string, name string, labels *map[string]string,
selectorLabels *map[string]string) *corev1.Service {
service := &corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
@@ -53,11 +71,14 @@ func mockService(namespace string, name string, labels
*map[string]string) *core
if labels != nil {
service.ObjectMeta.Labels = *labels
}
+ if selectorLabels != nil {
+ service.Spec.Selector = *selectorLabels
+ }
return service
}
func mockServiceWithPorts(namespace string, name string, ports
...corev1.ServicePort) *corev1.Service {
- service := mockService(namespace, name, &map[string]string{})
+ service := mockService(namespace, name, &map[string]string{}, nil)
service.Spec.Ports = ports
return service
}
@@ -108,3 +129,75 @@ func mockContainerPort(name string, protocol string, port
int32) corev1.Containe
Protocol: corev1.Protocol(protocol),
}
}
+
+func mockReplicaSet(namespace string, name string, ownerReferenceUID string)
*appsv1.ReplicaSet {
+ replicaSet := &appsv1.ReplicaSet{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "ReplicaSet",
+ APIVersion: "apps/v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: namespace,
+ Name: name,
+ OwnerReferences: []metav1.OwnerReference{{UID:
types.UID(ownerReferenceUID)}},
+ UID:
types.UID(fmt.Sprintf("%s-%s-mock-replicaset-uid", namespace, name)),
+ },
+ }
+ return replicaSet
+}
+
+func mockDeployment(namespace string, name string, labels *map[string]string,
selector *map[string]string) *appsv1.Deployment {
+ deployment := &appsv1.Deployment{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "Deployment",
+ APIVersion: "apps/v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: namespace,
+ Name: name,
+ UID:
types.UID(fmt.Sprintf("%s-%s-mock-deployment-uid", namespace, name)),
+ },
+ }
+ if labels != nil {
+ deployment.ObjectMeta.Labels = *labels
+ }
+ if selector != nil {
+ deployment.Spec.Selector = &metav1.LabelSelector{MatchLabels:
*selector}
+ }
+ return deployment
+}
+
+func mockStatefulSet(namespace string, name string, labels *map[string]string,
selector *map[string]string) *appsv1.StatefulSet {
+ statefulSet := &appsv1.StatefulSet{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "StatefulSet",
+ APIVersion: "apps/v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: namespace,
+ Name: name,
+ UID:
types.UID(fmt.Sprintf("%s-%s-mock-statefulset-uid", namespace, name)),
+ },
+ }
+ if labels != nil {
+ statefulSet.ObjectMeta.Labels = *labels
+ }
+ if selector != nil {
+ statefulSet.Spec.Selector = &metav1.LabelSelector{MatchLabels:
*selector}
+ }
+ return statefulSet
+}
+
+func mockIngress(namespace string, name string) *networkingV1.Ingress {
+ ingress := &networkingV1.Ingress{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "Ingress",
+ APIVersion: "networking.k8s.io/v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: namespace,
+ Name: name,
+ },
+ }
+ return ingress
+}
diff --git a/controllers/discovery/uri_parser.go
b/controllers/discovery/uri_parser.go
index 4f7425c6..1096780a 100644
--- a/controllers/discovery/uri_parser.go
+++ b/controllers/discovery/uri_parser.go
@@ -1,16 +1,21 @@
-// Copyright 2023 Red Hat, Inc. and/or its affiliates
-//
-// 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.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 discovery
@@ -74,14 +79,13 @@ func parseKubernetesUri(uri string, schemaAndGroup string,
after string) (*Resou
if queryParams, err = parseQueryParams(uri, split[1]); err !=
nil {
return nil, err
}
-
gvk, _ := parseGVK(schemaAndGroup)
return &ResourceUri{
- Scheme: KubernetesScheme,
- GVK: *gvk,
- Namespace: namespace,
- Name: name,
- CustomLabels: queryParams,
+ Scheme: KubernetesScheme,
+ GVK: *gvk,
+ Namespace: namespace,
+ Name: name,
+ QueryParams: queryParams,
}, nil
} else {
diff --git a/controllers/discovery/uri_parser_test.go
b/controllers/discovery/uri_parser_test.go
index 32cb158b..9b72d1d5 100644
--- a/controllers/discovery/uri_parser_test.go
+++ b/controllers/discovery/uri_parser_test.go
@@ -1,16 +1,21 @@
-// Copyright 2023 Red Hat, Inc. and/or its affiliates
-//
-// 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.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 discovery
@@ -40,7 +45,7 @@ var KubernetesServicesTestValues = map[string]*ResourceUri{
Kind("services").
Version("v1").
Name("my-service").
- WithLabel("label-a", "value-a").Build(),
+ WithQueryParam("label-a", "value-a").Build(),
"kubernetes:services.v1/my-service?label-a=value-a&": nil,
@@ -52,8 +57,8 @@ var KubernetesServicesTestValues = map[string]*ResourceUri{
Kind("services").
Version("v1").
Name("my-service").
- WithLabel("label-a", "value-a").
- WithLabel("label-b", "value-b").Build(),
+ WithQueryParam("label-a", "value-a").
+ WithQueryParam("label-b", "value-b").Build(),
"kubernetes:services.v1/my-namespace/": nil,
@@ -77,7 +82,7 @@ var KubernetesServicesTestValues = map[string]*ResourceUri{
Version("v1").
Namespace("my-namespace").
Name("my-service").
- WithLabel("label-a", "value-a").Build(),
+ WithQueryParam("label-a", "value-a").Build(),
"kubernetes:services.v1/my-namespace/my-service?label-a=value-a&": nil,
@@ -85,13 +90,14 @@ var KubernetesServicesTestValues = map[string]*ResourceUri{
"kubernetes:services.v1/my-namespace/my-service?label-a=value-a&label-b=": nil,
-
"kubernetes:services.v1/my-namespace/my-service?label-a=value-a&label-b=value-b":
NewResourceUriBuilder(KubernetesScheme).
+
"kubernetes:services.v1/my-namespace/my-service?label-a=value-a&label-b=value-b&port=custom-port-value":
NewResourceUriBuilder(KubernetesScheme).
Kind("services").
Version("v1").
Namespace("my-namespace").
Name("my-service").
- WithLabel("label-a", "value-a").
- WithLabel("label-b", "value-b").Build(),
+ WithQueryParam("label-a", "value-a").
+ WithQueryParam("label-b", "value-b").
+ WithPort("custom-port-value").Build(),
}
func TestParseKubernetesServicesURI(t *testing.T) {
@@ -122,9 +128,9 @@ func assertEquals(t *testing.T, uri *ResourceUri,
expectedUri *ResourceUri) {
assert.Equal(t, uri.GVK.Group, expectedUri.GVK.Group)
assert.Equal(t, uri.GVK.Version, expectedUri.GVK.Version)
assert.Equal(t, uri.GVK.Kind, expectedUri.GVK.Kind)
- assert.Equal(t, len(uri.CustomLabels), len(expectedUri.CustomLabels))
- for k, v := range uri.CustomLabels {
- assert.True(t, len(expectedUri.CustomLabels[k]) > 0, "label %s
is not present in expectedUri: %s", k, expectedUri.String())
- assert.Equal(t, v, expectedUri.CustomLabels[k], "value for
label %s in expectedUri should be %s, but is %s", k, v,
expectedUri.CustomLabels[k])
+ assert.Equal(t, len(uri.QueryParams), len(expectedUri.QueryParams))
+ for k, v := range uri.QueryParams {
+ assert.True(t, len(expectedUri.QueryParams[k]) > 0, "label %s
is not present in expectedUri: %s", k, expectedUri.String())
+ assert.Equal(t, v, expectedUri.QueryParams[k], "value for label
%s in expectedUri should be %s, but is %s", k, v, expectedUri.QueryParams[k])
}
}
diff --git a/controllers/discovery/uri_utils.go
b/controllers/discovery/uri_utils.go
index 4c1de70e..a3092d5c 100644
--- a/controllers/discovery/uri_utils.go
+++ b/controllers/discovery/uri_utils.go
@@ -1,16 +1,21 @@
-// Copyright 2023 Red Hat, Inc. and/or its affiliates
-//
-// 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.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 discovery
@@ -32,7 +37,7 @@ func resolveServiceUri(service *corev1.Service, customPort
string, outputFormat
case corev1.ServiceTypeExternalName:
// ExternalName may not work properly with SSL:
//
https://kubernetes.io/docs/concepts/services-networking/service/#externalname
- protocol = httpProtocolName
+ protocol = httpProtocol
host = service.Spec.ExternalName
port = 80
case corev1.ServiceTypeClusterIP:
@@ -60,9 +65,9 @@ func resolveServiceUri(service *corev1.Service, customPort
string, outputFormat
func resolveClusterIPOrTypeNodeServiceUriParams(service *corev1.Service,
customPort string) (protocol string, host string, port int) {
servicePort := findBestSuitedServicePort(service, customPort)
if isSecureServicePort(servicePort) {
- protocol = httpsProtocolName
+ protocol = httpsProtocol
} else {
- protocol = httpProtocolName
+ protocol = httpProtocol
}
host = service.Spec.ClusterIP
port = int(servicePort.Port)
@@ -83,9 +88,9 @@ func resolvePodUri(pod *corev1.Pod, customContainer string,
customPort string, o
if containerPort := findBestSuitedContainerPort(container,
customPort); containerPort == nil {
return "", fmt.Errorf("no container port was found for
pod: %s in namespace: %s", pod.Name, pod.Namespace)
} else {
- protocol := httpProtocolName
+ protocol := httpProtocol
if isSecure := isSecureContainerPort(containerPort);
isSecure {
- protocol = httpsProtocolName
+ protocol = httpsProtocol
}
if outputFormat == KubernetesDNSAddress {
return buildKubernetesPodDNSUri(protocol,
pod.Namespace, podIp, int(containerPort.ContainerPort)), nil
diff --git a/controllers/discovery/uri_utils_test.go
b/controllers/discovery/uri_utils_test.go
index 6c5fa703..3d296114 100644
--- a/controllers/discovery/uri_utils_test.go
+++ b/controllers/discovery/uri_utils_test.go
@@ -1,16 +1,21 @@
-// Copyright 2023 Red Hat, Inc. and/or its affiliates
-//
-// 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.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 discovery
@@ -22,35 +27,35 @@ import (
)
func Test_resolveServiceUriClusterIPServiceDNSMode(t *testing.T) {
- service := mockServiceWithPorts(namespace1, service1,
mockServicePort(httpProtocolName, tcp, defaultHttp))
- doTestResolveServiceUri(t, service, corev1.ServiceTypeClusterIP,
KubernetesDNSAddress, "http://service1.namespace1.svc:80")
+ service := mockServiceWithPorts(namespace1, service1Name,
mockServicePort(httpProtocol, tcp, defaultHttpPort))
+ doTestResolveServiceUri(t, service, corev1.ServiceTypeClusterIP,
KubernetesDNSAddress, "http://service1Name.namespace1.svc:80")
}
func Test_resolveServiceUriClusterIPServiceIPAddressMode(t *testing.T) {
- service := mockServiceWithPorts(namespace1, service1,
mockServicePort(httpProtocolName, tcp, defaultHttp))
+ service := mockServiceWithPorts(namespace1, service1Name,
mockServicePort(httpProtocol, tcp, defaultHttpPort))
service.Spec.ClusterIP = "10.1.15.16"
doTestResolveServiceUri(t, service, corev1.ServiceTypeClusterIP,
KubernetesIPAddress, "http://10.1.15.16:80")
}
func Test_resolveServiceUriNodeTypeServiceDNSMode(t *testing.T) {
- service := mockServiceWithPorts(namespace1, service1,
mockServicePort(httpProtocolName, tcp, defaultHttp))
- doTestResolveServiceUri(t, service, corev1.ServiceTypeNodePort,
KubernetesDNSAddress, "http://service1.namespace1.svc:80")
+ service := mockServiceWithPorts(namespace1, service1Name,
mockServicePort(httpProtocol, tcp, defaultHttpPort))
+ doTestResolveServiceUri(t, service, corev1.ServiceTypeNodePort,
KubernetesDNSAddress, "http://service1Name.namespace1.svc:80")
}
func Test_resolveServiceUriNodeTypeServiceIPAddressMode(t *testing.T) {
- service := mockServiceWithPorts(namespace1, service1,
mockServicePort(httpProtocolName, tcp, defaultHttp))
+ service := mockServiceWithPorts(namespace1, service1Name,
mockServicePort(httpProtocol, tcp, defaultHttpPort))
service.Spec.ClusterIP = "10.1.15.16"
doTestResolveServiceUri(t, service, corev1.ServiceTypeNodePort,
KubernetesIPAddress, "http://10.1.15.16:80")
}
func Test_resolveServiceUriExternalNameServiceDNSMode(t *testing.T) {
- service := mockServiceWithPorts(namespace1, service1,
mockServicePort(httpProtocolName, tcp, defaultHttp))
+ service := mockServiceWithPorts(namespace1, service1Name,
mockServicePort(httpProtocol, tcp, defaultHttpPort))
service.Spec.ExternalName = "external.service.com"
doTestResolveServiceUri(t, service, corev1.ServiceTypeExternalName,
KubernetesIPAddress, "http://external.service.com:80")
}
func Test_resolveServiceUriExternalNameServiceIPAddressMode(t *testing.T) {
- service := mockServiceWithPorts(namespace1, service1,
mockServicePort(httpProtocolName, tcp, defaultHttp))
+ service := mockServiceWithPorts(namespace1, service1Name,
mockServicePort(httpProtocol, tcp, defaultHttpPort))
service.Spec.ExternalName = "external.service.com"
doTestResolveServiceUri(t, service, corev1.ServiceTypeExternalName,
KubernetesIPAddress, "http://external.service.com:80")
}
@@ -63,40 +68,40 @@ func doTestResolveServiceUri(t *testing.T, service
*corev1.Service, serviceType
}
func Test_resolvePodUriDNSMode(t *testing.T) {
- pod := mockPodWithContainers(namespace1, pod1,
- *mockContainerWithPorts(container1,
mockContainerPort(httpProtocolName, tcp, defaultHttp)),
- *mockContainerWithPorts(container2,
mockContainerPort(httpsProtocolName, tcp, defaultHttps)))
+ pod := mockPodWithContainers(namespace1, pod1Name,
+ *mockContainerWithPorts(container1Name,
mockContainerPort(httpProtocol, tcp, defaultHttpPort)),
+ *mockContainerWithPorts(container2Name,
mockContainerPort(httpsProtocol, tcp, defaultHttpsPort)))
pod.Status.PodIP = "10.1.15.16"
doTestResolvePodUri(t, pod, "", "", KubernetesDNSAddress,
"http://10-1-15-16.namespace1.pod:80")
}
func Test_resolvePodUriIPAddressMode(t *testing.T) {
- pod := mockPodWithContainers(namespace1, pod1,
- *mockContainerWithPorts(container1,
mockContainerPort(httpProtocolName, tcp, defaultHttp)),
- *mockContainerWithPorts(container2,
mockContainerPort(httpsProtocolName, tcp, defaultHttps)))
+ pod := mockPodWithContainers(namespace1, pod1Name,
+ *mockContainerWithPorts(container1Name,
mockContainerPort(httpProtocol, tcp, defaultHttpPort)),
+ *mockContainerWithPorts(container2Name,
mockContainerPort(httpsProtocol, tcp, defaultHttpsPort)))
pod.Status.PodIP = "10.1.15.17"
doTestResolvePodUri(t, pod, "", "", KubernetesIPAddress,
"http://10.1.15.17:80")
}
func Test_resolvePodUriByCustomContainerDNSMode(t *testing.T) {
- pod := mockPodWithContainers(namespace1, pod1,
- *mockContainerWithPorts(container1,
mockContainerPort(httpsProtocolName, tcp, defaultHttps)),
- *mockContainerWithPorts("custom-container",
mockContainerPort(httpProtocolName, tcp, defaultHttp)))
+ pod := mockPodWithContainers(namespace1, pod1Name,
+ *mockContainerWithPorts(container1Name,
mockContainerPort(httpsProtocol, tcp, defaultHttpsPort)),
+ *mockContainerWithPorts("custom-container",
mockContainerPort(httpProtocol, tcp, defaultHttpPort)))
pod.Status.PodIP = "10.1.15.16"
doTestResolvePodUri(t, pod, "custom-container", "",
KubernetesDNSAddress, "http://10-1-15-16.namespace1.pod:80")
}
func Test_resolvePodUriByCustomContainerIPAddressMode(t *testing.T) {
- pod := mockPodWithContainers(namespace1, pod1,
- *mockContainerWithPorts(container1,
mockContainerPort(httpsProtocolName, tcp, defaultHttps)),
- *mockContainerWithPorts("custom-container",
mockContainerPort(httpProtocolName, tcp, defaultHttp)))
+ pod := mockPodWithContainers(namespace1, pod1Name,
+ *mockContainerWithPorts(container1Name,
mockContainerPort(httpsProtocol, tcp, defaultHttpsPort)),
+ *mockContainerWithPorts("custom-container",
mockContainerPort(httpProtocol, tcp, defaultHttpPort)))
pod.Status.PodIP = "10.1.15.17"
doTestResolvePodUri(t, pod, "custom-container", "",
KubernetesIPAddress, "http://10.1.15.17:80")
}
func Test_resolvePodUriByCustomContainerAndCustomPortDNSMode(t *testing.T) {
- pod := mockPodWithContainers(namespace1, pod1,
- *mockContainerWithPorts(container1,
mockContainerPort(httpsProtocolName, tcp, defaultHttps)),
+ pod := mockPodWithContainers(namespace1, pod1Name,
+ *mockContainerWithPorts(container1Name,
mockContainerPort(httpsProtocol, tcp, defaultHttpsPort)),
*mockContainerWithPorts("custom-container",
mockContainerPort("not-wanted", tcp, 8008),
mockContainerPort("custom-port", tcp, 8181)))
@@ -105,8 +110,8 @@ func
Test_resolvePodUriByCustomContainerAndCustomPortDNSMode(t *testing.T) {
}
func Test_resolvePodUriByCustomContainerAndCustomPortIPAddressMode(t
*testing.T) {
- pod := mockPodWithContainers(namespace1, pod1,
- *mockContainerWithPorts(container1,
mockContainerPort(httpsProtocolName, tcp, defaultHttps)),
+ pod := mockPodWithContainers(namespace1, pod1Name,
+ *mockContainerWithPorts(container1Name,
mockContainerPort(httpsProtocol, tcp, defaultHttpsPort)),
*mockContainerWithPorts("custom-container",
mockContainerPort("not-wanted", tcp, 8008),
mockContainerPort("custom-port", tcp, 8181)))
@@ -125,9 +130,9 @@ func Test_buildURI(t *testing.T) {
}
func Test_buildKubernetesServiceDNSUri(t *testing.T) {
- assert.Equal(t, "http://service1.namespace1.svc:8383",
buildKubernetesServiceDNSUri("http", namespace1, service1, 8383))
+ assert.Equal(t, "http://service1Name.namespace1.svc:8383",
buildKubernetesServiceDNSUri("http", namespace1, service1Name, 8383))
}
func Test_buildKubernetesPodDNSUri(t *testing.T) {
- assert.Equal(t, "http://pod1.namespace1.pod:8484",
buildKubernetesPodDNSUri("http", namespace1, pod1, 8484))
+ assert.Equal(t, "http://pod1Name.namespace1.pod:8484",
buildKubernetesPodDNSUri("http", namespace1, pod1Name, 8484))
}
diff --git a/controllers/profiles/common/app_properties.go
b/controllers/profiles/common/app_properties.go
index f97e9934..9564d160 100644
--- a/controllers/profiles/common/app_properties.go
+++ b/controllers/profiles/common/app_properties.go
@@ -1,16 +1,21 @@
-// Copyright 2023 Red Hat, Inc. and/or its affiliates
-//
-// 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.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 common
diff --git a/controllers/profiles/common/app_properties_test.go
b/controllers/profiles/common/app_properties_test.go
index f3d46f07..8fae160d 100644
--- a/controllers/profiles/common/app_properties_test.go
+++ b/controllers/profiles/common/app_properties_test.go
@@ -1,16 +1,21 @@
-// Copyright 2023 Red Hat, Inc. and/or its affiliates
-//
-// 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.
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 common
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]