diff --git a/cmd/config/config.go b/cmd/config/config.go index 5440865a0..e40be2b9b 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -51,5 +51,8 @@ func NewConfigCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Comma configRootCmd.AddCommand(NewInitCommand(rootSettings)) configRootCmd.AddCommand(NewUseContextCommand(rootSettings)) + configRootCmd.AddCommand(NewGetManifestCommand(rootSettings)) + configRootCmd.AddCommand(NewSetManifestCommand(rootSettings)) + return configRootCmd } diff --git a/cmd/config/get_manifest.go b/cmd/config/get_manifest.go new file mode 100644 index 000000000..a319e224c --- /dev/null +++ b/cmd/config/get_manifest.go @@ -0,0 +1,76 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://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 config + +import ( + "fmt" + + "github.com/spf13/cobra" + + "opendev.org/airship/airshipctl/pkg/config" + "opendev.org/airship/airshipctl/pkg/environment" +) + +const ( + getManifestsLong = ` +Display a specific manifest information, or all defined manifests if no name is provided. +` + + getManifestsExample = ` +# List all the manifests airshipctl knows about +airshipctl config get-manifests + +# Display a specific manifest +airshipctl config get-manifest e2e +` +) + +// NewGetManifestCommand creates a command for viewing the manifest information +// defined in the airshipctl config file. +func NewGetManifestCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Command { + o := &config.ManifestOptions{} + cmd := &cobra.Command{ + Use: "get-manifest NAME", + Short: "Get a manifest information from the airshipctl config", + Long: getManifestsLong[1:], + Example: getManifestsExample, + Args: cobra.MaximumNArgs(1), + Aliases: []string{"get-manifests"}, + RunE: func(cmd *cobra.Command, args []string) error { + airconfig := rootSettings.Config + if len(args) == 1 { + o.Name = args[0] + manifest, exists := airconfig.Manifests[o.Name] + if !exists { + return config.ErrMissingConfig{ + What: fmt.Sprintf("Manifest with name '%s'", o.Name), + } + } + fmt.Fprintln(cmd.OutOrStdout(), manifest) + } else { + manifests := airconfig.GetManifests() + if len(manifests) == 0 { + fmt.Fprintln(cmd.OutOrStdout(), "No Manifest found in the configuration.") + } + for _, manifest := range manifests { + fmt.Fprintln(cmd.OutOrStdout(), manifest) + } + } + return nil + }, + } + + return cmd +} diff --git a/cmd/config/get_manifest_test.go b/cmd/config/get_manifest_test.go new file mode 100644 index 000000000..8f16d4114 --- /dev/null +++ b/cmd/config/get_manifest_test.go @@ -0,0 +1,75 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://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 config_test + +import ( + "fmt" + "testing" + + cmd "opendev.org/airship/airshipctl/cmd/config" + "opendev.org/airship/airshipctl/pkg/config" + "opendev.org/airship/airshipctl/pkg/environment" + "opendev.org/airship/airshipctl/testutil" +) + +func TestGetManifestConfigCmd(t *testing.T) { + settings := &environment.AirshipCTLSettings{ + Config: &config.Config{ + Manifests: map[string]*config.Manifest{ + "fooManifestConfig": getTestManifest("foo"), + "barManifestConfig": getTestManifest("bar"), + "bazManifestConfig": getTestManifest("baz"), + }, + }, + } + + noConfigSettings := &environment.AirshipCTLSettings{Config: new(config.Config)} + + cmdTests := []*testutil.CmdTest{ + { + Name: "get-manifest", + CmdLine: "fooManifestConfig", + Cmd: cmd.NewGetManifestCommand(settings), + }, + { + Name: "get-all-manifests", + CmdLine: "", + Cmd: cmd.NewGetManifestCommand(settings), + }, + { + Name: "missing", + CmdLine: "manifestMissing", + Cmd: cmd.NewGetManifestCommand(settings), + Error: fmt.Errorf("Missing configuration: Manifest with name 'manifestMissing'"), + }, + { + Name: "no-manifests", + CmdLine: "", + Cmd: cmd.NewGetManifestCommand(noConfigSettings), + }, + } + + for _, tt := range cmdTests { + testutil.RunTest(t, tt) + } +} + +func getTestManifest(name string) *config.Manifest { + manifests := &config.Manifest{ + PrimaryRepositoryName: name + "_primary_repo", + TargetPath: name + "_target_path", + } + return manifests +} diff --git a/cmd/config/set_manifest.go b/cmd/config/set_manifest.go new file mode 100644 index 000000000..eadcd7638 --- /dev/null +++ b/cmd/config/set_manifest.go @@ -0,0 +1,148 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +import ( + "fmt" + + "github.com/spf13/cobra" + + "opendev.org/airship/airshipctl/pkg/config" + "opendev.org/airship/airshipctl/pkg/environment" + "opendev.org/airship/airshipctl/pkg/log" +) + +const ( + setManifestsLong = ` +Create or modify a manifests in the airshipctl config file. +` + + setManifestsExample = ` +# Create a new manifest +airshipctl config set-manifest exampleManifest \ + --repo exampleRepo \ + --url https://github.com/site \ + --branch master \ + --primary \ + --sub-path exampleSubpath \ + --target-path exampleTargetpath + +# Change the primary repo for manifest +airshipctl config set-manifest e2e \ + --repo exampleRepo \ + --primary + +# Change the sub-path for manifest +airshipctl config set-manifest e2e \ + --sub-path treasuremap/manifests/e2e + +# Change the target-path for manifest +airshipctl config set-manifest e2e \ + --target-path /tmp/e2e +` +) + +// NewSetManifestCommand creates a command for creating and modifying manifests +// in the airshipctl config file. +func NewSetManifestCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Command { + o := &config.ManifestOptions{} + cmd := &cobra.Command{ + Use: "set-manifest NAME", + Short: "Manage manifests in airship config", + Long: setManifestsLong[1:], + Example: setManifestsExample, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + o.Name = args[0] + modified, err := config.RunSetManifest(o, rootSettings.Config, true) + // Check if URL flag is passed with empty value + if cmd.Flags().Changed("url") && o.URL == "" { + log.Fatal("Repository URL cannot be empty.") + } + if err != nil { + return err + } + if modified { + fmt.Fprintf(cmd.OutOrStdout(), "Manifest %q modified.\n", o.Name) + } else { + fmt.Fprintf(cmd.OutOrStdout(), "Manifest %q created.\n", o.Name) + } + return nil + }, + } + + addSetManifestFlags(o, cmd) + return cmd +} + +func addSetManifestFlags(o *config.ManifestOptions, cmd *cobra.Command) { + flags := cmd.Flags() + + flags.StringVar( + &o.RepoName, + "repo", + "", + "the name of the repository to be associated with this manifest") + + flags.StringVar( + &o.URL, + "url", + "", + "the repository url to be associated with this manifest") + + flags.StringVar( + &o.Branch, + "branch", + "", + "the branch to be associated with repository in this manifest") + + flags.StringVar( + &o.CommitHash, + "commithash", + "", + "the commit hash to be associated with repository in this manifest") + + flags.StringVar( + &o.Tag, + "tag", + "", + "the tag to be associated with repository in this manifest") + + flags.BoolVar( + &o.Force, + "force", + false, + "if set, enable force checkout in repository with this manifest") + + flags.BoolVar( + &o.IsPrimary, + "primary", + false, + "if set, enable this repository as primary repository to be used with this manifest") + + flags.StringVar( + &o.SubPath, + "sub-path", + "", + "the sub path to be set for this manifest") + + flags.StringVar( + &o.TargetPath, + "target-path", + "", + "the target path for to be set for this manifest") +} diff --git a/cmd/config/set_manifest_test.go b/cmd/config/set_manifest_test.go new file mode 100644 index 000000000..681ed924d --- /dev/null +++ b/cmd/config/set_manifest_test.go @@ -0,0 +1,137 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://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 config_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + cmd "opendev.org/airship/airshipctl/cmd/config" + "opendev.org/airship/airshipctl/pkg/config" + "opendev.org/airship/airshipctl/pkg/environment" + "opendev.org/airship/airshipctl/testutil" +) + +const ( + mName = "dummymanifest" + mRepoName = "treasuremap" + mURL = "https://github.com/airshipit/treasuremap" + mBranch = "master" + mSubPath = "manifests/test-site" + mTargetPath = "/tmp/airship" + + testTargetPath = "/tmp/e2e" +) + +type setManifestTest struct { + inputConfig *config.Config + cmdTest *testutil.CmdTest + manifestName string + manifestTargetPath string +} + +func TestConfigSetManifest(t *testing.T) { + cmdTests := []*testutil.CmdTest{ + { + Name: "config-cmd-set-manifest-with-help", + CmdLine: "--help", + Cmd: cmd.NewSetManifestCommand(nil), + }, + { + Name: "config-cmd-set-manifest-too-many-args", + CmdLine: "arg1 arg2", + Cmd: cmd.NewSetManifestCommand(nil), + Error: fmt.Errorf("accepts %d arg(s), received %d", 1, 2), + }, + { + Name: "config-cmd-set-manifest-too-few-args", + CmdLine: "", + Cmd: cmd.NewSetManifestCommand(nil), + Error: fmt.Errorf("accepts %d arg(s), received %d", 1, 0), + }, + } + + for _, tt := range cmdTests { + testutil.RunTest(t, tt) + } +} + +func TestSetManifest(t *testing.T) { + given, cleanupGiven := testutil.InitConfig(t) + defer cleanupGiven(t) + + tests := []struct { + testName string + manifestName string + flags []string + givenConfig *config.Config + targetPath string + }{ + { + testName: "set-manifest", + manifestName: mName, + flags: []string{ + "--repo " + mRepoName, + "--url " + mURL, + "--branch " + mBranch, + "--primary", + "--sub-path " + mSubPath, + "--target-path " + mTargetPath, + }, + givenConfig: given, + targetPath: mTargetPath, + }, + { + testName: "modify-manifest", + manifestName: mName, + flags: []string{ + "--target-path " + testTargetPath, + }, + givenConfig: given, + targetPath: testTargetPath, + }, + } + + for _, tt := range tests { + tt := tt + cmd := &testutil.CmdTest{ + Name: tt.testName, + CmdLine: fmt.Sprintf("%s %s", tt.manifestName, strings.Join(tt.flags, " ")), + } + test := setManifestTest{ + inputConfig: tt.givenConfig, + cmdTest: cmd, + manifestName: tt.manifestName, + manifestTargetPath: tt.targetPath, + } + test.run(t) + } +} + +func (test setManifestTest) run(t *testing.T) { + settings := &environment.AirshipCTLSettings{Config: test.inputConfig} + test.cmdTest.Cmd = cmd.NewSetManifestCommand(settings) + testutil.RunTest(t, test.cmdTest) + + afterRunConf := settings.Config + // Find the Manifest Created or Modified + afterRunManifest, _ := afterRunConf.Manifests[test.manifestName] + require.NotNil(t, afterRunManifest) + assert.EqualValues(t, afterRunManifest.TargetPath, test.manifestTargetPath) +} diff --git a/cmd/config/testdata/TestConfigGoldenOutput/config-cmd-with-help.golden b/cmd/config/testdata/TestConfigGoldenOutput/config-cmd-with-help.golden index 6650a6c9b..e34cfc8dc 100644 --- a/cmd/config/testdata/TestConfigGoldenOutput/config-cmd-with-help.golden +++ b/cmd/config/testdata/TestConfigGoldenOutput/config-cmd-with-help.golden @@ -8,6 +8,7 @@ Available Commands: get-context Get context information from the airshipctl config get-credential Get user credentials from the airshipctl config get-management-config View a management config or all management configs defined in the airshipctl config + get-manifest Get a manifest information from the airshipctl config help Help about any command import Merge information from a kubernetes config file init Generate initial configuration files for airshipctl @@ -15,6 +16,7 @@ Available Commands: set-context Manage contexts set-credentials Manage user credentials set-management-config Modify an out-of-band management configuration + set-manifest Manage manifests in airship config use-context Switch to a different context Flags: diff --git a/cmd/config/testdata/TestConfigSetManifestGoldenOutput/config-cmd-set-manifest-too-few-args.golden b/cmd/config/testdata/TestConfigSetManifestGoldenOutput/config-cmd-set-manifest-too-few-args.golden new file mode 100644 index 000000000..d50687210 --- /dev/null +++ b/cmd/config/testdata/TestConfigSetManifestGoldenOutput/config-cmd-set-manifest-too-few-args.golden @@ -0,0 +1,41 @@ +Error: accepts 1 arg(s), received 0 +Usage: + set-manifest NAME [flags] + +Examples: + +# Create a new manifest +airshipctl config set-manifest exampleManifest \ + --repo exampleRepo \ + --url https://github.com/site \ + --branch master \ + --primary \ + --sub-path exampleSubpath \ + --target-path exampleTargetpath + +# Change the primary repo for manifest +airshipctl config set-manifest e2e \ + --repo exampleRepo \ + --primary + +# Change the sub-path for manifest +airshipctl config set-manifest e2e \ + --sub-path treasuremap/manifests/e2e + +# Change the target-path for manifest +airshipctl config set-manifest e2e \ + --target-path /tmp/e2e + + +Flags: + --branch string the branch to be associated with repository in this manifest + --commithash string the commit hash to be associated with repository in this manifest + --force if set, enable force checkout in repository with this manifest + -h, --help help for set-manifest + --primary if set, enable this repository as primary repository to be used with this manifest + --repo string the name of the repository to be associated with this manifest + --sub-path string the sub path to be set for this manifest + --tag string the tag to be associated with repository in this manifest + --target-path string the target path for to be set for this manifest + --url string the repository url to be associated with this manifest + diff --git a/cmd/config/testdata/TestConfigSetManifestGoldenOutput/config-cmd-set-manifest-too-many-args.golden b/cmd/config/testdata/TestConfigSetManifestGoldenOutput/config-cmd-set-manifest-too-many-args.golden new file mode 100644 index 000000000..5f91c08bf --- /dev/null +++ b/cmd/config/testdata/TestConfigSetManifestGoldenOutput/config-cmd-set-manifest-too-many-args.golden @@ -0,0 +1,41 @@ +Error: accepts 1 arg(s), received 2 +Usage: + set-manifest NAME [flags] + +Examples: + +# Create a new manifest +airshipctl config set-manifest exampleManifest \ + --repo exampleRepo \ + --url https://github.com/site \ + --branch master \ + --primary \ + --sub-path exampleSubpath \ + --target-path exampleTargetpath + +# Change the primary repo for manifest +airshipctl config set-manifest e2e \ + --repo exampleRepo \ + --primary + +# Change the sub-path for manifest +airshipctl config set-manifest e2e \ + --sub-path treasuremap/manifests/e2e + +# Change the target-path for manifest +airshipctl config set-manifest e2e \ + --target-path /tmp/e2e + + +Flags: + --branch string the branch to be associated with repository in this manifest + --commithash string the commit hash to be associated with repository in this manifest + --force if set, enable force checkout in repository with this manifest + -h, --help help for set-manifest + --primary if set, enable this repository as primary repository to be used with this manifest + --repo string the name of the repository to be associated with this manifest + --sub-path string the sub path to be set for this manifest + --tag string the tag to be associated with repository in this manifest + --target-path string the target path for to be set for this manifest + --url string the repository url to be associated with this manifest + diff --git a/cmd/config/testdata/TestConfigSetManifestGoldenOutput/config-cmd-set-manifest-with-help.golden b/cmd/config/testdata/TestConfigSetManifestGoldenOutput/config-cmd-set-manifest-with-help.golden new file mode 100644 index 000000000..579fc8e7f --- /dev/null +++ b/cmd/config/testdata/TestConfigSetManifestGoldenOutput/config-cmd-set-manifest-with-help.golden @@ -0,0 +1,41 @@ +Create or modify a manifests in the airshipctl config file. + +Usage: + set-manifest NAME [flags] + +Examples: + +# Create a new manifest +airshipctl config set-manifest exampleManifest \ + --repo exampleRepo \ + --url https://github.com/site \ + --branch master \ + --primary \ + --sub-path exampleSubpath \ + --target-path exampleTargetpath + +# Change the primary repo for manifest +airshipctl config set-manifest e2e \ + --repo exampleRepo \ + --primary + +# Change the sub-path for manifest +airshipctl config set-manifest e2e \ + --sub-path treasuremap/manifests/e2e + +# Change the target-path for manifest +airshipctl config set-manifest e2e \ + --target-path /tmp/e2e + + +Flags: + --branch string the branch to be associated with repository in this manifest + --commithash string the commit hash to be associated with repository in this manifest + --force if set, enable force checkout in repository with this manifest + -h, --help help for set-manifest + --primary if set, enable this repository as primary repository to be used with this manifest + --repo string the name of the repository to be associated with this manifest + --sub-path string the sub path to be set for this manifest + --tag string the tag to be associated with repository in this manifest + --target-path string the target path for to be set for this manifest + --url string the repository url to be associated with this manifest diff --git a/cmd/config/testdata/TestGetManifestConfigCmdGoldenOutput/get-all-manifests.golden b/cmd/config/testdata/TestGetManifestConfigCmdGoldenOutput/get-all-manifests.golden new file mode 100644 index 000000000..44b7858fe --- /dev/null +++ b/cmd/config/testdata/TestGetManifestConfigCmdGoldenOutput/get-all-manifests.golden @@ -0,0 +1,12 @@ +primaryRepositoryName: bar_primary_repo +subPath: "" +targetPath: bar_target_path + +primaryRepositoryName: baz_primary_repo +subPath: "" +targetPath: baz_target_path + +primaryRepositoryName: foo_primary_repo +subPath: "" +targetPath: foo_target_path + diff --git a/cmd/config/testdata/TestGetManifestConfigCmdGoldenOutput/get-manifest.golden b/cmd/config/testdata/TestGetManifestConfigCmdGoldenOutput/get-manifest.golden new file mode 100644 index 000000000..c85391380 --- /dev/null +++ b/cmd/config/testdata/TestGetManifestConfigCmdGoldenOutput/get-manifest.golden @@ -0,0 +1,4 @@ +primaryRepositoryName: foo_primary_repo +subPath: "" +targetPath: foo_target_path + diff --git a/cmd/config/testdata/TestGetManifestConfigCmdGoldenOutput/missing.golden b/cmd/config/testdata/TestGetManifestConfigCmdGoldenOutput/missing.golden new file mode 100644 index 000000000..e549db67e --- /dev/null +++ b/cmd/config/testdata/TestGetManifestConfigCmdGoldenOutput/missing.golden @@ -0,0 +1,19 @@ +Error: Missing configuration: Manifest with name 'manifestMissing' +Usage: + get-manifest NAME [flags] + +Aliases: + get-manifest, get-manifests + +Examples: + +# List all the manifests airshipctl knows about +airshipctl config get-manifests + +# Display a specific manifest +airshipctl config get-manifest e2e + + +Flags: + -h, --help help for get-manifest + diff --git a/cmd/config/testdata/TestGetManifestConfigCmdGoldenOutput/no-manifests.golden b/cmd/config/testdata/TestGetManifestConfigCmdGoldenOutput/no-manifests.golden new file mode 100644 index 000000000..84a3bf944 --- /dev/null +++ b/cmd/config/testdata/TestGetManifestConfigCmdGoldenOutput/no-manifests.golden @@ -0,0 +1 @@ +No Manifest found in the configuration. diff --git a/cmd/config/testdata/TestSetManifestGoldenOutput/modify-manifest.golden b/cmd/config/testdata/TestSetManifestGoldenOutput/modify-manifest.golden new file mode 100644 index 000000000..b1ffce0fc --- /dev/null +++ b/cmd/config/testdata/TestSetManifestGoldenOutput/modify-manifest.golden @@ -0,0 +1 @@ +Manifest "dummymanifest" modified. diff --git a/cmd/config/testdata/TestSetManifestGoldenOutput/set-manifest.golden b/cmd/config/testdata/TestSetManifestGoldenOutput/set-manifest.golden new file mode 100644 index 000000000..60995fed8 --- /dev/null +++ b/cmd/config/testdata/TestSetManifestGoldenOutput/set-manifest.golden @@ -0,0 +1 @@ +Manifest "dummymanifest" created. diff --git a/docs/source/cli/airshipctl_config.md b/docs/source/cli/airshipctl_config.md index af184c4ff..55df3b436 100644 --- a/docs/source/cli/airshipctl_config.md +++ b/docs/source/cli/airshipctl_config.md @@ -27,11 +27,13 @@ Manage the airshipctl config file * [airshipctl config get-context](airshipctl_config_get-context.md) - Get context information from the airshipctl config * [airshipctl config get-credential](airshipctl_config_get-credential.md) - Get user credentials from the airshipctl config * [airshipctl config get-management-config](airshipctl_config_get-management-config.md) - View a management config or all management configs defined in the airshipctl config +* [airshipctl config get-manifest](airshipctl_config_get-manifest.md) - Get a manifest information from the airshipctl config * [airshipctl config import](airshipctl_config_import.md) - Merge information from a kubernetes config file * [airshipctl config init](airshipctl_config_init.md) - Generate initial configuration files for airshipctl * [airshipctl config set-cluster](airshipctl_config_set-cluster.md) - Manage clusters * [airshipctl config set-context](airshipctl_config_set-context.md) - Manage contexts * [airshipctl config set-credentials](airshipctl_config_set-credentials.md) - Manage user credentials * [airshipctl config set-management-config](airshipctl_config_set-management-config.md) - Modify an out-of-band management configuration +* [airshipctl config set-manifest](airshipctl_config_set-manifest.md) - Manage manifests in airship config * [airshipctl config use-context](airshipctl_config_use-context.md) - Switch to a different context diff --git a/docs/source/cli/airshipctl_config_get-manifest.md b/docs/source/cli/airshipctl_config_get-manifest.md new file mode 100644 index 000000000..e0590d9a2 --- /dev/null +++ b/docs/source/cli/airshipctl_config_get-manifest.md @@ -0,0 +1,43 @@ +## airshipctl config get-manifest + +Get a manifest information from the airshipctl config + +### Synopsis + +Display a specific manifest information, or all defined manifests if no name is provided. + + +``` +airshipctl config get-manifest NAME [flags] +``` + +### Examples + +``` + +# List all the manifests airshipctl knows about +airshipctl config get-manifests + +# Display a specific manifest +airshipctl config get-manifest e2e + +``` + +### Options + +``` + -h, --help help for get-manifest +``` + +### Options inherited from parent commands + +``` + --airshipconf string Path to file for airshipctl configuration. (default "$HOME/.airship/config") + --debug enable verbose output + --kubeconfig string Path to kubeconfig associated with airshipctl configuration. (default "$HOME/.airship/kubeconfig") +``` + +### SEE ALSO + +* [airshipctl config](airshipctl_config.md) - Manage the airshipctl config file + diff --git a/docs/source/cli/airshipctl_config_set-manifest.md b/docs/source/cli/airshipctl_config_set-manifest.md new file mode 100644 index 000000000..21be6899f --- /dev/null +++ b/docs/source/cli/airshipctl_config_set-manifest.md @@ -0,0 +1,68 @@ +## airshipctl config set-manifest + +Manage manifests in airship config + +### Synopsis + +Create or modify a manifests in the airshipctl config file. + + +``` +airshipctl config set-manifest NAME [flags] +``` + +### Examples + +``` + +# Create a new manifest +airshipctl config set-manifest exampleManifest \ + --repo exampleRepo \ + --url https://github.com/site \ + --branch master \ + --primary \ + --sub-path exampleSubpath \ + --target-path exampleTargetpath + +# Change the primary repo for manifest +airshipctl config set-manifest e2e \ + --repo exampleRepo \ + --primary + +# Change the sub-path for manifest +airshipctl config set-manifest e2e \ + --sub-path treasuremap/manifests/e2e + +# Change the target-path for manifest +airshipctl config set-manifest e2e \ + --target-path /tmp/e2e + +``` + +### Options + +``` + --branch string the branch to be associated with repository in this manifest + --commithash string the commit hash to be associated with repository in this manifest + --force if set, enable force checkout in repository with this manifest + -h, --help help for set-manifest + --primary if set, enable this repository as primary repository to be used with this manifest + --repo string the name of the repository to be associated with this manifest + --sub-path string the sub path to be set for this manifest + --tag string the tag to be associated with repository in this manifest + --target-path string the target path for to be set for this manifest + --url string the repository url to be associated with this manifest +``` + +### Options inherited from parent commands + +``` + --airshipconf string Path to file for airshipctl configuration. (default "$HOME/.airship/config") + --debug enable verbose output + --kubeconfig string Path to kubeconfig associated with airshipctl configuration. (default "$HOME/.airship/kubeconfig") +``` + +### SEE ALSO + +* [airshipctl config](airshipctl_config.md) - Manage the airshipctl config file + diff --git a/pkg/config/config.go b/pkg/config/config.go index da8c32138..ad3936c0e 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -947,6 +947,112 @@ func (c *Config) CurrentContextBootstrapInfo() (*Bootstrap, error) { return bootstrap, nil } +// GetManifests returns all of the Manifests associated with the Config sorted by name +func (c *Config) GetManifests() []*Manifest { + keys := make([]string, 0, len(c.Manifests)) + for name := range c.Manifests { + keys = append(keys, name) + } + sort.Strings(keys) + + manifests := make([]*Manifest, 0, len(c.Manifests)) + for _, name := range keys { + manifests = append(manifests, c.Manifests[name]) + } + return manifests +} + +// AddManifest creates new Manifest +func (c *Config) AddManifest(theManifest *ManifestOptions) *Manifest { + nManifest := NewManifest() + c.Manifests[theManifest.Name] = nManifest + err := c.ModifyManifest(nManifest, theManifest) + if err != nil { + return nil + } + return nManifest +} + +// ModifyManifest set actual values to manifests +func (c *Config) ModifyManifest(manifest *Manifest, theManifest *ManifestOptions) error { + if theManifest.IsPrimary { + manifest.PrimaryRepositoryName = theManifest.RepoName + } + if theManifest.SubPath != "" { + manifest.SubPath = theManifest.SubPath + } + if theManifest.TargetPath != "" { + manifest.TargetPath = theManifest.TargetPath + } + // There is no repository details to be updated + if theManifest.RepoName == "" { + return nil + } + //when setting an existing repository as primary, verify whether the repository exists + //and user is also not passing any repository URL + if theManifest.IsPrimary && theManifest.URL == "" && (manifest.Repositories[theManifest.RepoName] == nil) { + return ErrRepositoryNotFound{theManifest.RepoName} + } + repository, exists := manifest.Repositories[theManifest.RepoName] + if !exists { + _, err := c.AddRepository(manifest, theManifest) + if err != nil { + return err + } + } else { + err := c.ModifyRepository(repository, theManifest) + if err != nil { + return err + } + } + return nil +} + +// AddRepository creates new Repository +func (c *Config) AddRepository(manifest *Manifest, theManifest *ManifestOptions) (*Repository, error) { + nRepository := NewRepository() + manifest.Repositories[theManifest.RepoName] = nRepository + err := c.ModifyRepository(nRepository, theManifest) + if err != nil { + return nil, err + } + return nRepository, nil +} + +// ModifyRepository set actual values to repository +func (c *Config) ModifyRepository(repository *Repository, theManifest *ManifestOptions) error { + if theManifest.URL != "" { + repository.URLString = theManifest.URL + } + if theManifest.Branch != "" { + repository.CheckoutOptions.Branch = theManifest.Branch + } + if theManifest.CommitHash != "" { + repository.CheckoutOptions.CommitHash = theManifest.CommitHash + } + if theManifest.Tag != "" { + repository.CheckoutOptions.Tag = theManifest.Tag + } + if theManifest.Force { + repository.CheckoutOptions.ForceCheckout = theManifest.Force + } + possibleValues := [3]string{repository.CheckoutOptions.CommitHash, + repository.CheckoutOptions.Branch, repository.CheckoutOptions.Tag} + var count int + for _, val := range possibleValues { + if val != "" { + count++ + } + } + if count > 1 { + return ErrMutuallyExclusiveCheckout{} + } + if count == 0 { + return ErrMissingRepoCheckoutOptions{} + } + return nil +} + // CurrentContextManagementConfig returns the management options for the current context func (c *Config) CurrentContextManagementConfig() (*ManagementConfiguration, error) { currentCluster, err := c.CurrentContextCluster() diff --git a/pkg/config/config_helper.go b/pkg/config/config_helper.go index 57c4024e0..18fe1b4e2 100644 --- a/pkg/config/config_helper.go +++ b/pkg/config/config_helper.go @@ -165,3 +165,35 @@ func RunUseContext(desiredContext string, airconfig *Config) error { } return nil } + +// RunSetManifest validates the given command line options and invokes AddManifest/ModifyManifest +func RunSetManifest(o *ManifestOptions, airconfig *Config, writeToStorage bool) (bool, error) { + modified := false + err := o.Validate() + if err != nil { + return modified, err + } + + manifest, exists := airconfig.Manifests[o.Name] + if !exists { + // manifest didn't exist, create it + // ignoring the returned added manifest + airconfig.AddManifest(o) + } else { + // manifest exists, lets update + err = airconfig.ModifyManifest(manifest, o) + if err != nil { + return modified, err + } + modified = true + } + // Update configuration file just in time persistence approach + if writeToStorage { + if err := airconfig.PersistConfig(); err != nil { + // Error that it didnt persist the changes + return modified, ErrConfigFailed{} + } + } + + return modified, nil +} diff --git a/pkg/config/config_helper_test.go b/pkg/config/config_helper_test.go index 1aeda3768..46e521a1f 100644 --- a/pkg/config/config_helper_test.go +++ b/pkg/config/config_helper_test.go @@ -117,3 +117,26 @@ func TestRunUseContext(t *testing.T) { assert.Error(t, err) }) } + +func TestRunSetManifest(t *testing.T) { + t.Run("testAddManifest", func(t *testing.T) { + conf := testutil.DummyConfig() + dummyManifestOptions := testutil.DummyManifestOptions() + dummyManifestOptions.Name = "test_manifest" + + modified, err := config.RunSetManifest(dummyManifestOptions, conf, false) + assert.NoError(t, err) + assert.False(t, modified) + }) + + t.Run("testModifyManifest", func(t *testing.T) { + conf := testutil.DummyConfig() + dummyManifestOptions := testutil.DummyManifestOptions() + dummyManifestOptions.TargetPath = "/tmp/default" + + modified, err := config.RunSetManifest(dummyManifestOptions, conf, false) + assert.NoError(t, err) + assert.True(t, modified) + assert.Equal(t, "/tmp/default", conf.Manifests["dummy_manifest"].TargetPath) + }) +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 91632ec5e..af919bc12 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -748,3 +748,13 @@ func TestManagementConfigurationByNameDoesNotExist(t *testing.T) { _, err := conf.GetManagementConfiguration(fmt.Sprintf("%s-test", config.AirshipDefaultContext)) assert.Error(t, err) } + +func TestGetManifests(t *testing.T) { + conf, cleanup := testutil.InitConfig(t) + defer cleanup(t) + + manifests := conf.GetManifests() + require.NotNil(t, manifests) + + assert.EqualValues(t, manifests[0].PrimaryRepositoryName, "primary") +} diff --git a/pkg/config/constants.go b/pkg/config/constants.go index 5c8ae17ba..1fee03085 100644 --- a/pkg/config/constants.go +++ b/pkg/config/constants.go @@ -65,3 +65,13 @@ const ( DefaultSystemActionRetries = 30 DefaultSystemRebootDelay = 30 ) + +// Default Value for manifest +const ( + // DefaultTestPrimaryRepo holds default repo name + DefaultTestPrimaryRepo = "primary" + // DefaultTargetPath holds default target path + DefaultTargetPath = "/tmp/default" + // DefaultSubPath holds default sub path + DefaultSubPath = "manifest/default" +) diff --git a/pkg/config/errors.go b/pkg/config/errors.go index 1abddf00d..c516fda88 100644 --- a/pkg/config/errors.go +++ b/pkg/config/errors.go @@ -70,6 +70,43 @@ func (e ErrMutuallyExclusiveCheckout) Error() string { return "Checkout mutually exclusive, use either: commit-hash, branch or tag." } +// ErrRepositoryNotFound is returned if repository is empty +// when using in set-manifest +type ErrRepositoryNotFound struct { + Name string +} + +func (e ErrRepositoryNotFound) Error() string { + return fmt.Sprintf("Repository %q not found.", e.Name) +} + +// ErrMissingRepositoryName is returned if repository name is empty +// when using in set-manifest +type ErrMissingRepositoryName struct { +} + +func (e ErrMissingRepositoryName) Error() string { + return "Missing repository name." +} + +// ErrMissingRepoURL is returned if repository is empty +// when using --primary in set-manifest +type ErrMissingRepoURL struct { +} + +func (e ErrMissingRepoURL) Error() string { + return "A valid URL should be specified." +} + +// ErrMissingRepoCheckoutOptions is returned if repository checkout +// options is empty in set-manifest +type ErrMissingRepoCheckoutOptions struct { +} + +func (e ErrMissingRepoCheckoutOptions) Error() string { + return "Missing repository checkout options." +} + // ErrBootstrapInfoNotFound returned if bootstrap // information is not found for cluster type ErrBootstrapInfoNotFound struct { diff --git a/pkg/config/options.go b/pkg/config/options.go index 63f341a10..97c8e8245 100644 --- a/pkg/config/options.go +++ b/pkg/config/options.go @@ -19,6 +19,8 @@ package config import ( "fmt" "os" + + "opendev.org/airship/airshipctl/pkg/errors" ) // AuthInfoOptions holds all configurable options for @@ -55,6 +57,21 @@ type ClusterOptions struct { EmbedCAData bool } +// ManifestOptions holds all configurable options for manifest configuration +type ManifestOptions struct { + Name string + RepoName string + URL string + Branch string + CommitHash string + Tag string + RemoteRef string + Force bool + IsPrimary bool + SubPath string + TargetPath string +} + // TODO(howell): The following functions are tightly coupled with flags passed // on the command line. We should find a way to remove this coupling, since it // is possible to create (and validate) these objects without using the command @@ -152,3 +169,28 @@ func checkExists(flagName, path string) error { } return nil } + +// Validate checks for the possible manifest option values and returns +// Error when invalid value or incompatible choice of values given +func (o *ManifestOptions) Validate() error { + if o.Name == "" { + return fmt.Errorf("you must specify a non-empty Manifest name") + } + if o.RemoteRef != "" { + return fmt.Errorf("Repository checkout by RemoteRef is not yet implemented\n%w", errors.ErrNotImplemented{}) + } + if o.IsPrimary && o.RepoName == "" { + return ErrMissingRepositoryName{} + } + possibleValues := [3]string{o.CommitHash, o.Branch, o.Tag} + var count int + for _, val := range possibleValues { + if val != "" { + count++ + } + } + if count > 1 { + return ErrMutuallyExclusiveCheckout{} + } + return nil +} diff --git a/pkg/config/utils.go b/pkg/config/utils.go index 288e10957..104824f38 100644 --- a/pkg/config/utils.go +++ b/pkg/config/utils.go @@ -20,11 +20,6 @@ import ( "encoding/base64" ) -const ( - // DefaultTestPrimaryRepo holds default repo name - DefaultTestPrimaryRepo = "primary" -) - // NewConfig returns a newly initialized Config object func NewConfig() *Config { return &Config{ @@ -95,13 +90,17 @@ func NewCluster() *Cluster { func NewManifest() *Manifest { return &Manifest{ PrimaryRepositoryName: DefaultTestPrimaryRepo, + TargetPath: DefaultTargetPath, + SubPath: DefaultSubPath, Repositories: map[string]*Repository{DefaultTestPrimaryRepo: NewRepository()}, } } // NewRepository is a convenience function that returns a new Repository func NewRepository() *Repository { - return &Repository{} + return &Repository{ + CheckoutOptions: &RepoCheckout{}, + } } // NewAuthInfo is a convenience function that returns a new AuthInfo diff --git a/testutil/testconfig.go b/testutil/testconfig.go index 1efdc27ba..e0a4ded46 100644 --- a/testutil/testconfig.go +++ b/testutil/testconfig.go @@ -264,6 +264,21 @@ func DummyManagementConfiguration() *config.ManagementConfiguration { } } +// DummyManifestOptions creates ManifestOptions config object +// for unit testing +func DummyManifestOptions() *config.ManifestOptions { + return &config.ManifestOptions{ + Name: "dummy_manifest", + SubPath: "manifests/dummy_site", + TargetPath: "/tmp/dummy_site", + IsPrimary: true, + RepoName: "dummy_repo", + URL: "https://github.com/treasuremap/dummy_site", + Branch: "master", + Force: true, + } +} + const ( testConfigYAML = `apiVersion: airshipit.org/v1alpha1 bootstrapInfo: