squakez commented on code in PR #6818:
URL: https://github.com/apache/camel-k/pull/6818#discussion_r4026774270


##########
pkg/trait/ingress.go:
##########
@@ -124,11 +132,38 @@ func (t *ingressTrait) Apply(e *Environment) error {
                ingress.Spec.IngressClassName = &t.IngressClassName
        }
 
-       if len(t.TLSHosts) > 0 && t.TLSSecretName != "" {
+       tlsHosts := t.TLSHosts
+       secretName := t.TLSSecretName
+
+       // The cert-manager path only activates when the user has not manually 
provided a
+       // secret name. It may fall back to t.Host so that auto-discovery also 
works for the
+       // common single-host case, without changing the pre-existing manual 
TLS behavior
+       // (which requires TLSHosts to be set explicitly and never considers 
t.Host).
+       if secretName == "" {

Review Comment:
   I think we miss a check on the `auto` parameter. If disabled on purpose, we 
should not proceed. Ideally the check has to be done in the `Configure`. And in 
fact, you can use in general the `Configure` func to compute any configuration 
that you will later use in `Apply` by storing private scoped variables.



##########
pkg/trait/ingress.go:
##########
@@ -147,6 +182,85 @@ func (t *ingressTrait) Apply(e *Environment) error {
        return nil
 }
 
+// resolveCertManagerIssuer determines which cert-manager Issuer or 
ClusterIssuer
+// annotation to apply to the Ingress, if any. It returns an empty issuerName 
when
+// no annotation should be applied (cert-manager auto-discovery is disabled, 
cert-manager
+// is not installed, or no issuer is found). A forced TLSIssuerName is 
verified to exist
+// and returns an error if it does not; auto-discovery degrades to a no-op 
instead.
+func (t *ingressTrait) resolveCertManagerIssuer(e *Environment) 
(annotationKey, issuerName string, err error) {
+       namespace := e.Integration.Namespace
+
+       if t.TLSIssuerName != "" {
+               installed, err := certmanager.IsInstalled(e.Client)

Review Comment:
   Trait should have also a client attached, `t.Client` iirc.



##########
pkg/trait/ingress.go:
##########
@@ -147,6 +182,85 @@ func (t *ingressTrait) Apply(e *Environment) error {
        return nil
 }
 
+// resolveCertManagerIssuer determines which cert-manager Issuer or 
ClusterIssuer
+// annotation to apply to the Ingress, if any. It returns an empty issuerName 
when
+// no annotation should be applied (cert-manager auto-discovery is disabled, 
cert-manager
+// is not installed, or no issuer is found). A forced TLSIssuerName is 
verified to exist
+// and returns an error if it does not; auto-discovery degrades to a no-op 
instead.
+func (t *ingressTrait) resolveCertManagerIssuer(e *Environment) 
(annotationKey, issuerName string, err error) {
+       namespace := e.Integration.Namespace
+
+       if t.TLSIssuerName != "" {
+               installed, err := certmanager.IsInstalled(e.Client)
+               if err != nil {
+                       return "", "", err
+               }
+               if !installed {
+                       return "", "", fmt.Errorf("cert-manager is not 
installed but tlsIssuerName %q was set", t.TLSIssuerName)
+               }
+
+               kind := t.TLSIssuerKind
+               if kind == "" {
+                       kind = "ClusterIssuer"
+               }
+
+               switch kind {
+               case "Issuer":
+                       exists, err := certmanager.GetIssuer(e.Ctx, e.Client, 
namespace, t.TLSIssuerName)
+                       if err != nil {
+                               return "", "", err
+                       }
+                       if !exists {
+                               return "", "", fmt.Errorf("issuer %q not found 
in namespace %q", t.TLSIssuerName, namespace)
+                       }
+
+                       return certmanager.AnnotationIssuer, t.TLSIssuerName, 
nil
+               case "ClusterIssuer":
+                       exists, err := certmanager.GetClusterIssuer(e.Ctx, 
e.Client, t.TLSIssuerName)
+                       if err != nil {
+                               return "", "", err
+                       }
+                       if !exists {
+                               return "", "", fmt.Errorf("clusterissuer %q not 
found", t.TLSIssuerName)
+                       }
+
+                       return certmanager.AnnotationClusterIssuer, 
t.TLSIssuerName, nil
+               default:
+                       return "", "", fmt.Errorf("invalid tlsIssuerKind %q: 
must be %q or %q", kind, "Issuer", "ClusterIssuer")
+               }
+       }
+
+       if !ptr.Deref(t.TLSCertManagerAuto, false) {

Review Comment:
   I see this is here, but IMO it does not make sense to do all the logic 
(involving API calls) if this is not set. Better do a guard if at the beginning 
or in the `Configure` func.



##########
pkg/util/certmanager/enabled.go:
##########
@@ -0,0 +1,163 @@
+/*
+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 certmanager
+
+import (
+       "context"
+       "sort"
+
+       k8serrors "k8s.io/apimachinery/pkg/api/errors"
+       "k8s.io/apimachinery/pkg/api/meta"
+       "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+       "k8s.io/apimachinery/pkg/runtime/schema"
+       "k8s.io/client-go/kubernetes"
+       ctrl "sigs.k8s.io/controller-runtime/pkg/client"
+
+       kubernetesutil "github.com/apache/camel-k/v2/pkg/util/kubernetes"
+)
+
+const (
+       // CertManagerAPIGroup is the API group for cert-manager.
+       CertManagerAPIGroup = "cert-manager.io"
+       // CertManagerAPIVersion is the current API version for cert-manager.
+       CertManagerAPIVersion = "v1"
+
+       // AnnotationClusterIssuer is the Ingress annotation to specify a 
ClusterIssuer.
+       AnnotationClusterIssuer = "cert-manager.io/cluster-issuer"
+       // AnnotationIssuer is the Ingress annotation to specify a namespaced 
Issuer.
+       AnnotationIssuer = "cert-manager.io/issuer"
+)
+
+var (
+       // ClusterIssuerGVK is the GroupVersionKind for cert-manager 
ClusterIssuer.
+       ClusterIssuerGVK = schema.GroupVersionKind{
+               Group:   CertManagerAPIGroup,
+               Version: CertManagerAPIVersion,
+               Kind:    "ClusterIssuer",
+       }
+
+       // IssuerGVK is the GroupVersionKind for cert-manager Issuer.
+       IssuerGVK = schema.GroupVersionKind{
+               Group:   CertManagerAPIGroup,
+               Version: CertManagerAPIVersion,
+               Kind:    "Issuer",
+       }
+)
+
+func isResourceNotFoundError(err error) bool {
+       if err == nil {
+               return false
+       }
+
+       return k8serrors.IsNotFound(err) || meta.IsNoMatchError(err) || 
kubernetesutil.IsUnknownAPIError(err)
+}
+
+// IsInstalled returns true if connected to a cluster with cert-manager 
installed.
+func IsInstalled(c kubernetes.Interface) (bool, error) {

Review Comment:
   I think we need to change a little bit this one, also for performance 
reason. Ideally we can copy the same logic we have done in the twin Camel 
Monitor Operator for resources such as Grafana or Prometheus: 
https://github.com/camel-tooling/camel-monitor-operator/blob/03c71a6e3540433915e132980c864b55c7c18bb5/pkg/util/kubernetes/discovery.go#L26
   
   And for performance reasons we should do this check only once and store in 
the reconciler and later cascading to the controller and the rest of the logic, 
or, at least in the env_platform.go as a variable that is turned on/off only at 
bootstrap time. See 
https://github.com/camel-tooling/camel-monitor-operator/blob/03c71a6e3540433915e132980c864b55c7c18bb5/pkg/controller/camel_monitor/camel_monitor_controller.go#L82-L83



##########
pkg/util/certmanager/enabled.go:
##########
@@ -0,0 +1,163 @@
+/*
+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 certmanager
+
+import (
+       "context"
+       "sort"
+
+       k8serrors "k8s.io/apimachinery/pkg/api/errors"
+       "k8s.io/apimachinery/pkg/api/meta"
+       "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+       "k8s.io/apimachinery/pkg/runtime/schema"
+       "k8s.io/client-go/kubernetes"
+       ctrl "sigs.k8s.io/controller-runtime/pkg/client"
+
+       kubernetesutil "github.com/apache/camel-k/v2/pkg/util/kubernetes"
+)
+
+const (
+       // CertManagerAPIGroup is the API group for cert-manager.
+       CertManagerAPIGroup = "cert-manager.io"
+       // CertManagerAPIVersion is the current API version for cert-manager.
+       CertManagerAPIVersion = "v1"
+
+       // AnnotationClusterIssuer is the Ingress annotation to specify a 
ClusterIssuer.
+       AnnotationClusterIssuer = "cert-manager.io/cluster-issuer"
+       // AnnotationIssuer is the Ingress annotation to specify a namespaced 
Issuer.
+       AnnotationIssuer = "cert-manager.io/issuer"
+)
+
+var (
+       // ClusterIssuerGVK is the GroupVersionKind for cert-manager 
ClusterIssuer.
+       ClusterIssuerGVK = schema.GroupVersionKind{
+               Group:   CertManagerAPIGroup,
+               Version: CertManagerAPIVersion,
+               Kind:    "ClusterIssuer",
+       }
+
+       // IssuerGVK is the GroupVersionKind for cert-manager Issuer.
+       IssuerGVK = schema.GroupVersionKind{
+               Group:   CertManagerAPIGroup,
+               Version: CertManagerAPIVersion,
+               Kind:    "Issuer",
+       }
+)
+
+func isResourceNotFoundError(err error) bool {
+       if err == nil {
+               return false
+       }
+
+       return k8serrors.IsNotFound(err) || meta.IsNoMatchError(err) || 
kubernetesutil.IsUnknownAPIError(err)
+}
+
+// IsInstalled returns true if connected to a cluster with cert-manager 
installed.
+func IsInstalled(c kubernetes.Interface) (bool, error) {
+       _, err := 
c.Discovery().ServerResourcesForGroupVersion(schema.GroupVersion{
+               Group:   CertManagerAPIGroup,
+               Version: CertManagerAPIVersion,
+       }.String())
+       if isResourceNotFoundError(err) {
+               return false, nil
+       } else if err != nil {
+               return false, err
+       }
+
+       return true, nil
+}
+
+// ListClusterIssuers returns all ClusterIssuer names available in the cluster.
+func ListClusterIssuers(ctx context.Context, c ctrl.Reader) ([]string, error) {
+       list := &unstructured.UnstructuredList{}

Review Comment:
   We should not deal with so many unstructured type. The risk is to have too 
many runtime errors. Consider to use any existing API, or duck type an API like 
we're doing, for example, for Keda type: 
https://github.com/apache/camel-k/blob/main/pkg/apis/duck/keda/v1alpha1/duck_types.go



##########
pkg/trait/ingress_test.go:
##########
@@ -335,6 +339,144 @@ func 
TestConfigureTLSWithoutSecretNameIngressTraitWDoesSucceed(t *testing.T) {
        assert.Equal(t, "service-name(hostname) -> service-name(http)", 
conditions[0].Message)
 }
 
+func TestApplyIngressTraitCertManagerAutoNotInstalledDoesNoop(t *testing.T) {
+       ingressTrait, environment := createNominalIngressTest()
+       ingressTrait.TLSCertManagerAuto = ptr.To(true)
+       environment.Ctx = context.Background()
+       fakeClient, err := internal.NewFakeClient()
+       require.NoError(t, err)
+       environment.Client = fakeClient
+
+       err = ingressTrait.Apply(environment)
+
+       require.NoError(t, err)
+       environment.Resources.Visit(func(resource runtime.Object) {
+               if ingress, ok := resource.(*networkingv1.Ingress); ok {
+                       assert.Nil(t, ingress.Spec.TLS)
+                       assert.NotContains(t, ingress.Annotations, 
certmanager.AnnotationClusterIssuer)
+                       assert.NotContains(t, ingress.Annotations, 
certmanager.AnnotationIssuer)
+               }
+       })
+}
+
+func TestApplyIngressTraitCertManagerAutoNoIssuerDoesNoop(t *testing.T) {
+       ingressTrait, environment := createNominalIngressTest()
+       ingressTrait.TLSCertManagerAuto = ptr.To(true)
+       environment.Ctx = context.Background()
+       fakeClient, err := internal.NewFakeClient()
+       require.NoError(t, err)
+       fakeClient.(*internal.FakeClient).EnableCertManagerDiscovery()
+       environment.Client = fakeClient
+
+       err = ingressTrait.Apply(environment)
+
+       require.NoError(t, err)
+       environment.Resources.Visit(func(resource runtime.Object) {
+               if ingress, ok := resource.(*networkingv1.Ingress); ok {
+                       assert.Nil(t, ingress.Spec.TLS)
+                       assert.NotContains(t, ingress.Annotations, 
certmanager.AnnotationClusterIssuer)
+               }
+       })
+}
+
+func TestApplyIngressTraitCertManagerAutoClusterIssuerFoundDoesSucceed(t 
*testing.T) {
+       ingressTrait, environment := createNominalIngressTest()
+       ingressTrait.TLSCertManagerAuto = ptr.To(true)
+       environment.Ctx = context.Background()
+
+       clusterIssuer := newClusterIssuer("letsencrypt-prod")
+       fakeClient, err := internal.NewFakeClient(clusterIssuer)
+       require.NoError(t, err)
+       fakeClient.(*internal.FakeClient).EnableCertManagerDiscovery()
+       environment.Client = fakeClient
+
+       err = ingressTrait.Apply(environment)
+
+       require.NoError(t, err)
+       environment.Resources.Visit(func(resource runtime.Object) {
+               if ingress, ok := resource.(*networkingv1.Ingress); ok {
+                       assert.Equal(t, "letsencrypt-prod", 
ingress.Annotations[certmanager.AnnotationClusterIssuer])
+                       require.NotNil(t, ingress.Spec.TLS)
+                       assert.Equal(t, []string{"hostname"}, 
ingress.Spec.TLS[0].Hosts)
+                       assert.Equal(t, "service-name-tls", 
ingress.Spec.TLS[0].SecretName)
+               }
+       })
+}
+
+func TestApplyIngressTraitForcedIssuerExistsDoesSucceed(t *testing.T) {
+       ingressTrait, environment := createNominalIngressTest()
+       ingressTrait.TLSIssuerName = "my-issuer"
+       ingressTrait.TLSIssuerKind = "Issuer"
+       environment.Ctx = context.Background()
+       environment.Integration.Namespace = "namespace"
+
+       issuer := newIssuer("my-issuer", "namespace")
+       fakeClient, err := internal.NewFakeClient(issuer)
+       require.NoError(t, err)
+       fakeClient.(*internal.FakeClient).EnableCertManagerDiscovery()

Review Comment:
   Is it needed? cannot we instead have it enabled by default?



##########
pkg/trait/ingress.go:
##########
@@ -147,6 +182,85 @@ func (t *ingressTrait) Apply(e *Environment) error {
        return nil
 }
 
+// resolveCertManagerIssuer determines which cert-manager Issuer or 
ClusterIssuer
+// annotation to apply to the Ingress, if any. It returns an empty issuerName 
when
+// no annotation should be applied (cert-manager auto-discovery is disabled, 
cert-manager
+// is not installed, or no issuer is found). A forced TLSIssuerName is 
verified to exist
+// and returns an error if it does not; auto-discovery degrades to a no-op 
instead.
+func (t *ingressTrait) resolveCertManagerIssuer(e *Environment) 
(annotationKey, issuerName string, err error) {
+       namespace := e.Integration.Namespace
+
+       if t.TLSIssuerName != "" {
+               installed, err := certmanager.IsInstalled(e.Client)
+               if err != nil {
+                       return "", "", err
+               }
+               if !installed {
+                       return "", "", fmt.Errorf("cert-manager is not 
installed but tlsIssuerName %q was set", t.TLSIssuerName)
+               }
+
+               kind := t.TLSIssuerKind
+               if kind == "" {
+                       kind = "ClusterIssuer"
+               }
+
+               switch kind {
+               case "Issuer":
+                       exists, err := certmanager.GetIssuer(e.Ctx, e.Client, 
namespace, t.TLSIssuerName)
+                       if err != nil {
+                               return "", "", err
+                       }
+                       if !exists {
+                               return "", "", fmt.Errorf("issuer %q not found 
in namespace %q", t.TLSIssuerName, namespace)
+                       }
+
+                       return certmanager.AnnotationIssuer, t.TLSIssuerName, 
nil
+               case "ClusterIssuer":
+                       exists, err := certmanager.GetClusterIssuer(e.Ctx, 
e.Client, t.TLSIssuerName)
+                       if err != nil {
+                               return "", "", err
+                       }
+                       if !exists {
+                               return "", "", fmt.Errorf("clusterissuer %q not 
found", t.TLSIssuerName)
+                       }
+
+                       return certmanager.AnnotationClusterIssuer, 
t.TLSIssuerName, nil
+               default:
+                       return "", "", fmt.Errorf("invalid tlsIssuerKind %q: 
must be %q or %q", kind, "Issuer", "ClusterIssuer")
+               }
+       }
+
+       if !ptr.Deref(t.TLSCertManagerAuto, false) {
+               return "", "", nil
+       }
+
+       installed, err := certmanager.IsInstalled(e.Client)

Review Comment:
   I think we have already this value from a previous call.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to