diff --git a/build/Dockerfile b/build/Dockerfile index 78ff6a3cd5..f1523076cf 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -43,20 +43,17 @@ ENV MAVEN_USER_HOME="${MAVEN_HOME}" RUN ${MVNW_DIR}/mvnw --version | grep "Maven home:" | sed 's/Maven home: //' >> ${MVNW_DIR}default \ && cp -r /usr/share/maven/lib/. $(cat ${MVNW_DIR}default)/lib \ && rm $(cat ${MVNW_DIR}default)/lib/maven-slf4j-provider* \ - && rm $(cat ${MVNW_DIR}default)/lib/slf4j-api-1.* + && rm $(cat ${MVNW_DIR}default)/lib/slf4j-api-1.* ENV MAVEN_OPTS="${MAVEN_OPTS} -Dlogback.configurationFile=${MAVEN_HOME}/conf/logback.xml" ADD build/_maven_output ${MVN_REPO} # Fix https://github.com/moby/moby/issues/37965 RUN true -ADD build/_kamelets /kamelets RUN chgrp -R 0 ${MVN_REPO} \ && chown -R 1001:0 ${MVN_REPO} \ && chmod -R 775 ${MVN_REPO} \ - && chgrp -R 0 /kamelets \ - && chmod -R g=u /kamelets \ && chgrp -R 0 ${MAVEN_HOME} \ && chown -R 1001:0 ${MAVEN_HOME} \ && chmod -R 775 ${MAVEN_HOME} diff --git a/pkg/apis/camel/v1/integrationplatform_types.go b/pkg/apis/camel/v1/integrationplatform_types.go index 3cc8e3a654..6c211996b8 100644 --- a/pkg/apis/camel/v1/integrationplatform_types.go +++ b/pkg/apis/camel/v1/integrationplatform_types.go @@ -192,6 +192,7 @@ const ( // IntegrationPlatformPhaseError when the IntegrationPlatform had some error (see Conditions). IntegrationPlatformPhaseError IntegrationPlatformPhase = "Error" // IntegrationPlatformPhaseCreateCatalog when the IntegrationPlatform creates a new CamelCatalog. + // Deprecated no longer in use. IntegrationPlatformPhaseCreateCatalog IntegrationPlatformPhase = "CreateCatalog" // IntegrationPlatformConditionReady is the condition if the IntegrationPlatform is ready. @@ -199,12 +200,12 @@ const ( IntegrationPlatformConditionReady = "Ready" // IntegrationPlatformConditionTypeCreated is the condition if the IntegrationPlatform has been created. IntegrationPlatformConditionTypeCreated IntegrationPlatformConditionType = "Created" - // IntegrationPlatformConditionTypeRegistryAvailable is the condition for the availability of a container registry. IntegrationPlatformConditionTypeRegistryAvailable IntegrationPlatformConditionType = "RegistryAvailable" - - // IntegrationPlatformConditionCamelCatalogAvailable is the condition for the availability of a container registry. + // IntegrationPlatformConditionCamelCatalogAvailable is the condition for the availability of a the CamelCatalog. IntegrationPlatformConditionCamelCatalogAvailable IntegrationPlatformConditionType = "CamelCatalogAvailable" + // IntegrationPlatformConditionKameletCatalogAvailable is the condition for the availability of a Kamelet catalog. + IntegrationPlatformConditionKameletCatalogAvailable IntegrationPlatformConditionType = "KameletCatalogAvailable" // IntegrationPlatformConditionCreatedReason represents the reason that the IntegrationPlatform is created. IntegrationPlatformConditionCreatedReason = "IntegrationPlatformCreated" diff --git a/pkg/apis/camel/v1/kamelet_types_support.go b/pkg/apis/camel/v1/kamelet_types_support.go index bb22c998e8..aa19fc2b9c 100644 --- a/pkg/apis/camel/v1/kamelet_types_support.go +++ b/pkg/apis/camel/v1/kamelet_types_support.go @@ -178,6 +178,11 @@ func (k *Kamelet) SortedTypesKeys() []TypeSlot { return res } +// IsBundled returns true if the Kamelet is coming from a pre-bundled installation. +func (k *Kamelet) IsBundled() bool { + return k.Labels != nil && k.Labels[KameletBundledLabel] == "true" +} + func ValidKameletName(name string) bool { return !reservedKameletNames[name] } diff --git a/pkg/cmd/reset.go b/pkg/cmd/reset.go index 02a6f414e7..c32112e5d4 100644 --- a/pkg/cmd/reset.go +++ b/pkg/cmd/reset.go @@ -18,7 +18,6 @@ limitations under the License. package cmd import ( - "errors" "fmt" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" @@ -93,13 +92,6 @@ func (o *resetCmdOptions) reset(cmd *cobra.Command, _ []string) { } fmt.Fprintln(cmd.OutOrStdout(), n, "integration kits deleted from namespace", o.Namespace) } - - if err = o.resetIntegrationPlatform(c); err != nil { - fmt.Fprintln(cmd.ErrOrStderr(), err) - return - } - - fmt.Fprintln(cmd.OutOrStdout(), "Camel K platform has been reset successfully!") } func (o *resetCmdOptions) deleteAllIntegrations(c client.Client) (int, error) { @@ -163,22 +155,6 @@ func (o *resetCmdOptions) deleteAllKameletBindings(c client.Client) (int, error) return len(list.Items), nil } -func (o *resetCmdOptions) resetIntegrationPlatform(c client.Client) error { - list := v1.NewIntegrationPlatformList() - if err := c.List(o.Context, &list, k8sclient.InNamespace(o.Namespace)); err != nil { - return fmt.Errorf("could not retrieve integration platform from namespace %s: %w", o.Namespace, err) - } - if len(list.Items) > 1 { - return fmt.Errorf("expected 1 integration platform in the namespace, found: %d", len(list.Items)) - } else if len(list.Items) == 0 { - return errors.New("no integration platforms found in the namespace") - } - platform := list.Items[0] - // Let's reset the status - platform.Status = v1.IntegrationPlatformStatus{} - return c.Status().Update(o.Context, &platform) -} - func isIntegrationOwned(it v1.Integration) bool { for _, ref := range it.OwnerReferences { gv, err := schema.ParseGroupVersion(ref.APIVersion) diff --git a/pkg/controller/catalog/initialize.go b/pkg/controller/catalog/initialize.go index 3aac6ab657..d5f3c7a4ae 100644 --- a/pkg/controller/catalog/initialize.go +++ b/pkg/controller/catalog/initialize.go @@ -45,7 +45,6 @@ func (action *initializeAction) CanHandle(catalog *v1.CamelCatalog) bool { func (action *initializeAction) Handle(ctx context.Context, catalog *v1.CamelCatalog) (*v1.CamelCatalog, error) { action.L.Info("Initializing CamelCatalog") - platform, err := platformutil.GetForName(ctx, action.client, catalog.Namespace, defaults.OperatorID()) if err != nil { diff --git a/pkg/controller/integrationplatform/catalog.go b/pkg/controller/integrationplatform/catalog.go deleted file mode 100644 index ea1406f1fb..0000000000 --- a/pkg/controller/integrationplatform/catalog.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -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 integrationplatform - -import ( - "context" - "fmt" - - v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" - "github.com/apache/camel-k/v2/pkg/util/camel" - - corev1 "k8s.io/api/core/v1" -) - -// NewCreateCatalogAction returns an action to create a new CamelCatalog. -func NewCreateCatalogAction() Action { - return &catalogAction{} -} - -type catalogAction struct { - baseAction -} - -func (action *catalogAction) Name() string { - return "catalog" -} - -func (action *catalogAction) CanHandle(platform *v1.IntegrationPlatform) bool { - return platform.Status.Phase == v1.IntegrationPlatformPhaseCreateCatalog -} - -func (action *catalogAction) Handle(ctx context.Context, platform *v1.IntegrationPlatform) (*v1.IntegrationPlatform, error) { - // New runtime version set - check that catalog exists and create it if it does not exist - runtimeSpec := v1.RuntimeSpec{ - Version: platform.Status.Build.RuntimeVersion, - Provider: v1.RuntimeProviderQuarkus, - } - - catalog, err := camel.LoadCatalog(ctx, action.client, platform.Namespace, runtimeSpec) - if err != nil { - action.L.Error(err, "IntegrationPlatform unable to load Camel catalog", - "runtime-version", runtimeSpec.Version, "runtime-provider", runtimeSpec.Provider) - return platform, nil - } else if catalog == nil { - if catalog, err = camel.CreateCatalog(ctx, action.client, platform.Namespace, platform, runtimeSpec); err != nil { - action.L.Error(err, "IntegrationPlatform unable to create Camel catalog", - "runtime-version", runtimeSpec.Version, "runtime-provider", runtimeSpec.Provider) - - platform.Status.Phase = v1.IntegrationPlatformPhaseError - platform.Status.SetCondition( - v1.IntegrationPlatformConditionCamelCatalogAvailable, - corev1.ConditionFalse, - v1.IntegrationPlatformConditionCamelCatalogAvailableReason, - fmt.Sprintf("camel catalog %s not available, please review given runtime version. Error: %s", runtimeSpec.Version, err)) - - return platform, nil - } - } - - platform.Status.Phase = v1.IntegrationPlatformPhaseReady - platform.Status.SetCondition( - v1.IntegrationPlatformConditionCamelCatalogAvailable, - corev1.ConditionTrue, - v1.IntegrationPlatformConditionCamelCatalogAvailableReason, - fmt.Sprintf("camel catalog %s available", runtimeSpec.Version)) - platform.Status.Build.RuntimeCoreVersion = catalog.Runtime.Metadata["camel.version"] - - return platform, nil -} diff --git a/pkg/controller/integrationplatform/catalog_test.go b/pkg/controller/integrationplatform/catalog_test.go deleted file mode 100644 index f2c5265089..0000000000 --- a/pkg/controller/integrationplatform/catalog_test.go +++ /dev/null @@ -1,235 +0,0 @@ -/* -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 integrationplatform - -import ( - "context" - "errors" - "fmt" - "os" - "strings" - "testing" - - "github.com/apache/camel-k/v2/pkg/util/boolean" - - v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" - "github.com/apache/camel-k/v2/pkg/platform" - "github.com/apache/camel-k/v2/pkg/resources" - "github.com/apache/camel-k/v2/pkg/util/defaults" - "github.com/apache/camel-k/v2/pkg/util/log" - "github.com/apache/camel-k/v2/pkg/util/maven" - "github.com/apache/camel-k/v2/pkg/util/test" - "github.com/rs/xid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" - k8stesting "k8s.io/client-go/testing" - k8sclient "sigs.k8s.io/controller-runtime/pkg/client" -) - -func TestCanHandlePhaseCreateCatalog(t *testing.T) { - ip := v1.IntegrationPlatform{} - ip.Namespace = "ns" - ip.Name = xid.New().String() - ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift - ip.Spec.Profile = v1.TraitProfileOpenShift - ip.Spec.Build.Registry.Address = defaults.OpenShiftRegistryAddress - - ip.Spec.Build.RuntimeVersion = defaults.DefaultRuntimeVersion - - ip.Status.Phase = v1.IntegrationPlatformPhaseCreateCatalog - - c, err := test.NewFakeClient(&ip) - require.NoError(t, err) - - action := NewCreateCatalogAction() - action.InjectLogger(log.Log) - action.InjectClient(c) - - answer := action.CanHandle(&ip) - assert.True(t, answer) - - ip.Status.Phase = v1.IntegrationPlatformPhaseError - answer = action.CanHandle(&ip) - assert.False(t, answer) - - ip.Status.Phase = v1.IntegrationPlatformPhaseReady - answer = action.CanHandle(&ip) - assert.False(t, answer) -} - -func TestCreateCatalog(t *testing.T) { - ip := v1.IntegrationPlatform{} - ip.Namespace = "ns" - ip.Name = xid.New().String() - ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift - ip.Spec.Profile = v1.TraitProfileOpenShift - ip.Spec.Build.Registry.Address = defaults.OpenShiftRegistryAddress - - ip.Status.Phase = v1.IntegrationPlatformPhaseCreateCatalog - ip.Spec.Build.RuntimeVersion = defaults.DefaultRuntimeVersion - if strings.Contains(ip.Spec.Build.RuntimeVersion, "SNAPSHOT") { - maven.DefaultMavenRepositories += ",https://repository.apache.org/content/repositories/snapshots-group@snapshots@id=apache-snapshots" - } - - c, err := test.NewFakeClient(&ip) - require.NoError(t, err) - - // use local Maven executable in tests - t.Setenv("MAVEN_WRAPPER", boolean.FalseString) - _, ok := os.LookupEnv("MAVEN_CMD") - if !ok { - t.Setenv("MAVEN_CMD", "mvn") - } - - fakeClient := c.(*test.FakeClient) //nolint - fakeClient.AddReactor("create", "*", func(action k8stesting.Action) (bool, runtime.Object, error) { - createAction := action.(k8stesting.CreateAction) //nolint - - assert.Equal(t, "ns", createAction.GetNamespace()) - - return true, createAction.GetObject(), nil - }) - - err = platform.ConfigureDefaults(context.TODO(), c, &ip, false) - require.NoError(t, err) - - action := NewCreateCatalogAction() - action.InjectLogger(log.Log) - action.InjectClient(c) - - answer, err := action.Handle(context.TODO(), &ip) - require.NoError(t, err) - assert.NotNil(t, answer) - - assert.Equal(t, v1.IntegrationPlatformPhaseReady, answer.Status.Phase, "Error", answer.Status.Conditions[0].Message) - assert.Equal(t, corev1.ConditionTrue, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status) - // We don't know exactly which is the core version, it is enough to check is not empty in the test - assert.NotEqual(t, "", answer.Status.Build.RuntimeCoreVersion) - - list := v1.NewCamelCatalogList() - err = c.List(context.TODO(), &list, k8sclient.InNamespace(ip.Namespace)) - - require.NoError(t, err) - assert.NotEmpty(t, list.Items) - - items, err := resources.WithPrefix("/camel-catelog-") - require.NoError(t, err) - - for _, k := range items { - found := false - - for _, c := range list.Items { - n := strings.TrimSuffix(k, ".yaml") - n = strings.ToLower(n) - - if c.Name == n { - found = true - } - } - - assert.True(t, found) - } -} - -func TestCatalogAlreadyPresent(t *testing.T) { - ip := v1.IntegrationPlatform{} - ip.Namespace = "ns" - ip.Name = xid.New().String() - ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift - ip.Spec.Profile = v1.TraitProfileOpenShift - ip.Spec.Build.Registry.Address = defaults.OpenShiftRegistryAddress - - ip.Status.Phase = v1.IntegrationPlatformPhaseCreateCatalog - - catalog := v1.NewCamelCatalog("ns", fmt.Sprintf("camel-catalog-%s", defaults.DefaultRuntimeVersion)) - catalog.Spec.Runtime.Version = defaults.DefaultRuntimeVersion - catalog.Spec.Runtime.Provider = v1.RuntimeProviderQuarkus - catalog.Spec.Runtime.Metadata = map[string]string{ - "camel.version": "4.4.0", - } - - c, err := test.NewFakeClient(&ip, &catalog) - require.NoError(t, err) - - err = platform.ConfigureDefaults(context.TODO(), c, &ip, false) - require.NoError(t, err) - - action := NewMonitorAction() - action.InjectLogger(log.Log) - action.InjectClient(c) - - answer, err := action.Handle(context.TODO(), &ip) - require.NoError(t, err) - assert.NotNil(t, answer) - - assert.Equal(t, v1.IntegrationPlatformPhaseReady, answer.Status.Phase) - assert.Equal(t, "4.4.0", answer.Status.Build.RuntimeCoreVersion) - assert.Equal(t, corev1.ConditionTrue, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status) -} - -func TestCreateCatalogError(t *testing.T) { - ip := v1.IntegrationPlatform{} - ip.Namespace = "ns" - ip.Name = xid.New().String() - ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift - ip.Spec.Profile = v1.TraitProfileOpenShift - ip.Spec.Build.Registry.Address = defaults.OpenShiftRegistryAddress - - ip.Status.Phase = v1.IntegrationPlatformPhaseCreateCatalog - - // force catalog build to fail - ip.Spec.Build.RuntimeVersion = "0.0.0" - - c, err := test.NewFakeClient(&ip) - require.NoError(t, err) - - // use local Maven executable in tests - t.Setenv("MAVEN_WRAPPER", boolean.FalseString) - _, ok := os.LookupEnv("MAVEN_CMD") - if !ok { - t.Setenv("MAVEN_CMD", "mvn") - } - - fakeClient := c.(*test.FakeClient) //nolint - fakeClient.AddReactor("create", "*", func(action k8stesting.Action) (bool, runtime.Object, error) { - createAction := action.(k8stesting.CreateAction) //nolint - - assert.Equal(t, "ns", createAction.GetNamespace()) - - return true, nil, errors.New("failed to create catalog for some reason") - }) - - err = platform.ConfigureDefaults(context.TODO(), c, &ip, false) - require.NoError(t, err) - - action := NewCreateCatalogAction() - action.InjectLogger(log.Log) - action.InjectClient(c) - - answer, err := action.Handle(context.TODO(), &ip) - require.NoError(t, err) - assert.NotNil(t, answer) - - assert.Equal(t, v1.IntegrationPlatformPhaseError, answer.Status.Phase) - assert.Equal(t, corev1.ConditionFalse, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status) - assert.Equal(t, v1.IntegrationPlatformConditionCamelCatalogAvailableReason, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Reason) - assert.Contains(t, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Message, "camel catalog 0.0.0 not available, please review given runtime version. Error:") -} diff --git a/pkg/controller/integrationplatform/create.go b/pkg/controller/integrationplatform/create.go index 8471ca5e52..85edbf94ad 100644 --- a/pkg/controller/integrationplatform/create.go +++ b/pkg/controller/integrationplatform/create.go @@ -19,14 +19,17 @@ package integrationplatform import ( "context" + "fmt" + "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" - ctrl "sigs.k8s.io/controller-runtime/pkg/client" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" - "github.com/apache/camel-k/v2/pkg/install" + "github.com/apache/camel-k/v2/pkg/client" "github.com/apache/camel-k/v2/pkg/resources" + "github.com/apache/camel-k/v2/pkg/util/camel" "github.com/apache/camel-k/v2/pkg/util/defaults" + k8sclient "sigs.k8s.io/controller-runtime/pkg/client" ) // NewCreateAction returns the action that creates resources needed by the platform. @@ -47,38 +50,141 @@ func (action *createAction) CanHandle(platform *v1.IntegrationPlatform) bool { } func (action *createAction) Handle(ctx context.Context, platform *v1.IntegrationPlatform) (*v1.IntegrationPlatform, error) { - paths, err := resources.WithPrefix("/resources/camel-catalog-") + runtimeSpec := v1.RuntimeSpec{ + Version: platform.Status.Build.RuntimeVersion, + Provider: platform.Status.Build.RuntimeProvider, + } + catalog, err := loadCatalog(ctx, action.client, platform.Namespace, runtimeSpec) if err != nil { return nil, err } - for _, k := range paths { - action.L.Infof("Installing camel catalog: %s", k) - err := install.Resources(ctx, action.client, platform.Namespace, true, - func(object ctrl.Object) ctrl.Object { - action.L.Infof("Copying platform annotations to catalog: %s", object.GetName()) - object.SetAnnotations(platform.Annotations) - return object - }, - k) + // if bundled version, load catalog spec from resources + if platform.Status.Build.RuntimeVersion == defaults.DefaultRuntimeVersion { + if platform, err = action.handleBundledCatalog(ctx, platform, catalog); err != nil { + return platform, err + } + } else { + // otherwise get the catalog from external dependency + if platform, err = action.handleNewCatalog(ctx, platform, catalog, runtimeSpec); err != nil { + return platform, err + } + } + + platform.Status.Phase = v1.IntegrationPlatformPhaseReady + platform.Status.SetCondition( + v1.IntegrationPlatformConditionCamelCatalogAvailable, + corev1.ConditionTrue, + v1.IntegrationPlatformConditionCamelCatalogAvailableReason, + fmt.Sprintf("camel catalog %s available", platform.Status.Build.RuntimeVersion)) + + if platform.Status.Build.RuntimeCoreVersion != "" { + action.L.Infof("IntegrationPlatform is about to install Apache Kamelet Catalog version %s", platform.Status.Build.RuntimeCoreVersion) + return installKamelets(ctx, action.client, platform) + } else { + action.L.Info("IntegrationPlatform has no Camel core version. " + + "It is likely an unsupported specification, please, update to the latest one") + } + + return platform, nil +} + +func loadCatalog(ctx context.Context, c client.Client, namespace string, runtimeSpec v1.RuntimeSpec) (*v1.CamelCatalog, error) { + options := []k8sclient.ListOption{ + k8sclient.InNamespace(namespace), + } + list := v1.NewCamelCatalogList() + if err := c.List(ctx, &list, options...); err != nil { + return nil, err + } + for _, cc := range list.Items { + if cc.Spec.Runtime.Provider == runtimeSpec.Provider && cc.Spec.Runtime.Version == runtimeSpec.Version { + return &cc, nil + } + } + + return nil, nil +} + +func (action *createAction) handleBundledCatalog(ctx context.Context, platform *v1.IntegrationPlatform, catalog *v1.CamelCatalog) (*v1.IntegrationPlatform, error) { + var camelVersion string + // Create the catalog only if it was not yet created + if catalog == nil { + camelCatalogData, err := resources.Resource(fmt.Sprintf("/resources/camel-catalog-%s.yaml", platform.Status.Build.RuntimeVersion)) if err != nil { return nil, err } + var cat v1.CamelCatalog + if err = yaml.Unmarshal(camelCatalogData, &cat); err != nil { + return nil, err + } + // Copy platform annotations to the catalog + cat.SetAnnotations(platform.Annotations) + cat.SetNamespace(platform.Namespace) + action.L.Infof("Installing bundled camel catalog: %s", platform.Status.Build.RuntimeVersion) + if err = action.client.Create(ctx, &cat); err != nil { + return nil, err + } + camelVersion = cat.Spec.GetCamelVersion() + } else { + camelVersion = catalog.Spec.GetCamelVersion() } + platform.Status.Build.RuntimeCoreVersion = camelVersion - if defaults.InstallDefaultKamelets() { - // Kamelet Catalog installed on platform reconciliation for cases where users install a global operator - if err := install.KameletCatalog(ctx, action.client, platform.Namespace); err != nil { - return nil, err + return platform, nil +} + +func (action *createAction) handleNewCatalog(ctx context.Context, platform *v1.IntegrationPlatform, + catalog *v1.CamelCatalog, runtimeSpec v1.RuntimeSpec) (*v1.IntegrationPlatform, error) { + var camelVersion string + if catalog == nil { + cat, err := camel.CreateCatalog(ctx, action.client, platform.Namespace, platform, runtimeSpec) + if err != nil { + action.L.Error(err, "IntegrationPlatform unable to create Camel catalog", + "runtime-version", runtimeSpec.Version, "runtime-provider", runtimeSpec.Provider) + + platform.Status.Phase = v1.IntegrationPlatformPhaseError + platform.Status.SetCondition( + v1.IntegrationPlatformConditionCamelCatalogAvailable, + corev1.ConditionFalse, + v1.IntegrationPlatformConditionCamelCatalogAvailableReason, + fmt.Sprintf("camel catalog %s not available, please review given runtime version. Error: %s", runtimeSpec.Version, err)) + + return platform, err } + camelVersion = cat.GetCamelVersion() + } else { + camelVersion = catalog.Spec.GetCamelVersion() } + platform.Status.Build.RuntimeCoreVersion = camelVersion - platform.Status.SetCondition( - v1.IntegrationPlatformConditionTypeCreated, - corev1.ConditionTrue, - v1.IntegrationPlatformConditionCreatedReason, - "integration platform created") + return platform, nil +} + +func installKamelets(ctx context.Context, c client.Client, platform *v1.IntegrationPlatform) (*v1.IntegrationPlatform, error) { + // We bundle the Kamelets driven by the catalog + if defaults.InstallDefaultKamelets() { + camelVersion := platform.Status.Build.RuntimeCoreVersion + installedKam, erroredKam, err := installKameletCatalog(ctx, c, platform, camelVersion) + if err != nil { + platform.Status.Phase = v1.IntegrationPlatformPhaseError + platform.Status.SetCondition( + v1.IntegrationPlatformConditionKameletCatalogAvailable, + corev1.ConditionFalse, + "IntegrationPlatformKameletCatalogAvailable", + fmt.Sprintf("kamelet catalog %s not available, please review given camel version. Error: %s", camelVersion, err), + ) + + return platform, nil + } + platform.Status.SetCondition( + v1.IntegrationPlatformConditionKameletCatalogAvailable, + corev1.ConditionTrue, + "IntegrationPlatformKameletCatalogAvailable", + fmt.Sprintf("successfully installed Kamelet catalog version %s: success %d Kamelets, failed %d Kamelets", + camelVersion, installedKam, erroredKam), + ) + } - platform.Status.Phase = v1.IntegrationPlatformPhaseReady return platform, nil } diff --git a/pkg/controller/integrationplatform/create_test.go b/pkg/controller/integrationplatform/create_test.go index 55230f0243..c47532070a 100644 --- a/pkg/controller/integrationplatform/create_test.go +++ b/pkg/controller/integrationplatform/create_test.go @@ -19,67 +19,206 @@ package integrationplatform import ( "context" + "errors" + "fmt" + "os" "strings" "testing" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" "github.com/apache/camel-k/v2/pkg/platform" - "github.com/apache/camel-k/v2/pkg/resources" + "github.com/apache/camel-k/v2/pkg/util/boolean" + "github.com/apache/camel-k/v2/pkg/util/defaults" "github.com/apache/camel-k/v2/pkg/util/log" + "github.com/apache/camel-k/v2/pkg/util/maven" "github.com/apache/camel-k/v2/pkg/util/test" - "github.com/rs/xid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + k8stesting "k8s.io/client-go/testing" k8sclient "sigs.k8s.io/controller-runtime/pkg/client" ) func TestCreate(t *testing.T) { ip := v1.IntegrationPlatform{} ip.Namespace = "ns" - ip.Name = xid.New().String() - ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift - ip.Spec.Profile = v1.TraitProfileOpenShift + ip.Name = "ck" + ip.Status = v1.IntegrationPlatformStatus{ + IntegrationPlatformSpec: v1.IntegrationPlatformSpec{ + Build: v1.IntegrationPlatformBuildSpec{ + RuntimeProvider: v1.RuntimeProviderQuarkus, + RuntimeVersion: defaults.DefaultRuntimeVersion, + }, + }, + } + c, err := test.NewFakeClient(&ip) + require.NoError(t, err) + + h := NewCreateAction() + h.InjectLogger(log.Log) + h.InjectClient(c) + // We don't want to test the installation procedure here + os.Setenv("KAMEL_INSTALL_DEFAULT_KAMELETS", "false") + answer, err := h.Handle(context.TODO(), &ip) + + require.NoError(t, err) + assert.NotNil(t, answer) + assert.Equal(t, v1.IntegrationPlatformPhaseReady, answer.Status.Phase) + assert.Equal(t, defaults.DefaultRuntimeVersion, answer.Status.Build.RuntimeVersion) + assert.Equal(t, v1.RuntimeProviderQuarkus, answer.Status.Build.RuntimeProvider) + assert.NotEqual(t, "", answer.Status.Build.RuntimeCoreVersion) + assert.Equal(t, corev1.ConditionTrue, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status) + + list := v1.NewCamelCatalogList() + err = c.List(context.TODO(), &list, k8sclient.InNamespace(ip.Namespace)) + require.NoError(t, err) + assert.NotEmpty(t, list.Items) +} + +func TestCatalogAlreadyPresent(t *testing.T) { + ip := v1.IntegrationPlatform{} + ip.Namespace = "ns" + ip.Name = "ck" + ip.Status = v1.IntegrationPlatformStatus{ + IntegrationPlatformSpec: v1.IntegrationPlatformSpec{ + Build: v1.IntegrationPlatformBuildSpec{ + RuntimeProvider: v1.RuntimeProviderQuarkus, + RuntimeVersion: defaults.DefaultRuntimeVersion, + }, + }, + } + + catalog := v1.NewCamelCatalog("ns", fmt.Sprintf("camel-catalog-%s", defaults.DefaultRuntimeVersion)) + catalog.Spec.Runtime.Version = defaults.DefaultRuntimeVersion + catalog.Spec.Runtime.Provider = v1.RuntimeProviderQuarkus + catalog.Spec.Runtime.Metadata = map[string]string{ + "camel.version": "4.4.0", + } + + c, err := test.NewFakeClient(&ip, &catalog) + require.NoError(t, err) + + action := NewCreateAction() + action.InjectLogger(log.Log) + action.InjectClient(c) + + // We don't want to test the installation procedure here + os.Setenv("KAMEL_INSTALL_DEFAULT_KAMELETS", "false") + answer, err := action.Handle(context.TODO(), &ip) + os.Unsetenv("KAMEL_INSTALL_DEFAULT_KAMELETS") + require.NoError(t, err) + assert.NotNil(t, answer) + + assert.Equal(t, v1.IntegrationPlatformPhaseReady, answer.Status.Phase) + assert.Equal(t, "4.4.0", answer.Status.Build.RuntimeCoreVersion) + assert.Equal(t, corev1.ConditionTrue, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status) +} + +func TestCreateNewCatalog(t *testing.T) { + ip := v1.IntegrationPlatform{} + ip.Namespace = "ns" + ip.Name = "ck" + ip.Status = v1.IntegrationPlatformStatus{ + IntegrationPlatformSpec: v1.IntegrationPlatformSpec{ + Build: v1.IntegrationPlatformBuildSpec{ + RuntimeProvider: v1.RuntimeProviderQuarkus, + RuntimeVersion: defaults.DefaultRuntimeVersion, + }, + }, + } + if strings.Contains(ip.Spec.Build.RuntimeVersion, "SNAPSHOT") { + maven.DefaultMavenRepositories += ",https://repository.apache.org/content/repositories/snapshots-group@snapshots@id=apache-snapshots" + } c, err := test.NewFakeClient(&ip) require.NoError(t, err) + // use local Maven executable in tests + t.Setenv("MAVEN_WRAPPER", boolean.FalseString) + _, ok := os.LookupEnv("MAVEN_CMD") + if !ok { + t.Setenv("MAVEN_CMD", "mvn") + } + + fakeClient := c.(*test.FakeClient) //nolint + fakeClient.AddReactor("create", "*", func(action k8stesting.Action) (bool, runtime.Object, error) { + createAction := action.(k8stesting.CreateAction) //nolint + + assert.Equal(t, "ns", createAction.GetNamespace()) + + return true, createAction.GetObject(), nil + }) + err = platform.ConfigureDefaults(context.TODO(), c, &ip, false) require.NoError(t, err) - h := NewCreateAction() - h.InjectLogger(log.Log) - h.InjectClient(c) + action := NewCreateAction() + action.InjectLogger(log.Log) + action.InjectClient(c) - answer, err := h.Handle(context.TODO(), &ip) + // Set the folder where to install testing kamelets + tmpDir, err := os.MkdirTemp("/tmp", "kamelets*") + assert.NoError(t, err) + os.Setenv(kameletDirEnv, tmpDir) + answer, err := action.Handle(context.TODO(), &ip) + os.Unsetenv(kameletDirEnv) require.NoError(t, err) assert.NotNil(t, answer) + assert.Equal(t, v1.IntegrationPlatformPhaseReady, answer.Status.Phase) + assert.Equal(t, corev1.ConditionTrue, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status) + // We don't know exactly which is the core version, it is enough to check is not empty in the test + assert.NotEqual(t, "", answer.Status.Build.RuntimeCoreVersion) + assert.Equal(t, corev1.ConditionTrue, answer.Status.GetCondition(v1.IntegrationPlatformConditionKameletCatalogAvailable).Status) + assert.Contains(t, answer.Status.GetCondition(v1.IntegrationPlatformConditionKameletCatalogAvailable).Message, + fmt.Sprintf("successfully installed Kamelet catalog version %s", answer.Status.Build.RuntimeCoreVersion), + "failed 0 Kamelets") + list := v1.NewCamelCatalogList() err = c.List(context.TODO(), &list, k8sclient.InNamespace(ip.Namespace)) require.NoError(t, err) assert.NotEmpty(t, list.Items) +} - items, err := resources.WithPrefix("/resources/camel-catalog-") +func TestCreateCatalogError(t *testing.T) { + ip := v1.IntegrationPlatform{} + ip.Namespace = "ns" + ip.Name = "ck" + ip.Spec.Build.RuntimeVersion = "0.0.0" + c, err := test.NewFakeClient(&ip) require.NoError(t, err) - foundOverall := 0 - for _, k := range items { - found := false + // use local Maven executable in tests + t.Setenv("MAVEN_WRAPPER", boolean.FalseString) + _, ok := os.LookupEnv("MAVEN_CMD") + if !ok { + t.Setenv("MAVEN_CMD", "mvn") + } + + fakeClient := c.(*test.FakeClient) //nolint + fakeClient.AddReactor("create", "*", func(action k8stesting.Action) (bool, runtime.Object, error) { + createAction := action.(k8stesting.CreateAction) //nolint - for _, c := range list.Items { - n := strings.TrimSuffix(k, ".yaml") - n = strings.TrimPrefix(n, "resources/") - n = strings.ToLower(n) + assert.Equal(t, "ns", createAction.GetNamespace()) - if c.Name == n { - found = true - foundOverall++ - } - } + return true, nil, errors.New("failed to create catalog for some reason") + }) - assert.True(t, found) - } + err = platform.ConfigureDefaults(context.TODO(), c, &ip, false) + require.NoError(t, err) + + action := NewCreateAction() + action.InjectLogger(log.Log) + action.InjectClient(c) + + answer, err := action.Handle(context.TODO(), &ip) + require.Error(t, err) + assert.NotNil(t, answer) - assert.Equal(t, 1, foundOverall) + assert.Equal(t, v1.IntegrationPlatformPhaseError, answer.Status.Phase) + assert.Equal(t, corev1.ConditionFalse, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status) + assert.Equal(t, v1.IntegrationPlatformConditionCamelCatalogAvailableReason, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Reason) + assert.Contains(t, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Message, "camel catalog 0.0.0 not available, please review given runtime version. Error:") } diff --git a/pkg/controller/integrationplatform/initialize.go b/pkg/controller/integrationplatform/initialize.go index 4b68c2ff90..9a0141f26d 100644 --- a/pkg/controller/integrationplatform/initialize.go +++ b/pkg/controller/integrationplatform/initialize.go @@ -19,10 +19,11 @@ package integrationplatform import ( "context" + "fmt" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" platformutil "github.com/apache/camel-k/v2/pkg/platform" - "github.com/apache/camel-k/v2/pkg/util/defaults" + corev1 "k8s.io/api/core/v1" ) // NewInitializeAction returns the action that initializes the integration platform when not provided by the user. @@ -47,8 +48,18 @@ func (action *initializeAction) Handle(ctx context.Context, platform *v1.Integra if err := platformutil.ConfigureDefaults(ctx, action.client, platform, true); err != nil { return nil, err } - platform.Status.Phase = v1.IntegrationPlatformPhaseCreating - platform.Status.Version = defaults.Version + if platform.Status.Build.RuntimeVersion == "" { + platform.Status.Phase = v1.IntegrationPlatformPhaseError + platform.Status.SetCondition( + v1.IntegrationPlatformConditionTypeCreated, + corev1.ConditionFalse, + "MissingRuntimeVersionSpec", + "Runtime version missing from build spec") + + return platform, fmt.Errorf("runtime version missing from build spec") + } else { + platform.Status.Phase = v1.IntegrationPlatformPhaseCreating + } return platform, nil } diff --git a/pkg/controller/integrationplatform/initialize_test.go b/pkg/controller/integrationplatform/initialize_test.go index b7b3960536..14a66deeb3 100644 --- a/pkg/controller/integrationplatform/initialize_test.go +++ b/pkg/controller/integrationplatform/initialize_test.go @@ -22,28 +22,66 @@ import ( "testing" "time" - "github.com/rs/xid" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" "github.com/apache/camel-k/v2/pkg/platform" + "github.com/apache/camel-k/v2/pkg/util/defaults" "github.com/apache/camel-k/v2/pkg/util/log" "github.com/apache/camel-k/v2/pkg/util/test" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func TestTimeouts_Default(t *testing.T) { +func TestDefaultRuntimeSpec(t *testing.T) { ip := v1.IntegrationPlatform{} ip.Namespace = "ns" - ip.Name = xid.New().String() - ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift - ip.Spec.Profile = v1.TraitProfileOpenShift + ip.Name = "ck" + c, err := test.NewFakeClient(&ip) + require.NoError(t, err) + + h := NewInitializeAction() + h.InjectLogger(log.Log) + h.InjectClient(c) + + answer, err := h.Handle(context.TODO(), &ip) + require.NoError(t, err) + assert.Equal(t, v1.IntegrationPlatformPhaseCreating, answer.Status.Phase) + assert.Equal(t, defaults.DefaultRuntimeVersion, answer.Status.Build.RuntimeVersion) + assert.Equal(t, v1.RuntimeProviderQuarkus, answer.Status.Build.RuntimeProvider) +} +func TestUserRuntimeSpec(t *testing.T) { + ip := v1.IntegrationPlatform{} + ip.Namespace = "ns" + ip.Name = "ck" + ip.Spec = v1.IntegrationPlatformSpec{ + Build: v1.IntegrationPlatformBuildSpec{ + RuntimeVersion: "1.2.3", + RuntimeProvider: "MyProvider", + }, + } c, err := test.NewFakeClient(&ip) require.NoError(t, err) + h := NewInitializeAction() + h.InjectLogger(log.Log) + h.InjectClient(c) + + answer, err := h.Handle(context.TODO(), &ip) + require.NoError(t, err) + assert.Equal(t, v1.IntegrationPlatformPhaseCreating, answer.Status.Phase) + assert.Equal(t, "1.2.3", answer.Status.Build.RuntimeVersion) + assert.Equal(t, v1.RuntimeProvider("MyProvider"), answer.Status.Build.RuntimeProvider) +} + +func TestDefaultTimeouts(t *testing.T) { + ip := v1.IntegrationPlatform{} + ip.Namespace = "ns" + ip.Name = "ck" + c, err := test.NewFakeClient(&ip) + + require.NoError(t, err) require.NoError(t, platform.ConfigureDefaults(context.TODO(), c, &ip, false)) h := NewInitializeAction() @@ -52,67 +90,50 @@ func TestTimeouts_Default(t *testing.T) { answer, err := h.Handle(context.TODO(), &ip) require.NoError(t, err) - assert.NotNil(t, answer) - + assert.Equal(t, v1.IntegrationPlatformPhaseCreating, answer.Status.Phase) assert.Equal(t, 5*time.Minute, answer.Status.Build.GetTimeout().Duration) } -func TestTimeouts_MavenComputedFromBuild(t *testing.T) { +func TestMavenComputedFromBuildTimeouts(t *testing.T) { ip := v1.IntegrationPlatform{} ip.Namespace = "ns" - ip.Name = xid.New().String() - ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift - ip.Spec.Profile = v1.TraitProfileOpenShift - + ip.Name = "ck" timeout, err := time.ParseDuration("1m1ms") require.NoError(t, err) - ip.Spec.Build.Timeout = &metav1.Duration{ Duration: timeout, } - c, err := test.NewFakeClient(&ip) require.NoError(t, err) - require.NoError(t, platform.ConfigureDefaults(context.TODO(), c, &ip, false)) - h := NewInitializeAction() h.InjectLogger(log.Log) h.InjectClient(c) - answer, err := h.Handle(context.TODO(), &ip) + require.NoError(t, err) assert.NotNil(t, answer) - assert.Equal(t, 1*time.Minute, answer.Status.Build.GetTimeout().Duration) } -func TestTimeouts_Truncated(t *testing.T) { +func TestTruncatedTimeouts(t *testing.T) { ip := v1.IntegrationPlatform{} ip.Namespace = "ns" - ip.Name = xid.New().String() - ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift - ip.Spec.Profile = v1.TraitProfileOpenShift - + ip.Name = "ck" bt, err := time.ParseDuration("5m1ms") require.NoError(t, err) - ip.Spec.Build.Timeout = &metav1.Duration{ Duration: bt, } - c, err := test.NewFakeClient(&ip) require.NoError(t, err) - require.NoError(t, platform.ConfigureDefaults(context.TODO(), c, &ip, false)) - h := NewInitializeAction() h.InjectLogger(log.Log) h.InjectClient(c) - answer, err := h.Handle(context.TODO(), &ip) + require.NoError(t, err) assert.NotNil(t, answer) - assert.Equal(t, 5*time.Minute, answer.Status.Build.GetTimeout().Duration) } diff --git a/pkg/controller/integrationplatform/integrationplatform_controller.go b/pkg/controller/integrationplatform/integrationplatform_controller.go index 99197df4ab..ba48423dab 100644 --- a/pkg/controller/integrationplatform/integrationplatform_controller.go +++ b/pkg/controller/integrationplatform/integrationplatform_controller.go @@ -19,7 +19,6 @@ package integrationplatform import ( "context" - "time" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -41,10 +40,6 @@ import ( "github.com/apache/camel-k/v2/pkg/util/monitoring" ) -const ( - requeueAfterDuration = 5 * time.Second -) - // Add creates a new IntegrationPlatform Controller and adds it to the Manager. The Manager will set fields // on the Controller and Start it when the Manager is Started. func Add(ctx context.Context, mgr manager.Manager, c client.Client) error { @@ -161,7 +156,6 @@ func (r *reconcileIntegrationPlatform) Reconcile(ctx context.Context, request re NewInitializeAction(), NewCreateAction(), NewMonitorAction(), - NewCreateCatalogAction(), } var targetPhase v1.IntegrationPlatformPhase @@ -214,12 +208,5 @@ func (r *reconcileIntegrationPlatform) Reconcile(ctx context.Context, request re break } - if targetPhase == v1.IntegrationPlatformPhaseReady { - return reconcile.Result{}, nil - } - - // Requeue - return reconcile.Result{ - RequeueAfter: requeueAfterDuration, - }, nil + return reconcile.Result{}, nil } diff --git a/pkg/controller/integrationplatform/kamelets.go b/pkg/controller/integrationplatform/kamelets.go new file mode 100644 index 0000000000..370f464e0a --- /dev/null +++ b/pkg/controller/integrationplatform/kamelets.go @@ -0,0 +1,209 @@ +/* +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 integrationplatform + +import ( + "context" + "encoding/json" + "fmt" + "io/fs" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + + v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" + "github.com/apache/camel-k/v2/pkg/install" + "github.com/apache/camel-k/v2/pkg/platform" + "knative.dev/pkg/ptr" + + "github.com/apache/camel-k/v2/pkg/client" + "github.com/apache/camel-k/v2/pkg/util" + "github.com/apache/camel-k/v2/pkg/util/defaults" + "github.com/apache/camel-k/v2/pkg/util/log" + "github.com/apache/camel-k/v2/pkg/util/maven" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8syaml "k8s.io/apimachinery/pkg/util/yaml" +) + +const ( + kameletDirEnv = "KAMELET_CATALOG_DIR" + defaultKameletDir = "/tmp/kamelets/" + kamelVersionAnnotation = "camel.apache.org/version" +) + +// installKameletCatalog installs the version Apache Kamelet Catalog into the specified namespace. It returns the number of Kamelets installed and errored +// if successful. +func installKameletCatalog(ctx context.Context, c client.Client, platform *v1.IntegrationPlatform, version string) (int, int, error) { + // Prepare proper privileges for Kamelets installed globally + if err := prepareKameletsPermissions(ctx, c, platform.Namespace); err != nil { + return -1, -1, err + } + // Prepare directory to contains kamelets + kameletDir := prepareKameletDirectory() + // Download Kamelet dependency + if err := downloadKameletDependency(ctx, version, kameletDir); err != nil { + return -1, -1, err + } + // Extract Kamelets files + if err := extractKameletsFromDependency(ctx, version, kameletDir); err != nil { + return -1, -1, err + } + + // Store Kamelets as Kubernetes resources + return applyKamelets(ctx, c, platform, kameletDir) +} + +func prepareKameletsPermissions(ctx context.Context, c client.Client, installingNamespace string) error { + watchOperatorNamespace := platform.GetOperatorWatchNamespace() + operatorNamespace := platform.GetOperatorNamespace() + if watchOperatorNamespace == "" && operatorNamespace == installingNamespace { + // Kamelets installed into the global operator namespace + // They need to be visible publicly + if err := kameletViewerRole(ctx, c, installingNamespace); err != nil { + return err + } + } + + return nil +} + +func prepareKameletDirectory() string { + kameletDir := os.Getenv(kameletDirEnv) + if kameletDir == "" { + kameletDir = defaultKameletDir + } + + return kameletDir +} + +func downloadKameletDependency(ctx context.Context, version, kameletsDir string) error { + // TODO: we may want to add the maven settings coming from the platform + // in order to cover any user security setting in place + p := maven.NewProjectWithGAV("org.apache.camel.k.kamelets", "kamelets-catalog", defaults.Version) + mc := maven.NewContext(kameletsDir) + mc.AddArgument("-q") + mc.AddArgument("dependency:copy") + mc.AddArgument(fmt.Sprintf("-Dartifact=org.apache.camel.kamelets:camel-kamelets:%s:jar", version)) + mc.AddArgument("-Dmdep.useBaseVersion=true") + mc.AddArgument(fmt.Sprintf("-DoutputDirectory=%s", kameletsDir)) + + return p.Command(mc).Do(ctx) +} + +func extractKameletsFromDependency(ctx context.Context, version, kameletsDir string) error { + args := strings.Split( + fmt.Sprintf("-xf camel-kamelets-%s.jar kamelets/", version), " ") + cmd := exec.CommandContext(ctx, "jar", args...) + cmd.Dir = kameletsDir + return util.RunAndLog(ctx, cmd, maven.LogHandler, maven.LogHandler) +} + +func applyKamelets(ctx context.Context, c client.Client, platform *v1.IntegrationPlatform, kameletDir string) (int, int, error) { + appliedKam := 0 + erroredKam := 0 + applier := c.ServerOrClientSideApplier() + dir := path.Join(kameletDir, "kamelets") + + err := filepath.WalkDir(dir, func(p string, f fs.DirEntry, err error) error { + if err != nil { + return err + } + if !(strings.HasSuffix(f.Name(), ".yaml") || strings.HasSuffix(f.Name(), ".yml")) { + return nil + } + kamelet, err := loadKamelet(filepath.Join(dir, f.Name()), platform) + // We cannot return if an error happen, otherwise the creation of the IntegrationPlatform would result + // in a failure. We better report in conditions. + if err != nil { + erroredKam++ + log.Errorf(err, "Error occurred whilst loading a bundled kamelet named %s", f.Name()) + return nil + } + err = applier.Apply(ctx, kamelet) + if err != nil { + erroredKam++ + log.Error(err, "Error occurred whilst applying a bundled kamelet named %s", kamelet.GetName()) + return nil + } + appliedKam++ + + return nil + }) + if err != nil { + return appliedKam, erroredKam, err + } + + return appliedKam, erroredKam, nil +} + +func loadKamelet(path string, platform *v1.IntegrationPlatform) (*v1.Kamelet, error) { + yamlContent, err := util.ReadFile(path) + if err != nil { + return nil, err + } + // Kamelet spec contains raw object spec, for which we need to convert to json + // for a proper serde + jsonContent, err := k8syaml.ToJSON(yamlContent) + if err != nil { + return nil, err + } + var kamelet *v1.Kamelet + if err = json.Unmarshal(jsonContent, &kamelet); err != nil { + return nil, err + } + kamelet.SetNamespace(platform.Namespace) + annotations := kamelet.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) + } + annotations[kamelVersionAnnotation] = defaults.Version + kamelet.SetAnnotations(annotations) + labels := kamelet.GetLabels() + if labels == nil { + labels = make(map[string]string) + } + labels[v1.KameletBundledLabel] = "true" + labels[v1.KameletReadOnlyLabel] = "true" + + // The Kamelet will be owned by the IntegrationPlatform + references := []metav1.OwnerReference{ + { + APIVersion: platform.APIVersion, + Kind: platform.Kind, + Name: platform.Name, + UID: platform.UID, + Controller: ptr.Bool(true), + BlockOwnerDeletion: ptr.Bool(true), + }, + } + kamelet.SetOwnerReferences(references) + + return kamelet, nil +} + +// kameletViewerRole installs the role that allows any user ro access kamelets in the global namespace. +func kameletViewerRole(ctx context.Context, c client.Client, namespace string) error { + if err := install.Resource(ctx, c, namespace, true, install.IdentityResourceCustomizer, + "/resources/viewer/user-global-kamelet-viewer-role.yaml"); err != nil { + return err + } + return install.Resource(ctx, c, namespace, true, install.IdentityResourceCustomizer, + "/resources/viewer/user-global-kamelet-viewer-role-binding.yaml") +} diff --git a/pkg/controller/integrationplatform/kamelets_test.go b/pkg/controller/integrationplatform/kamelets_test.go new file mode 100644 index 0000000000..7a1a79aa64 --- /dev/null +++ b/pkg/controller/integrationplatform/kamelets_test.go @@ -0,0 +1,153 @@ +/* +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 integrationplatform + +import ( + "context" + "fmt" + "io/fs" + "os" + "path" + "path/filepath" + "strings" + "testing" + + v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" + "github.com/apache/camel-k/v2/pkg/util/boolean" + "github.com/apache/camel-k/v2/pkg/util/camel" + "github.com/apache/camel-k/v2/pkg/util/test" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLoadKamelet(t *testing.T) { + itp := v1.NewIntegrationPlatform("itp-ns", "my-itp") + var tmpKameletFile *os.File + var err error + tmpKameletFile, err = os.CreateTemp("/tmp", "timer-source-*.kamelet.yaml") + require.NoError(t, err) + require.NoError(t, tmpKameletFile.Close()) + require.NoError(t, os.WriteFile(tmpKameletFile.Name(), []byte(`apiVersion: camel.apache.org/v1 +kind: Kamelet +metadata: + name: timer-source + annotations: + camel.apache.org/kamelet.icon: "data:image/svg+xml;base64,XYZABC123" + labels: + camel.apache.org/kamelet.type: "source" +spec: + definition: + title: "Timer Source" + description: "Produces periodic events with a custom payload" + required: + - message + properties: + period: + title: Period + description: The interval between two events + type: integer + default: 1000 + message: + title: Message + description: The message to generate + type: string + example: "hello world" + dataTypes: + out: + default: text + types: + text: + mediaType: text/plain + template: + from: + uri: timer:tick + parameters: + period: "{{period}}" + steps: + - setBody: + constant: "{{message}}" + - to: "kamelet:sink" +`), 0o400)) + + kamelet, err := loadKamelet(tmpKameletFile.Name(), &itp) + + assert.NotNil(t, kamelet) + require.NoError(t, err) + assert.Equal(t, "timer-source", kamelet.GetName()) + assert.Equal(t, "itp-ns", kamelet.GetNamespace()) + assert.Len(t, kamelet.GetLabels(), 3) + assert.Equal(t, "true", kamelet.GetLabels()[v1.KameletBundledLabel]) + assert.Equal(t, "true", kamelet.GetLabels()[v1.KameletReadOnlyLabel]) + assert.Len(t, kamelet.GetAnnotations(), 2) + assert.NotNil(t, kamelet.GetAnnotations()[kamelVersionAnnotation]) + assert.Equal(t, "my-itp", kamelet.GetOwnerReferences()[0].Name) +} + +func TestPrepareKameletsPermissions(t *testing.T) { + c, err := test.NewFakeClient() + assert.NoError(t, err) + err = prepareKameletsPermissions(context.TODO(), c, "camel-k") + assert.NoError(t, err) +} + +func TestPrepareKameletsDirectory(t *testing.T) { + kameletDir := prepareKameletDirectory() + assert.Equal(t, defaultKameletDir, kameletDir) +} + +func TestDownloadKameletDependencyAndExtract(t *testing.T) { + // use local Maven executable in tests + t.Setenv("MAVEN_WRAPPER", boolean.FalseString) + _, ok := os.LookupEnv("MAVEN_CMD") + if !ok { + t.Setenv("MAVEN_CMD", "mvn") + } + + tmpDir, err := os.MkdirTemp("/tmp", "kamelets*") + assert.NoError(t, err) + // Load default catalog in order to get the default Camel version + c, err := camel.DefaultCatalog() + assert.NoError(t, err) + camelVersion := c.Runtime.Metadata["camel.version"] + assert.NotEqual(t, "", camelVersion) + err = downloadKameletDependency(context.TODO(), camelVersion, tmpDir) + assert.NoError(t, err) + downloadedDependency, err := os.Stat(path.Join(tmpDir, fmt.Sprintf("camel-kamelets-%s.jar", camelVersion))) + assert.NoError(t, err) + + assert.Equal(t, fmt.Sprintf("camel-kamelets-%s.jar", camelVersion), downloadedDependency.Name()) + + // We can extract the Kamelets now + err = extractKameletsFromDependency(context.TODO(), camelVersion, tmpDir) + assert.NoError(t, err) + kameletsDir, err := os.Stat(path.Join(tmpDir, "kamelets")) + assert.NoError(t, err) + assert.True(t, kameletsDir.IsDir()) + count := 0 + err = filepath.WalkDir(path.Join(tmpDir, "kamelets"), func(p string, f fs.DirEntry, err error) error { + if err != nil { + return err + } + if strings.HasSuffix(f.Name(), ".yaml") || strings.HasSuffix(f.Name(), ".yml") { + count++ + } + return nil + }) + assert.NoError(t, err) + assert.True(t, count > 0) +} diff --git a/pkg/controller/integrationplatform/monitor.go b/pkg/controller/integrationplatform/monitor.go index 474fe2934f..d615525d97 100644 --- a/pkg/controller/integrationplatform/monitor.go +++ b/pkg/controller/integrationplatform/monitor.go @@ -24,7 +24,6 @@ import ( v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" platformutil "github.com/apache/camel-k/v2/pkg/platform" - "github.com/apache/camel-k/v2/pkg/util/camel" "github.com/apache/camel-k/v2/pkg/util/defaults" "github.com/apache/camel-k/v2/pkg/util/openshift" corev1 "k8s.io/api/core/v1" @@ -47,22 +46,37 @@ func (action *monitorAction) CanHandle(platform *v1.IntegrationPlatform) bool { return platform.Status.Phase == v1.IntegrationPlatformPhaseReady || platform.Status.Phase == v1.IntegrationPlatformPhaseError } -//nolint:nestif func (action *monitorAction) Handle(ctx context.Context, platform *v1.IntegrationPlatform) (*v1.IntegrationPlatform, error) { - // Just track the version of the operator in the platform resource - if platform.Status.Version != defaults.Version { - platform.Status.Version = defaults.Version - action.L.Info("IntegrationPlatform version updated", "version", platform.Status.Version) - } - - // TODO: refactor the phase transition as it is hard to reason - platformPhase := v1.IntegrationPlatformPhaseReady + runtimeVersion := specOrDefault(platform.Spec.Build.RuntimeVersion) + if platform.Status.Build.RuntimeVersion != runtimeVersion { + action.L.Infof("IntegrationPlatform version updated from %s to %s", platform.Status.Build.RuntimeVersion, runtimeVersion) + // Reset the status to reinitialize the resource + platform.Status = v1.IntegrationPlatformStatus{} - // Refresh applied configuration + return platform, nil + } + // Sync status configuration if err := platformutil.ConfigureDefaults(ctx, action.client, platform, false); err != nil { return nil, err } + // Get the information about Camel version in the catalog + runtimeSpec := v1.RuntimeSpec{ + Version: platform.Status.Build.RuntimeVersion, + Provider: platform.Status.Build.RuntimeProvider, + } + catalog, err := loadCatalog(ctx, action.client, platform.Namespace, runtimeSpec) + if catalog == nil || err != nil { + // error, a catalog must be available + platform.Status.Phase = v1.IntegrationPlatformPhaseError + platform.Status.SetCondition( + v1.IntegrationPlatformConditionCamelCatalogAvailable, + corev1.ConditionFalse, + v1.IntegrationPlatformConditionCamelCatalogAvailableReason, + fmt.Sprintf("camel catalog %s not available, please review given runtime version", runtimeSpec.Version)) + return platform, err + } + platform.Status.Build.RuntimeCoreVersion = catalog.Spec.GetCamelVersion() // Registry condition isOpenshift, err := openshift.IsOpenShift(action.client) if err != nil { @@ -77,7 +91,7 @@ func (action *monitorAction) Handle(ctx context.Context, platform *v1.Integratio } else { if platform.Status.Build.Registry.Address == "" { // error, we need a registry if we're not on Openshift - platformPhase = v1.IntegrationPlatformPhaseError + platform.Status.Phase = v1.IntegrationPlatformPhaseError platform.Status.SetCondition( v1.IntegrationPlatformConditionTypeRegistryAvailable, corev1.ConditionFalse, @@ -91,41 +105,6 @@ func (action *monitorAction) Handle(ctx context.Context, platform *v1.Integratio fmt.Sprintf("registry available at %s", platform.Status.Build.Registry.Address)) } } - - if platformPhase == v1.IntegrationPlatformPhaseReady { - // Camel catalog condition - runtimeSpec := v1.RuntimeSpec{ - Version: platform.Status.Build.RuntimeVersion, - Provider: v1.RuntimeProviderQuarkus, - } - if catalog, err := camel.LoadCatalog(ctx, action.client, platform.Namespace, runtimeSpec); err != nil { - action.L.Error(err, "IntegrationPlatform unable to load Camel catalog", - "runtime-version", runtimeSpec.Version, "runtime-provider", runtimeSpec.Provider) - } else if catalog == nil { - if platform.Status.Phase != v1.IntegrationPlatformPhaseError { - platformPhase = v1.IntegrationPlatformPhaseCreateCatalog - } else { - // IntegrationPlatform is in error phase for some reason - that error state must be resolved before we move into create catalog phase - // avoids to run into endless loop of error and catalog creation phase ping pong - platformPhase = v1.IntegrationPlatformPhaseError - } - - platform.Status.SetCondition( - v1.IntegrationPlatformConditionCamelCatalogAvailable, - corev1.ConditionFalse, - v1.IntegrationPlatformConditionCamelCatalogAvailableReason, - fmt.Sprintf("camel catalog %s not available, please review given runtime version", runtimeSpec.Version)) - } else { - platform.Status.SetCondition( - v1.IntegrationPlatformConditionCamelCatalogAvailable, - corev1.ConditionTrue, - v1.IntegrationPlatformConditionCamelCatalogAvailableReason, - fmt.Sprintf("camel catalog %s available", runtimeSpec.Version)) - platform.Status.Build.RuntimeCoreVersion = catalog.Runtime.Metadata["camel.version"] - } - } - - platform.Status.Phase = platformPhase action.checkTraitAnnotationsDeprecatedNotice(platform) return platform, nil @@ -151,3 +130,10 @@ func (action *monitorAction) checkTraitAnnotationsDeprecatedNotice(platform *v1. } } } + +func specOrDefault(runtimeVersionSpec string) string { + if runtimeVersionSpec == "" { + return defaults.DefaultRuntimeVersion + } + return runtimeVersionSpec +} diff --git a/pkg/controller/integrationplatform/monitor_test.go b/pkg/controller/integrationplatform/monitor_test.go index 875c237f8c..ddbcc1bdf6 100644 --- a/pkg/controller/integrationplatform/monitor_test.go +++ b/pkg/controller/integrationplatform/monitor_test.go @@ -23,11 +23,10 @@ import ( "testing" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" - "github.com/apache/camel-k/v2/pkg/platform" + "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait" "github.com/apache/camel-k/v2/pkg/util/defaults" "github.com/apache/camel-k/v2/pkg/util/log" "github.com/apache/camel-k/v2/pkg/util/test" - "github.com/rs/xid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -37,15 +36,8 @@ import ( func TestCanHandlePhaseReadyOrError(t *testing.T) { ip := v1.IntegrationPlatform{} ip.Namespace = "ns" - ip.Name = xid.New().String() - ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift - ip.Spec.Profile = v1.TraitProfileOpenShift - ip.Spec.Build.Registry.Address = defaults.OpenShiftRegistryAddress - - ip.Spec.Build.RuntimeVersion = defaults.DefaultRuntimeVersion - + ip.Name = "ck" ip.Status.Phase = v1.IntegrationPlatformPhaseReady - c, err := test.NewFakeClient(&ip) require.NoError(t, err) @@ -60,29 +52,31 @@ func TestCanHandlePhaseReadyOrError(t *testing.T) { answer = action.CanHandle(&ip) assert.True(t, answer) - ip.Status.Phase = v1.IntegrationPlatformPhaseCreateCatalog + ip.Status.Phase = v1.IntegrationPlatformPhaseCreating answer = action.CanHandle(&ip) assert.False(t, answer) } -func TestMonitor(t *testing.T) { +func TestMonitorReady(t *testing.T) { + catalog := v1.NewCamelCatalog("ns", fmt.Sprintf("camel-catalog-%s", "1.2.3")) + catalog.Spec.Runtime.Version = "1.2.3" + catalog.Spec.Runtime.Provider = v1.RuntimeProviderQuarkus + catalog.Spec.Runtime.Metadata = map[string]string{ + "camel.version": "3.2.1", + } ip := v1.IntegrationPlatform{} ip.Namespace = "ns" - ip.Name = xid.New().String() - ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift - ip.Spec.Profile = v1.TraitProfileOpenShift - ip.Spec.Build.Registry.Address = defaults.OpenShiftRegistryAddress - - catalog := v1.NewCamelCatalog("ns", fmt.Sprintf("camel-catalog-%s", defaults.DefaultRuntimeVersion)) - catalog.Spec.Runtime.Version = defaults.DefaultRuntimeVersion - catalog.Spec.Runtime.Provider = v1.RuntimeProviderQuarkus - + ip.Name = "ck" + ip.Spec.Build.Registry.Address = "1.2.3.4" + ip.Spec.Build.RuntimeVersion = "1.2.3" + ip.Spec.Build.RuntimeProvider = v1.RuntimeProviderQuarkus + ip.Status.Build.RuntimeVersion = "1.2.3" + ip.Status.Build.RuntimeProvider = v1.RuntimeProviderQuarkus + ip.Status.Build.Registry.Address = "1.2.3.4" + ip.Status.Phase = v1.IntegrationPlatformPhaseReady c, err := test.NewFakeClient(&ip, &catalog) require.NoError(t, err) - err = platform.ConfigureDefaults(context.TODO(), c, &ip, false) - require.NoError(t, err) - action := NewMonitorAction() action.InjectLogger(log.Log) action.InjectClient(c) @@ -93,25 +87,22 @@ func TestMonitor(t *testing.T) { assert.Equal(t, v1.IntegrationPlatformPhaseReady, answer.Status.Phase) assert.Equal(t, corev1.ConditionTrue, answer.Status.GetCondition(v1.IntegrationPlatformConditionTypeRegistryAvailable).Status) - assert.Equal(t, corev1.ConditionTrue, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status) + assert.Equal(t, "3.2.1", answer.Status.Build.RuntimeCoreVersion) } -func TestMonitorTransitionToCreateCatalog(t *testing.T) { +func TestMonitorDriftRuntime(t *testing.T) { ip := v1.IntegrationPlatform{} ip.Namespace = "ns" - ip.Name = xid.New().String() - ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift - ip.Spec.Profile = v1.TraitProfileOpenShift - ip.Spec.Build.Registry.Address = defaults.OpenShiftRegistryAddress - - ip.Spec.Build.RuntimeVersion = defaults.DefaultRuntimeVersion - + ip.Name = "ck" + ip.Spec.Build.RuntimeVersion = "3.2.1" + ip.Spec.Build.RuntimeProvider = v1.RuntimeProviderQuarkus + ip.Status.Build.RuntimeVersion = "1.2.3" + ip.Status.Build.RuntimeProvider = v1.RuntimeProviderQuarkus + ip.Status.Build.Registry.Address = "1.2.3.4" + ip.Status.Phase = v1.IntegrationPlatformPhaseReady c, err := test.NewFakeClient(&ip) require.NoError(t, err) - err = platform.ConfigureDefaults(context.TODO(), c, &ip, false) - require.NoError(t, err) - action := NewMonitorAction() action.InjectLogger(log.Log) action.InjectClient(c) @@ -120,29 +111,77 @@ func TestMonitorTransitionToCreateCatalog(t *testing.T) { require.NoError(t, err) assert.NotNil(t, answer) - assert.Equal(t, v1.IntegrationPlatformPhaseCreateCatalog, answer.Status.Phase) - assert.Equal(t, corev1.ConditionTrue, answer.Status.GetCondition(v1.IntegrationPlatformConditionTypeRegistryAvailable).Status) - assert.Equal(t, corev1.ConditionFalse, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status) - assert.Equal(t, v1.IntegrationPlatformConditionCamelCatalogAvailableReason, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Reason) - assert.Equal(t, fmt.Sprintf("camel catalog %s not available, please review given runtime version", defaults.DefaultRuntimeVersion), answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Message) + assert.Equal(t, v1.IntegrationPlatformPhaseNone, answer.Status.Phase) } -func TestMonitorRetainErrorState(t *testing.T) { +func TestMonitorDriftDefault(t *testing.T) { + catalog := v1.NewCamelCatalog("ns", fmt.Sprintf("camel-catalog-%s", defaults.DefaultRuntimeVersion)) + catalog.Spec.Runtime.Version = defaults.DefaultRuntimeVersion + catalog.Spec.Runtime.Provider = v1.RuntimeProviderQuarkus + catalog.Spec.Runtime.Metadata = map[string]string{ + "camel.version": "3.2.1", + } ip := v1.IntegrationPlatform{} ip.Namespace = "ns" - ip.Name = xid.New().String() - ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift - ip.Spec.Profile = v1.TraitProfileOpenShift - ip.Spec.Build.Registry.Address = defaults.OpenShiftRegistryAddress - - ip.Spec.Build.RuntimeVersion = defaults.DefaultRuntimeVersion + ip.Name = "ck" + ip.Spec.Build.Registry.Address = "1.2.3.4" + ip.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion + ip.Status.Build.RuntimeProvider = v1.RuntimeProviderQuarkus + ip.Status.Build.Registry.Address = "1.2.3.4" + ip.Status.Phase = v1.IntegrationPlatformPhaseReady + c, err := test.NewFakeClient(&ip, &catalog) + require.NoError(t, err) - ip.Status.Phase = v1.IntegrationPlatformPhaseError + action := NewMonitorAction() + action.InjectLogger(log.Log) + action.InjectClient(c) - c, err := test.NewFakeClient(&ip) + answer, err := action.Handle(context.TODO(), &ip) require.NoError(t, err) + assert.NotNil(t, answer) + + assert.Equal(t, v1.IntegrationPlatformPhaseReady, answer.Status.Phase) + assert.Equal(t, "3.2.1", answer.Status.Build.RuntimeCoreVersion) +} - err = platform.ConfigureDefaults(context.TODO(), c, &ip, false) +func TestMonitorDriftConfiguration(t *testing.T) { + catalog := v1.NewCamelCatalog("ns", fmt.Sprintf("camel-catalog-%s", defaults.DefaultRuntimeVersion)) + catalog.Spec.Runtime.Version = defaults.DefaultRuntimeVersion + catalog.Spec.Runtime.Provider = v1.RuntimeProviderQuarkus + catalog.Spec.Runtime.Metadata = map[string]string{ + "camel.version": "3.2.1", + } + ip := v1.IntegrationPlatform{ + Spec: v1.IntegrationPlatformSpec{ + Build: v1.IntegrationPlatformBuildSpec{ + Registry: v1.RegistrySpec{ + Address: "1.2.3.4", + }, + }, + Traits: v1.Traits{ + Container: &trait.ContainerTrait{ + Name: "override", + }, + }, + Profile: v1.TraitProfileKnative, + }, + Status: v1.IntegrationPlatformStatus{ + IntegrationPlatformSpec: v1.IntegrationPlatformSpec{ + Build: v1.IntegrationPlatformBuildSpec{ + RuntimeVersion: defaults.DefaultRuntimeVersion, + RuntimeProvider: v1.RuntimeProviderQuarkus, + RuntimeCoreVersion: "3.2.1", + Registry: v1.RegistrySpec{ + Address: "1.2.3.4", + }, + }, + }, + Phase: v1.IntegrationPlatformPhaseReady, + }, + } + ip.Namespace = "ns" + ip.Name = "ck" + c, err := test.NewFakeClient(&ip, &catalog) require.NoError(t, err) action := NewMonitorAction() @@ -153,30 +192,30 @@ func TestMonitorRetainErrorState(t *testing.T) { require.NoError(t, err) assert.NotNil(t, answer) - assert.Equal(t, v1.IntegrationPlatformPhaseError, answer.Status.Phase) - assert.Equal(t, corev1.ConditionTrue, answer.Status.GetCondition(v1.IntegrationPlatformConditionTypeRegistryAvailable).Status) - assert.Equal(t, corev1.ConditionFalse, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status) - assert.Equal(t, v1.IntegrationPlatformConditionCamelCatalogAvailableReason, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Reason) - assert.Equal(t, fmt.Sprintf("camel catalog %s not available, please review given runtime version", defaults.DefaultRuntimeVersion), answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Message) + // It should not change the phase, however, it should change the values + assert.Equal(t, v1.IntegrationPlatformPhaseReady, answer.Status.Phase) + assert.Equal(t, defaults.Version, answer.Status.Version) + assert.Equal(t, defaults.DefaultRuntimeVersion, answer.Status.Build.RuntimeVersion) + assert.Equal(t, v1.RuntimeProviderQuarkus, answer.Status.Build.RuntimeProvider) + assert.Equal(t, "3.2.1", answer.Status.Build.RuntimeCoreVersion) + assert.Equal(t, v1.TraitProfileKnative, answer.Status.Profile) + assert.Equal(t, "override", answer.Status.Traits.Container.Name) } func TestMonitorMissingRegistryError(t *testing.T) { + catalog := v1.NewCamelCatalog("ns", fmt.Sprintf("camel-catalog-%s", "1.2.3")) + catalog.Spec.Runtime.Version = "1.2.3" + catalog.Spec.Runtime.Provider = v1.RuntimeProviderQuarkus ip := v1.IntegrationPlatform{} ip.Namespace = "ns" - ip.Name = xid.New().String() - ip.Spec.Cluster = v1.IntegrationPlatformClusterOpenShift - ip.Spec.Profile = v1.TraitProfileOpenShift - - catalog := v1.NewCamelCatalog("ns", fmt.Sprintf("camel-catalog-%s", defaults.DefaultRuntimeVersion)) - catalog.Spec.Runtime.Version = defaults.DefaultRuntimeVersion - catalog.Spec.Runtime.Provider = v1.RuntimeProviderQuarkus - + ip.Name = "ck" + ip.Spec.Build.RuntimeVersion = "1.2.3" + ip.Spec.Build.RuntimeProvider = v1.RuntimeProviderQuarkus + ip.Status.Build.RuntimeVersion = "1.2.3" + ip.Status.Build.RuntimeProvider = v1.RuntimeProviderQuarkus c, err := test.NewFakeClient(&ip, &catalog) require.NoError(t, err) - err = platform.ConfigureDefaults(context.TODO(), c, &ip, false) - require.NoError(t, err) - action := NewMonitorAction() action.InjectLogger(log.Log) action.InjectClient(c) @@ -190,3 +229,29 @@ func TestMonitorMissingRegistryError(t *testing.T) { assert.Equal(t, v1.IntegrationPlatformConditionTypeRegistryAvailableReason, answer.Status.GetCondition(v1.IntegrationPlatformConditionTypeRegistryAvailable).Reason) assert.Equal(t, "registry address not available, you need to set one", answer.Status.GetCondition(v1.IntegrationPlatformConditionTypeRegistryAvailable).Message) } + +func TestMonitorMissingCatalogError(t *testing.T) { + ip := v1.IntegrationPlatform{} + ip.Namespace = "ns" + ip.Name = "ck" + ip.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion + ip.Status.Build.RuntimeProvider = v1.RuntimeProviderQuarkus + c, err := test.NewFakeClient(&ip) + require.NoError(t, err) + + action := NewMonitorAction() + action.InjectLogger(log.Log) + action.InjectClient(c) + + answer, err := action.Handle(context.TODO(), &ip) + require.NoError(t, err) + assert.NotNil(t, answer) + + assert.Equal(t, v1.IntegrationPlatformPhaseError, answer.Status.Phase) + assert.Equal(t, corev1.ConditionFalse, answer.Status.GetCondition(v1.IntegrationPlatformConditionCamelCatalogAvailable).Status) + assert.Equal(t, v1.IntegrationPlatformConditionCamelCatalogAvailableReason, answer.Status.GetCondition( + v1.IntegrationPlatformConditionCamelCatalogAvailable).Reason) + assert.Equal(t, fmt.Sprintf("camel catalog %s not available, please review given runtime version", + defaults.DefaultRuntimeVersion), answer.Status.GetCondition( + v1.IntegrationPlatformConditionCamelCatalogAvailable).Message) +} diff --git a/pkg/install/kamelets.go b/pkg/install/kamelets.go deleted file mode 100644 index daaa7894e6..0000000000 --- a/pkg/install/kamelets.go +++ /dev/null @@ -1,136 +0,0 @@ -/* -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 install - -import ( - "context" - "fmt" - "io/fs" - "os" - "path/filepath" - "strings" - - "golang.org/x/sync/errgroup" - - ctrl "sigs.k8s.io/controller-runtime/pkg/client" - - v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" - - "github.com/apache/camel-k/v2/pkg/client" - "github.com/apache/camel-k/v2/pkg/util" - "github.com/apache/camel-k/v2/pkg/util/defaults" - "github.com/apache/camel-k/v2/pkg/util/kubernetes" - "github.com/apache/camel-k/v2/pkg/util/log" -) - -const ( - kameletDirEnv = "KAMELET_CATALOG_DIR" - defaultKameletDir = "/kamelets/" -) - -// KameletCatalog installs the bundled Kamelets into the specified namespace. -func KameletCatalog(ctx context.Context, c client.Client, namespace string) error { - kameletDir := os.Getenv(kameletDirEnv) - if kameletDir == "" { - kameletDir = defaultKameletDir - } - d, err := os.Stat(kameletDir) - switch { - case err != nil && os.IsNotExist(err): - return nil - case err != nil: - return err - case !d.IsDir(): - return fmt.Errorf("kamelet directory %q is a file", kameletDir) - } - - g, gCtx := errgroup.WithContext(ctx) - applier := c.ServerOrClientSideApplier() - - err = filepath.WalkDir(kameletDir, func(p string, f fs.DirEntry, err error) error { - if err != nil { - return err - } - if f.IsDir() && f.Name() != d.Name() { - return fs.SkipDir - } - if !(strings.HasSuffix(f.Name(), ".yaml") || strings.HasSuffix(f.Name(), ".yml")) { - return nil - } - // We may want to throttle the creation of Go routines if the number of bundled Kamelets increases. - g.Go(func() error { - kamelet, err := loadKamelet(filepath.Join(kameletDir, f.Name()), namespace) - if err != nil { - return err - } - err = applier.Apply(gCtx, kamelet) - // We only log the error. If we returned the error, the creation of the ITP would have stopped - if err != nil { - log.Error(err, "Error occurred whilst applying bundled kamelet") - } - return nil - }) - return nil - }) - if err != nil { - return err - } - - return g.Wait() -} - -func loadKamelet(path string, namespace string) (ctrl.Object, error) { - content, err := util.ReadFile(path) - if err != nil { - return nil, err - } - - kamelet, err := kubernetes.LoadUnstructuredFromYaml(string(content)) - if err != nil { - return nil, err - } - gvk := kamelet.GetObjectKind().GroupVersionKind() - if gvk.Group != v1.SchemeGroupVersion.Group || gvk.Kind != "Kamelet" { - return nil, fmt.Errorf("file %q does not define a Kamelet", path) - } - - kamelet.SetNamespace(namespace) - - annotations := kamelet.GetAnnotations() - if annotations == nil { - annotations = make(map[string]string) - } - annotations[kamelVersionAnnotation] = defaults.Version - kamelet.SetAnnotations(annotations) - - labels := kamelet.GetLabels() - if labels == nil { - labels = make(map[string]string) - } - labels[v1.KameletBundledLabel] = "true" - labels[v1.KameletReadOnlyLabel] = "true" - - kamelet.SetLabels(labels) - - return kamelet, nil -} - -// KameletViewerRole installs the role that allows any user ro access kamelets in the global namespace. -func KameletViewerRole(ctx context.Context, c client.Client, namespace string) error { - return Resource(ctx, c, namespace, true, IdentityResourceCustomizer, "/resources/viewer/user-global-kamelet-viewer-role-binding.yaml") -} diff --git a/pkg/install/kamelets_test.go b/pkg/install/kamelets_test.go deleted file mode 100644 index 67d0cb5779..0000000000 --- a/pkg/install/kamelets_test.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -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 install - -import ( - "testing" - - v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestLoadKamelet(t *testing.T) { - kamelet, err := loadKamelet("testdata/timer-source.kamelet.yaml", "some-namespace") - - assert.NotNil(t, kamelet) - require.NoError(t, err) - assert.Equal(t, "timer-source", kamelet.GetName()) - assert.Equal(t, "some-namespace", kamelet.GetNamespace()) - assert.Len(t, kamelet.GetLabels(), 3) - assert.Equal(t, "true", kamelet.GetLabels()[v1.KameletBundledLabel]) - assert.Equal(t, "true", kamelet.GetLabels()[v1.KameletReadOnlyLabel]) - assert.Len(t, kamelet.GetAnnotations(), 2) - assert.NotNil(t, kamelet.GetAnnotations()[kamelVersionAnnotation]) -} diff --git a/pkg/install/optional.go b/pkg/install/optional.go index eaa5999f74..ddef6f740d 100644 --- a/pkg/install/optional.go +++ b/pkg/install/optional.go @@ -19,10 +19,8 @@ package install import ( "context" - "strings" "github.com/apache/camel-k/v2/pkg/client" - "github.com/apache/camel-k/v2/pkg/util/defaults" logutil "github.com/apache/camel-k/v2/pkg/util/log" ) @@ -33,33 +31,4 @@ func OperatorStartupOptionalTools(ctx context.Context, c client.Client, namespac log.Info("Cannot install OpenShift CLI download link: skipping.") log.Debug("Error while installing OpenShift CLI download link", "error", err) } - - // Try to install Kamelet Catalog automatically - var kameletNamespace string - globalOperator := false - if namespace != "" && !strings.Contains(namespace, ",") { - kameletNamespace = namespace - } else { - kameletNamespace = operatorNamespace - globalOperator = true - } - - if kameletNamespace != "" { - if defaults.InstallDefaultKamelets() { - if err := KameletCatalog(ctx, c, kameletNamespace); err != nil { - log.Info("Cannot install bundled Kamelet Catalog: skipping.") - log.Debug("Error while installing bundled Kamelet Catalog", "error", err) - } - } else { - log.Info("Kamelet Catalog installation is disabled") - } - - if globalOperator { - // Make sure that Kamelets installed in operator namespace can be used by others - if err := KameletViewerRole(ctx, c, kameletNamespace); err != nil { - log.Info("Cannot install global Kamelet viewer role: skipping.") - log.Debug("Error while installing global Kamelet viewer role", "error", err) - } - } - } } diff --git a/pkg/install/testdata/timer-source.kamelet.yaml b/pkg/install/testdata/timer-source.kamelet.yaml deleted file mode 100644 index 3058964c2e..0000000000 --- a/pkg/install/testdata/timer-source.kamelet.yaml +++ /dev/null @@ -1,57 +0,0 @@ -# --------------------------------------------------------------------------- -# 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. -# --------------------------------------------------------------------------- - -apiVersion: camel.apache.org/v1alpha1 -kind: Kamelet -metadata: - name: timer-source - annotations: - camel.apache.org/kamelet.icon: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gU3ZnIFZlY3RvciBJY29ucyA6IGh0dHA6Ly93d3cub25saW5ld2ViZm9udHMuY29tL2ljb24gLS0+DQo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTAwMCAxMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAxMDAwIDEwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPG1ldGFkYXRhPiBTdmcgVmVjdG9yIEljb25zIDogaHR0cDovL3d3dy5vbmxpbmV3ZWJmb250cy5jb20vaWNvbiA8L21ldGFkYXRhPg0KPGc+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wMDAwMDAsNTExLjAwMDAwMCkgc2NhbGUoMC4xMDAwMDAsLTAuMTAwMDAwKSI+PHBhdGggZD0iTTM4ODguMSw0Nzc0Ljl2LTIzNS4xaDQxMS40aDQxNC4zbC04LjgtMzI5LjFsLTguOC0zMzJsLTExNy41LTguOGMtMjI5LjItMTQuNy02MjAtOTkuOS05MjUuNi0xOTYuOUMyMjU3LjQsMzIyMC42LDExNjcuMiwyMDY1LjgsODAyLjksNjQ5LjZjLTUxMS4zLTE5ODYuMywzODQuOS00MDAyLDIyMDYuNy00OTY1LjhjMzAyLjYtMTYxLjYsNzU4LjEtMzIwLjIsMTE1NC44LTQwNS41YzQyNi4xLTkxLjEsMTI1MS43LTkxLjEsMTY4MC43LDBjMTc2OC45LDM4MiwzMDQ0LjEsMTY1Ny4yLDM0MjYuMSwzNDI2LjFjOTEuMSw0MjYuMSw5MS4xLDEyNTQuNiwwLDE2NzcuOGMtNDIwLjIsMTk0Mi4yLTE5MzYuNCwzMzAyLjYtMzg5MC4zLDM0OTYuNmwtMTk5LjgsMjAuNnYzMjAuM3YzMjAuM2g0MTEuNGg0MTEuNHYyMzUuMVY1MDEwSDQ5NDUuOUgzODg4LjFWNDc3NC45eiBNNTc1My45LDMzNDkuOWM3NzguNy0xNjEuNiwxNDE5LjItNTA4LjMsMTk4My40LTEwNzIuNWM1NjQuMi01NjEuMiw4ODcuNC0xMTU3LjcsMTA2MC43LTE5NDIuMmM5OS45LTQzNy44LDk5LjktMTE0MywzLTE1ODAuOEM4NTYzLTIzMDYuNCw3OTY2LjUtMzE1OC41LDcwNDMuOS0zNzUyYy0zMzUtMjE0LjUtNzg3LjUtMzk2LjctMTI0OC44LTQ5OS41Yy00MzcuOC05Ny0xMTQzLTk3LTE1ODAuOCwyLjljLTc4NC41LDE3My4zLTEzODEsNDk2LjYtMTk0Mi4yLDEwNjAuN2MtNTcwLDU2Ny4xLTkwNy45LDExOTguOC0xMDc4LjQsMTk5OC4xYy03My41LDM0Ni43LTczLjUsMTEyMi40LDAsMTQ2OS4yYzE3MC40LDc5OS4yLDUwOC4zLDE0MzEsMTA3OC40LDE5OThDMjg5NSwyOTAwLjMsMzYzOC40LDMyNzMuNSw0NDkzLjQsMzM5MUM0Nzc4LjQsMzQzMi4xLDU0NzQuOCwzNDA4LjYsNTc1My45LDMzNDkuOXoiLz48cGF0aCBkPSJNNDcxMC44LDEzNzUuM1YyMDUuOUw0NTUyLjIsNjcuOGMtMzE3LjMtMjc5LjEtMzQwLjgtNjc4LjctNTUuOC05OTMuMWMyODcuOS0zMjAuMyw2OTMuNC0zMTcuMywxMDEzLjcsNS45bDE3MC40LDE3MC40aDEwNDMuMWgxMDQzLjFWLTUxNFYtMjc5SDY3MjkuNUg1NjkyLjJsLTQ5LjksMTE0LjZjLTU4LjgsMTMyLjItMjUyLjcsMzE3LjMtMzc2LjEsMzYxLjRsLTg1LjIsMjkuNHYxMTU3Ljd2MTE1Ny43aC0yMzUuMWgtMjM1LjFWMTM3NS4zeiBNNTE2Ni4zLTI5My42YzE0Ni45LTE0NCw0NC4xLTM5Ni43LTE2MS42LTM5Ni43Yy01NS44LDAtMTE3LjUsMjYuNC0xNjEuNiw3My40Yy00Nyw0NC4xLTczLjUsMTA1LjgtNzMuNSwxNjEuNnMyNi40LDExNy41LDczLjUsMTYxLjZjNDQuMSw0NywxMDUuOCw3My41LDE2MS42LDczLjVDNTA2MC41LTIyMC4yLDUxMjIuMi0yNDYuNyw1MTY2LjMtMjkzLjZ6Ii8+PC9nPjwvZz4NCjwvc3ZnPg==" - labels: - camel.apache.org/kamelet.type: "source" -spec: - definition: - title: "Timer Source" - description: "Produces periodic events with a custom payload" - required: - - message - properties: - period: - title: Period - description: The interval between two events - type: integer - default: 1000 - message: - title: Message - description: The message to generate - type: string - example: "hello world" - dataTypes: - out: - default: text - types: - text: - mediaType: text/plain - template: - from: - uri: timer:tick - parameters: - period: "{{period}}" - steps: - - setBody: - constant: "{{message}}" - - to: "kamelet:sink" diff --git a/pkg/platform/defaults.go b/pkg/platform/defaults.go index 3b6e89a604..66398013a6 100644 --- a/pkg/platform/defaults.go +++ b/pkg/platform/defaults.go @@ -53,12 +53,22 @@ const ( func ConfigureDefaults(ctx context.Context, c client.Client, p *v1.IntegrationPlatform, verbose bool) error { // Reset the state to initial values p.ResyncStatusFullConfig() - + // Set the operator version controlling this resource + p.Status.Version = defaults.Version // Apply settings from global integration platform that is bound to this operator if err := applyGlobalPlatformDefaults(ctx, c, p); err != nil { return err } + if p.Status.Build.RuntimeProvider == "" { + p.Status.Build.RuntimeProvider = v1.RuntimeProviderQuarkus + log.Debugf("Integration Platform %s [%s]: setting default runtime provider %s", p.Name, p.Namespace, v1.RuntimeProviderQuarkus) + } + if p.Status.Build.RuntimeVersion == "" { + p.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion + log.Debugf("Integration Platform %s [%s]: setting default runtime version %s", p.Name, p.Namespace, p.Status.Build.PublishStrategy) + } + // update missing fields in the resource if p.Status.Cluster == "" { log.Debugf("Integration Platform %s [%s]: setting cluster status", p.Name, p.Namespace) diff --git a/pkg/trait/kamelets.go b/pkg/trait/kamelets.go index 8e861c5d33..fd764264b6 100644 --- a/pkg/trait/kamelets.go +++ b/pkg/trait/kamelets.go @@ -121,6 +121,7 @@ func (t *kameletsTrait) collectKamelets(e *Environment) (map[string]*v1.Kamelet, kamelets := make(map[string]*v1.Kamelet) missingKamelets := make([]string, 0) availableKamelets := make([]string, 0) + bundledKamelets := make([]string, 0) for _, kml := range strings.Split(t.List, ",") { name := getKameletKey(kml, false) @@ -138,6 +139,10 @@ func (t *kameletsTrait) collectKamelets(e *Environment) (map[string]*v1.Kamelet, } else { availableKamelets = append(availableKamelets, name) } + if kamelet.IsBundled() { + bundledKamelets = append(bundledKamelets, name) + } + // We control which version to use (if any is specified) clonedKamelet, err := kamelet.CloneWithVersion(getKameletVersion(kml)) if err != nil { return nil, err @@ -147,13 +152,12 @@ func (t *kameletsTrait) collectKamelets(e *Environment) (map[string]*v1.Kamelet, sort.Strings(availableKamelets) sort.Strings(missingKamelets) + sort.Strings(bundledKamelets) if len(missingKamelets) > 0 { - message := fmt.Sprintf("kamelets [%s] found, kamelets [%s] not found in %s repositories", - strings.Join(availableKamelets, ","), + message := fmt.Sprintf("kamelets [%s] not found in %s repositories", strings.Join(missingKamelets, ","), repo.String()) - e.Integration.Status.SetCondition( v1.IntegrationConditionKameletsAvailable, corev1.ConditionFalse, @@ -164,6 +168,24 @@ func (t *kameletsTrait) collectKamelets(e *Environment) (map[string]*v1.Kamelet, return nil, errors.New(message) } + // TODO: + // We list the Kamelets coming from a bundle. We want to warn the user + // that in the future we'll use the specification coming from the dependency runtime + // instead of using the one installed in the cluster. + // It may be a good idea in the future to let the user specify the catalog dependency to use + // in order to override the one coming from Apache catalog + if len(bundledKamelets) > 0 { + message := fmt.Sprintf("using bundled kamelets [%s]: make sure the Kamelet specifications is compatible with this Integration runtime."+ + " This feature is deprecated as in the future we will use directly the specification coming from the Kamelet catalog dependency jar.", + strings.Join(bundledKamelets, ",")) + e.Integration.Status.SetCondition( + v1.IntegrationConditionType("KameletsDeprecationNotice"), + corev1.ConditionTrue, + "KameletsDeprecationNotice", + message, + ) + } + e.Integration.Status.SetCondition( v1.IntegrationConditionKameletsAvailable, corev1.ConditionTrue, diff --git a/pkg/trait/kamelets_test.go b/pkg/trait/kamelets_test.go index 161ac3714f..ac7ad9411c 100644 --- a/pkg/trait/kamelets_test.go +++ b/pkg/trait/kamelets_test.go @@ -506,7 +506,6 @@ func TestKameletConditionFalse(t *testing.T) { cond := environment.Integration.Status.GetCondition(v1.IntegrationConditionKameletsAvailable) assert.Equal(t, corev1.ConditionFalse, cond.Status) assert.Equal(t, v1.IntegrationConditionKameletsAvailableReason, cond.Reason) - assert.Contains(t, cond.Message, "[timer] found") assert.Contains(t, cond.Message, "kamelets [none] not found") } diff --git a/pkg/util/camel/camel_runtime.go b/pkg/util/camel/camel_runtime.go index 063a30a226..753c548019 100644 --- a/pkg/util/camel/camel_runtime.go +++ b/pkg/util/camel/camel_runtime.go @@ -33,7 +33,8 @@ import ( ) // CreateCatalog --. -func CreateCatalog(ctx context.Context, client client.Client, namespace string, platform *v1.IntegrationPlatform, runtime v1.RuntimeSpec) (*RuntimeCatalog, error) { +func CreateCatalog(ctx context.Context, client client.Client, namespace string, platform *v1.IntegrationPlatform, + runtime v1.RuntimeSpec) (*RuntimeCatalog, error) { ctx, cancel := context.WithTimeout(ctx, platform.Status.Build.GetTimeout().Duration) defer cancel() catalog, err := GenerateCatalog(ctx, client, namespace, platform.Status.Build.Maven, runtime, []maven.Dependency{}) diff --git a/release.adoc b/release.adoc index 6f5407a9b8..a54dc69796 100644 --- a/release.adoc +++ b/release.adoc @@ -1,4 +1,3 @@ - = Releasing Apache Camel K This procedure describes all the steps required to release a new version of Apache Camel K. diff --git a/script/Makefile b/script/Makefile index 6a952deab7..3f6a8b1d18 100644 --- a/script/Makefile +++ b/script/Makefile @@ -90,12 +90,7 @@ OPM := opm # Used to push pre-release artifacts STAGING_IMAGE := docker.io/camelk/camel-k - -# Kamelets options INSTALL_DEFAULT_KAMELETS ?= true -KAMELET_CATALOG_REPO := https://github.com/apache/camel-kamelets.git -# Make sure to use a released tag or empty if you want to get the latest development bits -KAMELET_CATALOG_REPO_TAG := v4.4.1 # When performing integration tests, it is not necessary to always execute build, especially # in e2e tests when lots of tests are being executed sequentially & the build has already taken place. @@ -356,16 +351,6 @@ endif build-resources: ./script/get_catalog.sh $(DEFAULT_RUNTIME_VERSION) -bundle-kamelets: - @echo "Preparing Kamelets bundle resource..." -ifneq (,$(findstring release,$(MAKECMDGOALS))) -ifneq (,$(findstring $(KAMELET_CATALOG_REPO_TAG), main)) - @echo "You cannot set KAMELET_CATALOG_REPO_TAG=$(KAMELET_CATALOG_REPO_TAG) when doing a release" - @exit 1 -endif -endif - ./script/bundle_kamelets.sh $(KAMELET_CATALOG_REPO) $(KAMELET_CATALOG_REPO_TAG) - build-compile-integration-tests: @echo "####### Compiling integration tests..." export CAMEL_K_E2E_JUST_COMPILE="true"; \ @@ -445,7 +430,7 @@ endif DOCKER_TAG := $(CUSTOM_IMAGE):$(CUSTOM_VERSION)-$(IMAGE_ARCH) -images: build maven-overlay bundle-kamelets image-build build-kamel-platform +images: build maven-overlay image-build build-kamel-platform image-build: ifneq (,$(findstring SNAPSHOT,$(DEFAULT_RUNTIME_VERSION))) @@ -550,7 +535,7 @@ release-kustomize: RELEASE_NAME=$(PACKAGE) \ ./script/release_kustomize.sh -.PHONY: do-build build build-kamel build-kamel-platform build-resources dep codegen images images-push images-push-staging image-build test check clean release cross-compile package-examples set-version git-tag check-licenses build-resources release-helm release-staging release-nightly get-staging-repo get-version bundle-kamelets +.PHONY: do-build build build-kamel build-kamel-platform build-resources dep codegen images images-push images-push-staging image-build test check clean release cross-compile package-examples set-version git-tag check-licenses build-resources release-helm release-staging release-nightly get-staging-repo get-version .PHONY: controller-gen kubectl kustomize operator-sdk opm # find or download controller-gen if necessary diff --git a/script/bundle_kamelets.sh b/script/bundle_kamelets.sh deleted file mode 100755 index 2d94ab91e4..0000000000 --- a/script/bundle_kamelets.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/sh - -# 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. - -location=$(dirname $0) -rootdir=$location/../ - -set -e - -repo=$1 -tag=$2 - -cd $rootdir -target=./build/_kamelets - -# Always recreate the dir -rm -rf $target -mkdir $target - -if [ "$repo" = "" ]; then - echo "no kamelet catalog defined: skipping" - exit 0 -fi - -if [ "$tag" = "" ]; then - echo "ERROR: no kamelet catalog version defined" - exit 1 -fi - -echo "Cloning repository $repo from tag $tag to bundle kamelets..." - -rm -rf ./tmp_kamelet_catalog -git clone -q -c advice.detachedHead=false -b $tag --single-branch --depth 1 $repo ./tmp_kamelet_catalog - -cp ./tmp_kamelet_catalog/kamelets/*.kamelet.yaml $target - -rm -rf ./tmp_kamelet_catalog - -# -# Check that all the kamelets have licences -# -./script/add_license.sh $target ./script/headers/yaml.txt