This is an automated email from the ASF dual-hosted git repository. tsato pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-k.git
The following commit(s) were added to refs/heads/main by this push: new 5d6829d30 feat(cli): Make add-repo and remove-repo compatible with a global op 5d6829d30 is described below commit 5d6829d30e54b657ff81eb8590519dfd986aeaa8 Author: Nicolas Filotto <nfilo...@talend.com> AuthorDate: Mon Oct 10 12:58:53 2022 +0200 feat(cli): Make add-repo and remove-repo compatible with a global op --- e2e/global/common/kamelet_test.go | 33 ++++---- .../cli/files/TimerCustomKameletIntegration.java | 28 +++++++ e2e/namespace/install/cli/kamelet_test.go | 60 +++++++++++++ pkg/cmd/kamelet.go | 1 + pkg/cmd/kamelet_add_repo.go | 66 +++++++++++---- pkg/cmd/kamelet_remove_repo.go | 98 ++++++++++++++++++++++ pkg/cmd/kamelet_remove_repo_test.go | 86 +++++++++++++++++++ 7 files changed, 338 insertions(+), 34 deletions(-) diff --git a/e2e/global/common/kamelet_test.go b/e2e/global/common/kamelet_test.go index e6b8834fb..778bc3419 100644 --- a/e2e/global/common/kamelet_test.go +++ b/e2e/global/common/kamelet_test.go @@ -23,7 +23,6 @@ limitations under the License. package common import ( - "os" "testing" . "github.com/onsi/gomega" @@ -32,27 +31,19 @@ import ( . "github.com/apache/camel-k/e2e/support" ) -/* - * This seems to be problematic in a global context - * See https://github.com/apache/camel-k/issues/3667 for details - */ func TestKameletClasspathLoading(t *testing.T) { - if os.Getenv("CAMEL_K_TEST_SKIP_PROBLEMATIC") == "true" { - t.Skip("WARNING: Test marked as problematic ... skipping") - } WithNewTestNamespace(t, func(ns string) { operatorID := "camel-k-kamelet" Expect(KamelInstallWithID(operatorID, ns).Execute()).To(Succeed()) - kameletName := "timer-source" - removeKamelet(kameletName, ns) - - Eventually(Kamelet(kameletName, ns)).Should(BeNil()) - // Basic t.Run("test basic case", func(t *testing.T) { + kameletName := "timer-source" + removeKamelet(kameletName, ns) + Eventually(Kamelet(kameletName, ns)).Should(BeNil()) + Expect(KamelRunWithID(operatorID, ns, "files/TimerKameletIntegration.java", "-t", "kamelets.enabled=false", "--resource", "file:files/timer-source.kamelet.yaml@/kamelets/timer-source.kamelet.yaml", "-p camel.component.kamelet.location=file:/kamelets", @@ -62,21 +53,25 @@ func TestKameletClasspathLoading(t *testing.T) { Eventually(IntegrationPodPhase(ns, "timer-kamelet-integration"), TestTimeoutLong).Should(Equal(corev1.PodRunning)) Eventually(IntegrationLogs(ns, "timer-kamelet-integration")).Should(ContainSubstring("important message")) - - // Cleanup - Expect(Kamel("delete", "--all", "-n", ns).Execute()).Should(BeNil()) }) - // Custom repo - t.Run("test custom Kamelet repository", func(t *testing.T) { + // Custom repo without operator ID + t.Run("test custom Kamelet repository without operator ID", func(t *testing.T) { + + kameletName := "timer-custom-source" + removeKamelet(kameletName, ns) + Eventually(Kamelet(kameletName, ns)).Should(BeNil()) // Add the custom repository - Expect(Kamel("kamelet", "add-repo", "github:apache/camel-k/e2e/global/common/files/kamelets", "-n", ns, "-x", operatorID).Execute()).To(Succeed()) + Expect(Kamel("kamelet", "add-repo", "github:apache/camel-k/e2e/global/common/files/kamelets", "-n", ns).Execute()).To(Succeed()) Expect(KamelRunWithID(operatorID, ns, "files/TimerCustomKameletIntegration.java").Execute()).To(Succeed()) Eventually(IntegrationPodPhase(ns, "timer-custom-kamelet-integration"), TestTimeoutLong).Should(Equal(corev1.PodRunning)) Eventually(IntegrationLogs(ns, "timer-custom-kamelet-integration")).Should(ContainSubstring("great message")) + + // Remove the custom repository + Expect(Kamel("kamelet", "remove-repo", "github:apache/camel-k/e2e/global/common/files/kamelets", "-n", ns).Execute()).To(Succeed()) }) }) } diff --git a/e2e/namespace/install/cli/files/TimerCustomKameletIntegration.java b/e2e/namespace/install/cli/files/TimerCustomKameletIntegration.java new file mode 100644 index 000000000..fe96710d1 --- /dev/null +++ b/e2e/namespace/install/cli/files/TimerCustomKameletIntegration.java @@ -0,0 +1,28 @@ +/* + * 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. + */ + +import java.lang.Exception; +import java.lang.Override; +import org.apache.camel.builder.RouteBuilder; + +public class TimerCustomKameletIntegration extends RouteBuilder { + @Override + public void configure() throws Exception { + from("kamelet:timer-custom-source?message=hello%20world") + .to("log:info"); + } +} diff --git a/e2e/namespace/install/cli/kamelet_test.go b/e2e/namespace/install/cli/kamelet_test.go new file mode 100644 index 000000000..9df9a5bfc --- /dev/null +++ b/e2e/namespace/install/cli/kamelet_test.go @@ -0,0 +1,60 @@ +//go:build integration +// +build integration + +// To enable compilation of this file in Goland, go to "Settings -> Go -> Vendoring & Build Tags -> Custom Tags" and add "integration" + +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "testing" + + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + + . "github.com/apache/camel-k/e2e/support" +) + +func TestKameletFromCustomRepository(t *testing.T) { + WithNewTestNamespace(t, func(ns string) { + operatorID := operatorID(ns) + installWithID(ns) + + kameletName := "timer-custom-source" + removeKamelet(kameletName, ns) + + Eventually(Kamelet(kameletName, ns)).Should(BeNil()) + + // Add the custom repository + Expect(Kamel("kamelet", "add-repo", "github:apache/camel-k/e2e/global/common/files/kamelets", "-n", ns, "-x", operatorID).Execute()).To(Succeed()) + + Expect(KamelRunWithID(operatorID, ns, "files/TimerCustomKameletIntegration.java").Execute()).To(Succeed()) + Eventually(IntegrationPodPhase(ns, "timer-custom-kamelet-integration"), TestTimeoutLong).Should(Equal(corev1.PodRunning)) + + Eventually(IntegrationLogs(ns, "timer-custom-kamelet-integration")).Should(ContainSubstring("hello world")) + + // Remove the custom repository + Expect(Kamel("kamelet", "remove-repo", "github:apache/camel-k/e2e/global/common/files/kamelets", "-n", ns, "-x", operatorID).Execute()).To(Succeed()) + }) +} + +func removeKamelet(name string, ns string) { + kamelet := Kamelet(name, ns)() + TestClient().Delete(TestContext, kamelet) +} diff --git a/pkg/cmd/kamelet.go b/pkg/cmd/kamelet.go index 7645fd0c2..8b1f48b96 100644 --- a/pkg/cmd/kamelet.go +++ b/pkg/cmd/kamelet.go @@ -31,6 +31,7 @@ func newCmdKamelet(rootCmdOptions *RootCmdOptions) *cobra.Command { cmd.AddCommand(cmdOnly(newKameletGetCmd(rootCmdOptions))) cmd.AddCommand(cmdOnly(newKameletDeleteCmd(rootCmdOptions))) cmd.AddCommand(cmdOnly(newKameletAddRepoCmd(rootCmdOptions))) + cmd.AddCommand(cmdOnly(newKameletRemoveRepoCmd(rootCmdOptions))) return &cmd } diff --git a/pkg/cmd/kamelet_add_repo.go b/pkg/cmd/kamelet_add_repo.go index 92591823d..f7f8b6d9f 100644 --- a/pkg/cmd/kamelet_add_repo.go +++ b/pkg/cmd/kamelet_add_repo.go @@ -23,6 +23,7 @@ import ( "regexp" v1 "github.com/apache/camel-k/pkg/apis/camel/v1" + platformutil "github.com/apache/camel-k/pkg/platform" "github.com/spf13/cobra" k8serrors "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/client" @@ -33,7 +34,9 @@ var kameletRepositoryURIRegexp = regexp.MustCompile(`^github:[^/]+/[^/]+((/[^/]+ func newKameletAddRepoCmd(rootCmdOptions *RootCmdOptions) (*cobra.Command, *kameletAddRepoCommandOptions) { options := kameletAddRepoCommandOptions{ - RootCmdOptions: rootCmdOptions, + kameletUpdateRepoCommandOptions: &kameletUpdateRepoCommandOptions{ + RootCmdOptions: rootCmdOptions, + }, } cmd := cobra.Command{ @@ -49,23 +52,24 @@ func newKameletAddRepoCmd(rootCmdOptions *RootCmdOptions) (*cobra.Command, *kame }, } - cmd.Flags().StringP("operator-id", "x", "camel-k", "Id of the Operator to update.") + cmd.Flags().StringP("operator-id", "x", "", "Id of the Operator to update. If not set, the active primary Integration Platform is updated.") return &cmd, &options } -type kameletAddRepoCommandOptions struct { +type kameletUpdateRepoCommandOptions struct { *RootCmdOptions OperatorID string `mapstructure:"operator-id" yaml:",omitempty"` } +type kameletAddRepoCommandOptions struct { + *kameletUpdateRepoCommandOptions +} + func (o *kameletAddRepoCommandOptions) validate(args []string) error { if len(args) == 0 { return errors.New("at least one Kamelet repository is expected") } - if o.OperatorID == "" { - return fmt.Errorf("cannot use empty operator id") - } return nil } @@ -74,6 +78,30 @@ func (o *kameletAddRepoCommandOptions) run(cmd *cobra.Command, args []string) er if err != nil { return err } + var platform *v1.IntegrationPlatform + if o.OperatorID == "" { + platform, err = o.findIntegrationPlatorm(cmd, c) + } else { + platform, err = o.getIntegrationPlatorm(cmd, c) + } + if err != nil { + return err + } else if platform == nil { + return nil + } + for _, uri := range args { + if err := checkURI(uri, platform.Spec.Kamelet.Repositories); err != nil { + return err + } + platform.Spec.Kamelet.Repositories = append(platform.Spec.Kamelet.Repositories, v1.IntegrationPlatformKameletRepositorySpec{ + URI: uri, + }) + } + return c.Update(o.Context, platform) +} + +// getIntegrationPlatorm gives the integration plaform matching with the operator id in the provided namespace. +func (o *kameletUpdateRepoCommandOptions) getIntegrationPlatorm(cmd *cobra.Command, c client.Client) (*v1.IntegrationPlatform, error) { key := client.ObjectKey{ Namespace: o.Namespace, Name: o.OperatorID, @@ -83,19 +111,27 @@ func (o *kameletAddRepoCommandOptions) run(cmd *cobra.Command, args []string) er if k8serrors.IsNotFound(err) { // IntegrationPlatform may be in the operator namespace, but we currently don't have a way to determine it: we just warn fmt.Fprintf(cmd.ErrOrStderr(), "Warning: IntegrationPlatform %q not found in namespace %q\n", key.Name, key.Namespace) - return nil + return nil, nil } - return err + return nil, err } - for _, uri := range args { - if err := checkURI(uri, platform.Spec.Kamelet.Repositories); err != nil { - return err + return &platform, nil +} + +// findIntegrationPlatorm gives the primary integration plaform that could be found in the provided namespace. +func (o *kameletUpdateRepoCommandOptions) findIntegrationPlatorm(cmd *cobra.Command, c client.Client) (*v1.IntegrationPlatform, error) { + platforms, err := platformutil.ListPrimaryPlatforms(o.Context, c, o.Namespace) + if err != nil { + return nil, err + } + for _, p := range platforms.Items { + p := p // pin + if platformutil.IsActive(&p) { + return &p, nil } - platform.Spec.Kamelet.Repositories = append(platform.Spec.Kamelet.Repositories, v1.IntegrationPlatformKameletRepositorySpec{ - URI: uri, - }) } - return c.Update(o.Context, &platform) + fmt.Fprintf(cmd.ErrOrStderr(), "Warning: No active primary IntegrationPlatform could be found in namespace %q\n", o.Namespace) + return nil, nil } func checkURI(uri string, repositories []v1.IntegrationPlatformKameletRepositorySpec) error { diff --git a/pkg/cmd/kamelet_remove_repo.go b/pkg/cmd/kamelet_remove_repo.go new file mode 100644 index 000000000..18703681f --- /dev/null +++ b/pkg/cmd/kamelet_remove_repo.go @@ -0,0 +1,98 @@ +/* +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 cmd + +import ( + "errors" + "fmt" + + v1 "github.com/apache/camel-k/pkg/apis/camel/v1" + "github.com/spf13/cobra" +) + +func newKameletRemoveRepoCmd(rootCmdOptions *RootCmdOptions) (*cobra.Command, *kameletRemoveRepoCommandOptions) { + options := kameletRemoveRepoCommandOptions{ + kameletUpdateRepoCommandOptions: &kameletUpdateRepoCommandOptions{ + RootCmdOptions: rootCmdOptions, + }, + } + + cmd := cobra.Command{ + Use: "remove-repo github:owner/repo[/path_to_kamelets_folder][@version] ...", + Short: "Remove a Kamelet repository", + Long: `Remove a Kamelet repository.`, + PreRunE: decode(&options), + RunE: func(cmd *cobra.Command, args []string) error { + if err := options.validate(args); err != nil { + return err + } + return options.run(cmd, args) + }, + } + + cmd.Flags().StringP("operator-id", "x", "", "Id of the Operator to update. If not set, the active primary Integration Platform is updated.") + + return &cmd, &options +} + +type kameletRemoveRepoCommandOptions struct { + *kameletUpdateRepoCommandOptions +} + +func (o *kameletRemoveRepoCommandOptions) validate(args []string) error { + if len(args) == 0 { + return errors.New("at least one Kamelet repository is expected") + } + return nil +} + +func (o *kameletRemoveRepoCommandOptions) run(cmd *cobra.Command, args []string) error { + c, err := o.GetCmdClient() + if err != nil { + return err + } + var platform *v1.IntegrationPlatform + if o.OperatorID == "" { + platform, err = o.findIntegrationPlatorm(cmd, c) + } else { + platform, err = o.getIntegrationPlatorm(cmd, c) + } + if err != nil { + return err + } else if platform == nil { + return nil + } + for _, uri := range args { + i, err := getURIIndex(uri, platform.Spec.Kamelet.Repositories) + if err != nil { + return err + } + platform.Spec.Kamelet.Repositories[i] = platform.Spec.Kamelet.Repositories[len(platform.Spec.Kamelet.Repositories)-1] + platform.Spec.Kamelet.Repositories = platform.Spec.Kamelet.Repositories[:len(platform.Spec.Kamelet.Repositories)-1] + } + return c.Update(o.Context, platform) +} + +func getURIIndex(uri string, repositories []v1.IntegrationPlatformKameletRepositorySpec) (int, error) { + for i, repo := range repositories { + if repo.URI == uri { + return i, nil + } + } + return 0, fmt.Errorf("non existing Kamelet repository uri %s", uri) +} diff --git a/pkg/cmd/kamelet_remove_repo_test.go b/pkg/cmd/kamelet_remove_repo_test.go new file mode 100644 index 000000000..0d77840c5 --- /dev/null +++ b/pkg/cmd/kamelet_remove_repo_test.go @@ -0,0 +1,86 @@ +/* +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 cmd + +import ( + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + + v1 "github.com/apache/camel-k/pkg/apis/camel/v1" + "github.com/apache/camel-k/pkg/util/test" +) + +const cmdKameletRemoveRepo = "remove-repo" + +// nolint: unparam +func initializeKameletRemoveRepoCmdOptions(t *testing.T) (*kameletRemoveRepoCommandOptions, *cobra.Command, RootCmdOptions) { + t.Helper() + + options, rootCmd := kamelTestPreAddCommandInit() + kameletRemoveRepoCommandOptions := addTestKameletRemoveRepoCmd(*options, rootCmd) + kamelTestPostAddCommandInit(t, rootCmd) + + return kameletRemoveRepoCommandOptions, rootCmd, *options +} + +func addTestKameletRemoveRepoCmd(options RootCmdOptions, rootCmd *cobra.Command) *kameletRemoveRepoCommandOptions { + // Add a testing version of kamelet remove-repo Command + kameletRemoveRepoCmd, kameletRemoveRepoOptions := newKameletRemoveRepoCmd(&options) + kameletRemoveRepoCmd.RunE = func(c *cobra.Command, args []string) error { + return nil + } + kameletRemoveRepoCmd.PostRunE = func(c *cobra.Command, args []string) error { + return nil + } + kameletRemoveRepoCmd.Args = test.ArbitraryArgs + rootCmd.AddCommand(kameletRemoveRepoCmd) + return kameletRemoveRepoOptions +} + +func TestKameletRemoveRepoNoFlag(t *testing.T) { + _, rootCmd, _ := initializeKameletRemoveRepoCmdOptions(t) + _, err := test.ExecuteCommand(rootCmd, cmdKameletRemoveRepo, "foo") + assert.Nil(t, err) +} + +func TestKameletRemoveRepoNonExistingFlag(t *testing.T) { + _, rootCmd, _ := initializeKameletRemoveRepoCmdOptions(t) + _, err := test.ExecuteCommand(rootCmd, cmdKameletRemoveRepo, "--nonExistingFlag", "foo") + assert.NotNil(t, err) +} + +func TestKameletRemoveRepoURINotFoundEmpty(t *testing.T) { + repositories := []v1.IntegrationPlatformKameletRepositorySpec{} + _, err := getURIIndex("foo", repositories) + assert.NotNil(t, err) +} + +func TestKameletRemoveRepoURINotFoundNotEmpty(t *testing.T) { + repositories := []v1.IntegrationPlatformKameletRepositorySpec{{URI: "github:foo/bar"}} + _, err := getURIIndex("foo", repositories) + assert.NotNil(t, err) +} + +func TestKameletRemoveRepoURIFound(t *testing.T) { + repositories := []v1.IntegrationPlatformKameletRepositorySpec{{URI: "github:foo/bar1"}, {URI: "github:foo/bar2"}, {URI: "github:foo/bar3"}} + i, err := getURIIndex("github:foo/bar2", repositories) + assert.Nil(t, err) + assert.Equal(t, 1, i) +}