From 6dc39f0735d6a2ec1cc6840d3e97532ee0b61ff6 Mon Sep 17 00:00:00 2001 From: Ruslan Aliev Date: Thu, 4 Nov 2021 05:06:48 +0000 Subject: [PATCH] Introduce new kubeconfig workflow The proposed workflow is more generalized and conforms with other k8s tools like kubectl in the way of using kubeconfig. airshipctl will use single kubeconfig file (by default located at ~/.kube/config), and this file will be managed (adding/removing contexts) via KRM function named kubeconfig-manager. Change-Id: I9764eaf9665eaac5543b91c6c876094dfe23a86f Signed-off-by: Ruslan Aliev Closes: #666 --- Makefile | 1 + cmd/cluster/cluster.go | 44 - cmd/cluster/cluster_test.go | 35 - cmd/cluster/get_kubeconfig.go | 94 --- cmd/cluster/get_kubeconfig_test.go | 77 -- cmd/cluster/list.go | 61 -- cmd/cluster/list_test.go | 35 - cmd/cluster/status.go | 44 - cmd/cluster/status_test.go | 35 - .../cluster-cmd-with-help.golden | 16 - .../cluster-status-cmd-with-help.golden | 8 - ...luster-get-kubeconfig-cmd-with-help.golden | 35 - .../cluster-list-cmd-with-help.golden | 17 - cmd/phase/run.go | 19 +- .../TestRunGoldenOutput/run-with-help.golden | 2 + cmd/plan/run.go | 10 + .../plan-run-with-help.golden | 1 + cmd/root.go | 2 - .../rootCmd-with-default-subcommands.golden | 1 - docs/source/cli/airshipctl.rst | 1 - .../source/cli/cluster/airshipctl_cluster.rst | 1 - docs/source/cli/cluster/index.rst | 1 - docs/source/cli/index.rst | 1 - .../source/cli/phase/airshipctl_phase_run.rst | 2 + docs/source/cli/plan/airshipctl_plan_run.rst | 1 + docs/source/img/phase_overview.svg | 2 +- docs/source/phases.rst | 46 -- go.mod | 6 +- go.sum | 11 +- krm-functions/applier/image/main.go | 2 +- krm-functions/kubeconfig-manager/Dockerfile | 32 + krm-functions/kubeconfig-manager/README.md | 21 + .../kubeconfig-manager/certs/README.md | 6 + krm-functions/kubeconfig-manager/image/go.mod | 15 + krm-functions/kubeconfig-manager/image/go.sum | 761 ++++++++++++++++++ .../kubeconfig-manager/image/main.go | 241 ++++++ .../local-resource/example-use.yaml | 27 +- .../airshipit.org_clusterctls.yaml | 6 + .../airshipit.org_clustermaps.yaml | 102 --- .../function/phase-helpers/kustomization.yaml | 1 - .../merge_kubeconfig/kustomization.yaml | 6 - manifests/phases/cluster-map.yaml | 32 - manifests/phases/executors.yaml | 35 +- manifests/phases/kustomization.yaml | 1 - manifests/phases/phases.yaml | 14 +- .../phases/cluster_map_patch.yaml | 22 - .../phases/kustomization.yaml | 1 - .../site/test-site/phases/kustomization.yaml | 1 - pkg/api/v1alpha1/cluster_map_types.go | 87 -- pkg/api/v1alpha1/clusterctl_types.go | 5 +- pkg/api/v1alpha1/groupversion_info.go | 1 - pkg/api/v1alpha1/zz_generated.deepcopy.go | 139 ---- pkg/cluster/clustermap/errors.go | 51 -- pkg/cluster/clustermap/map.go | 148 ---- pkg/cluster/clustermap/map_test.go | 209 ----- pkg/cluster/command.go | 67 -- pkg/config/options.go | 5 +- pkg/container/api_test.go | 10 - pkg/k8s/kubeconfig/builder.go | 316 -------- pkg/k8s/kubeconfig/builder_test.go | 313 ------- pkg/k8s/kubeconfig/errors.go | 67 -- pkg/k8s/kubeconfig/kubeconfig.go | 307 ------- pkg/k8s/kubeconfig/kubeconfig_test.go | 605 -------------- pkg/k8s/kubeconfig/testdata/kubeconfig | 19 - pkg/k8s/kubeconfig/testdata/kubeconfig-test | 18 - pkg/k8s/kubeconfig/testdata/kubeconfig.yaml | 23 - .../kubeconfig/testdata/kustomization.yaml | 2 - pkg/k8s/kubeconfig/testdata/result-kubeconf | 43 - .../testdata_fail/kustomization.yaml | 2 - pkg/k8s/utils/utils.go | 43 - pkg/phase/client.go | 16 - pkg/phase/command.go | 38 +- pkg/phase/command_test.go | 74 -- pkg/phase/executors/clusterctl.go | 82 +- pkg/phase/executors/clusterctl_test.go | 136 ---- pkg/phase/executors/common.go | 3 + pkg/phase/executors/common_test.go | 13 - pkg/phase/executors/container.go | 31 +- pkg/phase/executors/container_test.go | 54 +- pkg/phase/executors/k8s_applier.go | 39 +- pkg/phase/executors/k8s_applier_test.go | 77 -- pkg/phase/helper.go | 29 - pkg/phase/helper_test.go | 122 --- pkg/phase/ifc/executor.go | 10 +- pkg/phase/ifc/helper.go | 3 - pkg/phase/ifc/phase.go | 2 - pkg/phase/render_test.go | 1 - .../phases/cluster_map.yaml | 12 - .../phases/kustomization.yaml | 1 - pkg/phase/testdata/phases/cluster-map.yaml | 15 - pkg/phase/testdata/phases/kustomization.yaml | 3 +- .../valid_site/phases/cluster_map.yaml | 12 - .../valid_site/phases/kustomization.yaml | 1 - .../valid_site/phases/cluster_map.yaml | 12 - .../valid_site/phases/kustomization.yaml | 1 - .../phases/cluster_map.yaml | 12 - .../phases/kustomization.yaml | 1 - pkg/util/yaml/writer_test.go | 1 - testutil/phase/helper.go | 21 - tools/deployment/23_generate_secrets.sh | 5 +- tools/deployment/25_deploy_gating.sh | 2 +- .../provider_common/01_install_kind.sh | 2 +- 102 files changed, 1254 insertions(+), 3992 deletions(-) delete mode 100644 cmd/cluster/cluster.go delete mode 100644 cmd/cluster/cluster_test.go delete mode 100644 cmd/cluster/get_kubeconfig.go delete mode 100644 cmd/cluster/get_kubeconfig_test.go delete mode 100755 cmd/cluster/list.go delete mode 100755 cmd/cluster/list_test.go delete mode 100644 cmd/cluster/status.go delete mode 100644 cmd/cluster/status_test.go delete mode 100644 cmd/cluster/testdata/TestNewClusterCommandGoldenOutput/cluster-cmd-with-help.golden delete mode 100644 cmd/cluster/testdata/TestNewClusterStatusCmdGoldenOutput/cluster-status-cmd-with-help.golden delete mode 100644 cmd/cluster/testdata/TestNewKubeConfigCommandCmdGoldenOutput/cluster-get-kubeconfig-cmd-with-help.golden delete mode 100644 cmd/cluster/testdata/TestNewListCommandGoldenOutput/cluster-list-cmd-with-help.golden create mode 100644 krm-functions/kubeconfig-manager/Dockerfile create mode 100644 krm-functions/kubeconfig-manager/README.md create mode 100644 krm-functions/kubeconfig-manager/certs/README.md create mode 100644 krm-functions/kubeconfig-manager/image/go.mod create mode 100644 krm-functions/kubeconfig-manager/image/go.sum create mode 100644 krm-functions/kubeconfig-manager/image/main.go rename manifests/function/phase-helpers/merge_kubeconfig/kubectl_merge_kubeconfig.sh => krm-functions/kubeconfig-manager/local-resource/example-use.yaml (58%) delete mode 100644 manifests/function/airshipctl-schemas/airshipit.org_clustermaps.yaml delete mode 100644 manifests/function/phase-helpers/merge_kubeconfig/kustomization.yaml delete mode 100644 manifests/phases/cluster-map.yaml delete mode 100644 manifests/site/docker-test-site/phases/cluster_map_patch.yaml delete mode 100644 pkg/api/v1alpha1/cluster_map_types.go delete mode 100644 pkg/cluster/clustermap/errors.go delete mode 100644 pkg/cluster/clustermap/map.go delete mode 100644 pkg/cluster/clustermap/map_test.go delete mode 100755 pkg/cluster/command.go delete mode 100644 pkg/k8s/kubeconfig/builder.go delete mode 100644 pkg/k8s/kubeconfig/builder_test.go delete mode 100644 pkg/k8s/kubeconfig/errors.go delete mode 100644 pkg/k8s/kubeconfig/kubeconfig.go delete mode 100644 pkg/k8s/kubeconfig/kubeconfig_test.go delete mode 100644 pkg/k8s/kubeconfig/testdata/kubeconfig delete mode 100644 pkg/k8s/kubeconfig/testdata/kubeconfig-test delete mode 100644 pkg/k8s/kubeconfig/testdata/kubeconfig.yaml delete mode 100644 pkg/k8s/kubeconfig/testdata/kustomization.yaml delete mode 100644 pkg/k8s/kubeconfig/testdata/result-kubeconf delete mode 100644 pkg/k8s/kubeconfig/testdata_fail/kustomization.yaml delete mode 100644 pkg/k8s/utils/utils.go delete mode 100755 pkg/phase/testdata/invalid_validation_site/phases/cluster_map.yaml delete mode 100755 pkg/phase/testdata/phases/cluster-map.yaml delete mode 100755 pkg/phase/testdata/reponame/valid_site/phases/cluster_map.yaml delete mode 100644 pkg/phase/testdata/valid_site/phases/cluster_map.yaml delete mode 100755 pkg/phase/testdata/valid_validation_site/phases/cluster_map.yaml diff --git a/Makefile b/Makefile index b6e46a462..092452958 100644 --- a/Makefile +++ b/Makefile @@ -121,6 +121,7 @@ docker-image_DOCKERFILE:=Dockerfile # need to be called from the root of the repo. applier_IS_INDEPENDED:=true kubeval-validator_IS_INDEPENDED:=true +kubeconfig-manager_IS_INDEPENDED:=true clusterctl_IS_INDEPENDED:=true clusterctl-v0.3_IS_INDEPENDED:=true toolbox-virsh_IS_INDEPENDED:=true diff --git a/cmd/cluster/cluster.go b/cmd/cluster/cluster.go deleted file mode 100644 index 855334c82..000000000 --- a/cmd/cluster/cluster.go +++ /dev/null @@ -1,44 +0,0 @@ -/* - 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 cluster - -import ( - "github.com/spf13/cobra" - - "opendev.org/airship/airshipctl/pkg/config" -) - -const ( - // TODO: (kkalynovskyi) Add more description when more subcommands are added - clusterLong = ` -Provides capabilities for interacting with a Kubernetes cluster, -such as getting status and deploying initial infrastructure. -` -) - -// NewClusterCommand creates a command for interacting with a Kubernetes cluster. -func NewClusterCommand(cfgFactory config.Factory) *cobra.Command { - clusterRootCmd := &cobra.Command{ - Use: "cluster", - Short: "Airshipctl command to manage kubernetes clusters", - Long: clusterLong[1:], - } - - clusterRootCmd.AddCommand(NewStatusCommand(cfgFactory)) - clusterRootCmd.AddCommand(NewGetKubeconfigCommand(cfgFactory)) - clusterRootCmd.AddCommand(NewListCommand(cfgFactory)) - - return clusterRootCmd -} diff --git a/cmd/cluster/cluster_test.go b/cmd/cluster/cluster_test.go deleted file mode 100644 index d1a19168d..000000000 --- a/cmd/cluster/cluster_test.go +++ /dev/null @@ -1,35 +0,0 @@ -/* - 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 cluster_test - -import ( - "testing" - - "opendev.org/airship/airshipctl/cmd/cluster" - "opendev.org/airship/airshipctl/testutil" -) - -func TestNewClusterCommand(t *testing.T) { - tests := []*testutil.CmdTest{ - { - Name: "cluster-cmd-with-help", - CmdLine: "--help", - Cmd: cluster.NewClusterCommand(nil), - }, - } - for _, testcase := range tests { - testutil.RunTest(t, testcase) - } -} diff --git a/cmd/cluster/get_kubeconfig.go b/cmd/cluster/get_kubeconfig.go deleted file mode 100644 index 5774879f0..000000000 --- a/cmd/cluster/get_kubeconfig.go +++ /dev/null @@ -1,94 +0,0 @@ -/* - 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 cluster - -import ( - "github.com/spf13/cobra" - - "opendev.org/airship/airshipctl/pkg/cluster" - "opendev.org/airship/airshipctl/pkg/config" -) - -const ( - getKubeconfigLong = ` -Retrieves kubeconfig of the cluster(s) and prints it to stdout. - -If you specify single CLUSTER_NAME, kubeconfig will have a CurrentContext set to CLUSTER_NAME and -will have its context defined. - -If you specify multiple CLUSTER_NAME args, kubeconfig will contain contexts for all of them, but current one -won't be specified. - -If you don't specify CLUSTER_NAME, kubeconfig will have multiple contexts for every cluster -in the airship site. Context names will correspond to cluster names. CurrentContext will be empty. -` - getKubeconfigExample = ` -Retrieve target-cluster kubeconfig -# airshipctl cluster get-kubeconfig target-cluster - -Retrieve kubeconfig for the entire site; the kubeconfig will have context for every cluster -# airshipctl cluster get-kubeconfig - -Specify a file where kubeconfig should be written -# airshipctl cluster get-kubeconfig --file ~/my-kubeconfig - -Merge site kubeconfig with existing kubeconfig file. -Keep in mind that this can override a context if it has the same name -Airshipctl will overwrite the contents of the file, if you want merge with existing file, specify "--merge" flag -# airshipctl cluster get-kubeconfig --file ~/.airship/kubeconfig --merge -` -) - -// NewGetKubeconfigCommand creates a command which retrieves cluster kubeconfig -func NewGetKubeconfigCommand(cfgFactory config.Factory) *cobra.Command { - opts := &cluster.GetKubeconfigCommand{} - cmd := &cobra.Command{ - Use: "get-kubeconfig [CLUSTER_NAME...]", - Short: "Airshipctl command to retrieve kubeconfig for a desired cluster(s)", - Long: getKubeconfigLong[1:], - Args: GetKubeconfArgs(opts), - Example: getKubeconfigExample, - RunE: func(cmd *cobra.Command, args []string) error { - return opts.RunE(cfgFactory, cmd.OutOrStdout()) - }, - } - flags := cmd.Flags() - - flags.StringVarP( - &opts.File, - "file", - "f", - "", - "specify where to write kubeconfig file. If flag isn't specified, airshipctl will write it to stdout", - ) - flags.BoolVar( - &opts.Merge, - "merge", - false, - "specify if you want to merge kubeconfig with the one that exists at --file location", - ) - return cmd -} - -// GetKubeconfArgs extracts one or less arguments from command line, and saves it as name -func GetKubeconfArgs(opts *cluster.GetKubeconfigCommand) cobra.PositionalArgs { - return func(cmd *cobra.Command, args []string) error { - for _, arg := range args { - opts.ClusterNames = append(opts.ClusterNames, arg) - } - - return cobra.MinimumNArgs(0)(cmd, args) - } -} diff --git a/cmd/cluster/get_kubeconfig_test.go b/cmd/cluster/get_kubeconfig_test.go deleted file mode 100644 index e57f08696..000000000 --- a/cmd/cluster/get_kubeconfig_test.go +++ /dev/null @@ -1,77 +0,0 @@ -/* - 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 cluster_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "opendev.org/airship/airshipctl/cmd/cluster" - pkgcluster "opendev.org/airship/airshipctl/pkg/cluster" - "opendev.org/airship/airshipctl/testutil" -) - -func TestNewKubeConfigCommandCmd(t *testing.T) { - tests := []*testutil.CmdTest{ - { - Name: "cluster-get-kubeconfig-cmd-with-help", - CmdLine: "--help", - Cmd: cluster.NewGetKubeconfigCommand(nil), - }, - } - for _, testcase := range tests { - testutil.RunTest(t, testcase) - } -} - -func TestGetKubeconfArgs(t *testing.T) { - tests := []struct { - name string - args []string - expectedErrStr string - expectedClusterNames []string - }{ - { - name: "success one cluster specified", - args: []string{"cluster01"}, - expectedClusterNames: []string{"cluster01"}, - }, - { - name: "success no cluster specified", - }, - { - name: "success two cluster specified", - args: []string{"cluster01", "cluster02"}, - expectedClusterNames: []string{"cluster01", "cluster02"}, - }, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - cmd := &pkgcluster.GetKubeconfigCommand{} - args := cluster.GetKubeconfArgs(cmd) - err := args(cluster.NewGetKubeconfigCommand(nil), tt.args) - if tt.expectedErrStr != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.expectedErrStr) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.expectedClusterNames, cmd.ClusterNames) - } - }) - } -} diff --git a/cmd/cluster/list.go b/cmd/cluster/list.go deleted file mode 100755 index 58216a47c..000000000 --- a/cmd/cluster/list.go +++ /dev/null @@ -1,61 +0,0 @@ -/* - 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 cluster - -import ( - "github.com/spf13/cobra" - - "opendev.org/airship/airshipctl/pkg/config" - "opendev.org/airship/airshipctl/pkg/phase" -) - -const ( - listLong = ` -Retrieve and list the defined clusters in table form or display just the cluster names. The contents of the -table would include cluster name, kubeconfig context and parent cluster name. -` - - listExample = ` -Retrieve list of clusters -# airshipctl cluster list --airshipconf /tmp/airconfig -# airshipctl cluster list -o table -# airshipctl cluster list -o name -` -) - -// NewListCommand creates a command which retrieves list of clusters -func NewListCommand(cfgFactory config.Factory) *cobra.Command { - o := &phase.ClusterListCommand{Factory: cfgFactory} - cmd := &cobra.Command{ - Use: "list", - Short: "Airshipctl command to get and list defined clusters", - Long: listLong[1:], - Example: listExample, - RunE: listRunE(o), - } - flags := cmd.Flags() - flags.StringVarP(&o.Format, "output", "o", "name", "output formats. Supported options are 'table' and 'name'") - - return cmd -} - -// listRunE returns a function to cobra command to be executed in runtime -func listRunE(o *phase.ClusterListCommand) func( - cmd *cobra.Command, args []string) error { - return func(cmd *cobra.Command, args []string) error { - o.Writer = cmd.OutOrStdout() - return o.RunE() - } -} diff --git a/cmd/cluster/list_test.go b/cmd/cluster/list_test.go deleted file mode 100755 index f66933bba..000000000 --- a/cmd/cluster/list_test.go +++ /dev/null @@ -1,35 +0,0 @@ -/* - 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 cluster_test - -import ( - "testing" - - "opendev.org/airship/airshipctl/cmd/cluster" - "opendev.org/airship/airshipctl/testutil" -) - -func TestNewListCommand(t *testing.T) { - tests := []*testutil.CmdTest{ - { - Name: "cluster-list-cmd-with-help", - CmdLine: "--help", - Cmd: cluster.NewListCommand(nil), - }, - } - for _, testcase := range tests { - testutil.RunTest(t, testcase) - } -} diff --git a/cmd/cluster/status.go b/cmd/cluster/status.go deleted file mode 100644 index 6e2230734..000000000 --- a/cmd/cluster/status.go +++ /dev/null @@ -1,44 +0,0 @@ -/* - 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 cluster - -import ( - "github.com/spf13/cobra" - - "opendev.org/airship/airshipctl/pkg/config" - "opendev.org/airship/airshipctl/pkg/errors" -) - -// NewStatusCommand creates a command which reports the statuses of a cluster's deployed components. -func NewStatusCommand(cfgFactory config.Factory) *cobra.Command { - var kubeconfig string - statusCmd := &cobra.Command{ - Use: "status", - Short: "Retrieve statuses of deployed cluster components", - RunE: clusterStatusRunE, - } - - statusCmd.Flags().StringVar( - &kubeconfig, - "kubeconfig", - "", - "Path to kubeconfig associated with cluster being managed") - - return statusCmd -} - -func clusterStatusRunE(cmd *cobra.Command, args []string) error { - return errors.ErrNotImplemented{What: "Cluster Status"} -} diff --git a/cmd/cluster/status_test.go b/cmd/cluster/status_test.go deleted file mode 100644 index 40806033b..000000000 --- a/cmd/cluster/status_test.go +++ /dev/null @@ -1,35 +0,0 @@ -/* - 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 cluster_test - -import ( - "testing" - - "opendev.org/airship/airshipctl/cmd/cluster" - "opendev.org/airship/airshipctl/testutil" -) - -func TestNewClusterStatusCmd(t *testing.T) { - tests := []*testutil.CmdTest{ - { - Name: "cluster-status-cmd-with-help", - CmdLine: "--help", - Cmd: cluster.NewStatusCommand(nil), - }, - } - for _, testcase := range tests { - testutil.RunTest(t, testcase) - } -} diff --git a/cmd/cluster/testdata/TestNewClusterCommandGoldenOutput/cluster-cmd-with-help.golden b/cmd/cluster/testdata/TestNewClusterCommandGoldenOutput/cluster-cmd-with-help.golden deleted file mode 100644 index 4bc97b7d7..000000000 --- a/cmd/cluster/testdata/TestNewClusterCommandGoldenOutput/cluster-cmd-with-help.golden +++ /dev/null @@ -1,16 +0,0 @@ -Provides capabilities for interacting with a Kubernetes cluster, -such as getting status and deploying initial infrastructure. - -Usage: - cluster [command] - -Available Commands: - get-kubeconfig Airshipctl command to retrieve kubeconfig for a desired cluster(s) - help Help about any command - list Airshipctl command to get and list defined clusters - status Retrieve statuses of deployed cluster components - -Flags: - -h, --help help for cluster - -Use "cluster [command] --help" for more information about a command. diff --git a/cmd/cluster/testdata/TestNewClusterStatusCmdGoldenOutput/cluster-status-cmd-with-help.golden b/cmd/cluster/testdata/TestNewClusterStatusCmdGoldenOutput/cluster-status-cmd-with-help.golden deleted file mode 100644 index 12df09777..000000000 --- a/cmd/cluster/testdata/TestNewClusterStatusCmdGoldenOutput/cluster-status-cmd-with-help.golden +++ /dev/null @@ -1,8 +0,0 @@ -Retrieve statuses of deployed cluster components - -Usage: - status [flags] - -Flags: - -h, --help help for status - --kubeconfig string Path to kubeconfig associated with cluster being managed diff --git a/cmd/cluster/testdata/TestNewKubeConfigCommandCmdGoldenOutput/cluster-get-kubeconfig-cmd-with-help.golden b/cmd/cluster/testdata/TestNewKubeConfigCommandCmdGoldenOutput/cluster-get-kubeconfig-cmd-with-help.golden deleted file mode 100644 index ac9c45a15..000000000 --- a/cmd/cluster/testdata/TestNewKubeConfigCommandCmdGoldenOutput/cluster-get-kubeconfig-cmd-with-help.golden +++ /dev/null @@ -1,35 +0,0 @@ -Retrieves kubeconfig of the cluster(s) and prints it to stdout. - -If you specify single CLUSTER_NAME, kubeconfig will have a CurrentContext set to CLUSTER_NAME and -will have its context defined. - -If you specify multiple CLUSTER_NAME args, kubeconfig will contain contexts for all of them, but current one -won't be specified. - -If you don't specify CLUSTER_NAME, kubeconfig will have multiple contexts for every cluster -in the airship site. Context names will correspond to cluster names. CurrentContext will be empty. - -Usage: - get-kubeconfig [CLUSTER_NAME...] [flags] - -Examples: - -Retrieve target-cluster kubeconfig -# airshipctl cluster get-kubeconfig target-cluster - -Retrieve kubeconfig for the entire site; the kubeconfig will have context for every cluster -# airshipctl cluster get-kubeconfig - -Specify a file where kubeconfig should be written -# airshipctl cluster get-kubeconfig --file ~/my-kubeconfig - -Merge site kubeconfig with existing kubeconfig file. -Keep in mind that this can override a context if it has the same name -Airshipctl will overwrite the contents of the file, if you want merge with existing file, specify "--merge" flag -# airshipctl cluster get-kubeconfig --file ~/.airship/kubeconfig --merge - - -Flags: - -f, --file string specify where to write kubeconfig file. If flag isn't specified, airshipctl will write it to stdout - -h, --help help for get-kubeconfig - --merge specify if you want to merge kubeconfig with the one that exists at --file location diff --git a/cmd/cluster/testdata/TestNewListCommandGoldenOutput/cluster-list-cmd-with-help.golden b/cmd/cluster/testdata/TestNewListCommandGoldenOutput/cluster-list-cmd-with-help.golden deleted file mode 100644 index 6b90a22ab..000000000 --- a/cmd/cluster/testdata/TestNewListCommandGoldenOutput/cluster-list-cmd-with-help.golden +++ /dev/null @@ -1,17 +0,0 @@ -Retrieve and list the defined clusters in table form or display just the cluster names. The contents of the -table would include cluster name, kubeconfig context and parent cluster name. - -Usage: - list [flags] - -Examples: - -Retrieve list of clusters -# airshipctl cluster list --airshipconf /tmp/airconfig -# airshipctl cluster list -o table -# airshipctl cluster list -o name - - -Flags: - -h, --help help for list - -o, --output string output formats. Supported options are 'table' and 'name' (default "name") diff --git a/cmd/phase/run.go b/cmd/phase/run.go index 0e7056c7a..77738024e 100644 --- a/cmd/phase/run.go +++ b/cmd/phase/run.go @@ -15,6 +15,8 @@ package phase import ( + "os" + "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -23,8 +25,6 @@ import ( ) const ( - // TODO (kkalynovskyi) when different phase executors will be implemented, and their description is more clear, - // add documentation here. also consider adding dynamic phase descriptions based on executors. runLong = ` Run a phase such as controlplane-ephemeral, remotedirect-ephemeral, initinfra-ephemeral, etc... To list the phases associated with a site, run 'airshipctl phase list'. @@ -38,6 +38,7 @@ Run initinfra phase // NewRunCommand creates a command to run specific phase func NewRunCommand(cfgFactory config.Factory) *cobra.Command { p := &phase.RunCommand{Factory: cfgFactory} + p.Options.Kubeconfig = os.Getenv("KUBECONFIG") f := &phase.RunFlags{} runCmd := &cobra.Command{ @@ -51,6 +52,10 @@ func NewRunCommand(cfgFactory config.Factory) *cobra.Command { // Go through all the flags that have been set fn := func(flag *pflag.Flag) { switch flag.Name { + case "kubeconfig": + p.Options.Kubeconfig = f.Kubeconfig + case "context": + p.Options.Context = f.Context case "dry-run": p.Options.DryRun = f.DryRun case "wait-timeout": @@ -62,6 +67,16 @@ func NewRunCommand(cfgFactory config.Factory) *cobra.Command { }, } flags := runCmd.Flags() + flags.StringVar( + &f.Kubeconfig, + "kubeconfig", + "", + "Path to the kubeconfig file to use for phase's purposes") + flags.StringVar( + &f.Context, + "context", + "", + "The name of the kubeconfig context to use") flags.BoolVar(&f.DryRun, "dry-run", false, "simulate phase execution") flags.DurationVar(&f.Timeout, "wait-timeout", 0, "wait timeout") return runCmd diff --git a/cmd/phase/testdata/TestRunGoldenOutput/run-with-help.golden b/cmd/phase/testdata/TestRunGoldenOutput/run-with-help.golden index fa2576593..f37cc2368 100644 --- a/cmd/phase/testdata/TestRunGoldenOutput/run-with-help.golden +++ b/cmd/phase/testdata/TestRunGoldenOutput/run-with-help.golden @@ -11,6 +11,8 @@ Run initinfra phase Flags: + --context string The name of the kubeconfig context to use --dry-run simulate phase execution -h, --help help for run + --kubeconfig string Path to the kubeconfig file to use for phase's purposes --wait-timeout duration wait timeout diff --git a/cmd/plan/run.go b/cmd/plan/run.go index 21c7c02c0..7989b604c 100644 --- a/cmd/plan/run.go +++ b/cmd/plan/run.go @@ -15,6 +15,8 @@ package plan import ( + "os" + "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -39,6 +41,7 @@ Perform a dry run of a plan // NewRunCommand creates a command which execute a particular phase plan func NewRunCommand(cfgFactory config.Factory) *cobra.Command { r := &phase.PlanRunCommand{Factory: cfgFactory} + r.Options.Kubeconfig = os.Getenv("KUBECONFIG") f := &phase.RunFlags{} runCmd := &cobra.Command{ @@ -51,6 +54,8 @@ func NewRunCommand(cfgFactory config.Factory) *cobra.Command { r.PlanID.Name = args[0] fn := func(flag *pflag.Flag) { switch flag.Name { + case "kubeconfig": + r.Options.Kubeconfig = f.Kubeconfig case "dry-run": r.Options.DryRun = f.DryRun case "wait-timeout": @@ -63,6 +68,11 @@ func NewRunCommand(cfgFactory config.Factory) *cobra.Command { } flags := runCmd.Flags() + flags.StringVar( + &f.Kubeconfig, + "kubeconfig", + "", + "Path to the kubeconfig file to use for plan's purposes") flags.BoolVar(&f.DryRun, "dry-run", false, "simulate phase execution") flags.DurationVar(&f.Timeout, "wait-timeout", 0, "wait timeout") return runCmd diff --git a/cmd/plan/testdata/TestNewRunCommandGoldenOutput/plan-run-with-help.golden b/cmd/plan/testdata/TestNewRunCommandGoldenOutput/plan-run-with-help.golden index b9906b492..e460c022c 100644 --- a/cmd/plan/testdata/TestNewRunCommandGoldenOutput/plan-run-with-help.golden +++ b/cmd/plan/testdata/TestNewRunCommandGoldenOutput/plan-run-with-help.golden @@ -16,4 +16,5 @@ Perform a dry run of a plan Flags: --dry-run simulate phase execution -h, --help help for run + --kubeconfig string Path to the kubeconfig file to use for plan's purposes --wait-timeout duration wait timeout diff --git a/cmd/root.go b/cmd/root.go index 79bc453cd..2f3787b0c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -23,7 +23,6 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth" "opendev.org/airship/airshipctl/cmd/baremetal" - "opendev.org/airship/airshipctl/cmd/cluster" "opendev.org/airship/airshipctl/cmd/completion" "opendev.org/airship/airshipctl/cmd/config" "opendev.org/airship/airshipctl/cmd/document" @@ -77,7 +76,6 @@ func NewRootCommand(out io.Writer) (*cobra.Command, *RootOptions) { // default commands to airshipctl func AddDefaultAirshipCTLCommands(cmd *cobra.Command, factory cfg.Factory) *cobra.Command { cmd.AddCommand(baremetal.NewBaremetalCommand(factory)) - cmd.AddCommand(cluster.NewClusterCommand(factory)) cmd.AddCommand(completion.NewCompletionCommand()) cmd.AddCommand(document.NewDocumentCommand(factory)) cmd.AddCommand(config.NewConfigCommand(factory)) diff --git a/cmd/testdata/TestRootGoldenOutput/rootCmd-with-default-subcommands.golden b/cmd/testdata/TestRootGoldenOutput/rootCmd-with-default-subcommands.golden index a950bdebc..97bad81c4 100644 --- a/cmd/testdata/TestRootGoldenOutput/rootCmd-with-default-subcommands.golden +++ b/cmd/testdata/TestRootGoldenOutput/rootCmd-with-default-subcommands.golden @@ -7,7 +7,6 @@ Usage: Available Commands: baremetal Airshipctl command to manage bare metal host(s) - cluster Airshipctl command to manage kubernetes clusters completion Airshipctl command to generate completion script for the specified shell (bash or zsh) config Airshipctl command to manage airshipctl config file document Airshipctl command to manage site manifest documents diff --git a/docs/source/cli/airshipctl.rst b/docs/source/cli/airshipctl.rst index 6ec398c86..1c4fdd5ef 100644 --- a/docs/source/cli/airshipctl.rst +++ b/docs/source/cli/airshipctl.rst @@ -27,7 +27,6 @@ SEE ALSO ~~~~~~~~ * :ref:`airshipctl baremetal ` - Airshipctl command to manage bare metal host(s) -* :ref:`airshipctl cluster ` - Airshipctl command to manage kubernetes clusters * :ref:`airshipctl completion ` - Airshipctl command to generate completion script for the specified shell (bash or zsh) * :ref:`airshipctl config ` - Airshipctl command to manage airshipctl config file * :ref:`airshipctl document ` - Airshipctl command to manage site manifest documents diff --git a/docs/source/cli/cluster/airshipctl_cluster.rst b/docs/source/cli/cluster/airshipctl_cluster.rst index e9abef0e9..c85fdeedf 100644 --- a/docs/source/cli/cluster/airshipctl_cluster.rst +++ b/docs/source/cli/cluster/airshipctl_cluster.rst @@ -33,6 +33,5 @@ SEE ALSO * :ref:`airshipctl ` - A unified command line tool for management of end-to-end kubernetes cluster deployment on cloud infrastructure environments. * :ref:`airshipctl cluster get-kubeconfig ` - Airshipctl command to retrieve kubeconfig for a desired cluster(s) -* :ref:`airshipctl cluster list ` - Airshipctl command to get and list defined clusters * :ref:`airshipctl cluster status ` - Retrieve statuses of deployed cluster components diff --git a/docs/source/cli/cluster/index.rst b/docs/source/cli/cluster/index.rst index 5c9b63e4c..050fcba00 100644 --- a/docs/source/cli/cluster/index.rst +++ b/docs/source/cli/cluster/index.rst @@ -7,5 +7,4 @@ cluster airshipctl_cluster airshipctl_cluster_get-kubeconfig - airshipctl_cluster_list airshipctl_cluster_status diff --git a/docs/source/cli/index.rst b/docs/source/cli/index.rst index 4abb27dde..6325fb608 100644 --- a/docs/source/cli/index.rst +++ b/docs/source/cli/index.rst @@ -7,7 +7,6 @@ Commands airshipctl baremetal/index - cluster/index completion/index config/index document/index diff --git a/docs/source/cli/phase/airshipctl_phase_run.rst b/docs/source/cli/phase/airshipctl_phase_run.rst index 5620523d1..933e8a2ae 100644 --- a/docs/source/cli/phase/airshipctl_phase_run.rst +++ b/docs/source/cli/phase/airshipctl_phase_run.rst @@ -32,8 +32,10 @@ Options :: + --context string The name of the kubeconfig context to use --dry-run simulate phase execution -h, --help help for run + --kubeconfig string Path to the kubeconfig file to use for phase's purposes --wait-timeout duration wait timeout Options inherited from parent commands diff --git a/docs/source/cli/plan/airshipctl_plan_run.rst b/docs/source/cli/plan/airshipctl_plan_run.rst index 8239af19d..8ae2b9768 100644 --- a/docs/source/cli/plan/airshipctl_plan_run.rst +++ b/docs/source/cli/plan/airshipctl_plan_run.rst @@ -37,6 +37,7 @@ Options --dry-run simulate phase execution -h, --help help for run + --kubeconfig string Path to the kubeconfig file to use for plan's purposes --wait-timeout duration wait timeout Options inherited from parent commands diff --git a/docs/source/img/phase_overview.svg b/docs/source/img/phase_overview.svg index 8245d8e3a..e2d1d7701 100644 --- a/docs/source/img/phase_overview.svg +++ b/docs/source/img/phase_overview.svg @@ -1 +1 @@ -AirshipConfigContextmanfiestTargePath:/home/docscommandlineusage:airshipctlphaserunphasebExectuor:KubernetesApplyperformkubectlapplymetadata.yamlphase:path:phasesdocEntryPointPrefix:typesPhaseBundlePhaseBentryPoint:manifests/ephemeral/bootstrapClusterMapKubeconfigclusterName:ephemeralExecutorDocument:KubernetsApplyexecutor:KubernetesApplyExecutorConfigExecutorBundle12343453phaseRepositoryName:primarymetadataPath:metadata.yaml2 \ No newline at end of file +AirshipConfigContextmanfiestTargePath:/home/docscommandlineusage:airshipctlphaserunphasebExectuor:KubernetesApplyperformkubectlapplymetadata.yamlphase:path:phasesdocEntryPointPrefix:typesPhaseBundlePhaseBentryPoint:manifests/ephemeral/bootstrapKubeconfigclusterName:ephemeralExecutorDocument:KubernetsApplyexecutor:KubernetesApplyExecutorConfigExecutorBundle12343453phaseRepositoryName:primarymetadataPath:metadata.yaml2 \ No newline at end of file diff --git a/docs/source/phases.rst b/docs/source/phases.rst index 2f14d6d81..485de0ced 100644 --- a/docs/source/phases.rst +++ b/docs/source/phases.rst @@ -323,52 +323,6 @@ Kubeconfig api object example client-certificate-data: client-key-data: -Cluster Map ------------ - -Cluster map defines parent-child relationship between clusters, allows -dynamic kubeconfig for clusters. When kubeconfig must be sourced from -a parent cluster, cluster map will be used to find parent cluster and -request kubeconfig from it. - -Also, ClusterAPIRef is a part of Cluster Map and will be used to find -cluster object in kubernetes parent cluster. It also maps a clusterapi -name and namespaces for a given cluster. In the below example object, -the cluster api ref describes the reference to the cluster api object. -The cluster `workload01` in the cluster map has an clusterAPIRef to -corresponding cluster-api cluster object (kind: cluster) with name workload01 -and inside namespace tenant01-namespace. - -Cluster map is defined in `Phase bundle <#phase-bundle>`__ as a document. - -- `Cluster map API object source code - `__ -- `Cluster map interface source code - `__ - -Example of cluster map -~~~~~~~~~~~~~~~~~~~~~~ - -.. code:: yaml - - apiVersion: airshipit.org/v1alpha1 - kind: ClusterMap - metadata: - labels: - airshipit.org/deploy-k8s: "false" - name: main-map - map: - target-cluster: - parent: ephemeral-cluster - # dynamicKubeConf: false # default value - ephemeral-cluster: {} - workload01: - parent: target-cluster - dynamicKubeConf: true - clusterAPIRef: - name: workload01 - namespace: tenant01-namespace - Metadata file ------------- diff --git a/go.mod b/go.mod index 80cabd9f8..d1109c53e 100644 --- a/go.mod +++ b/go.mod @@ -24,12 +24,12 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.7.0 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 - golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect - golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect + golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect + golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect + golang.org/x/text v0.3.7 // indirect k8s.io/api v0.21.1 k8s.io/apiextensions-apiserver v0.21.1 k8s.io/apimachinery v0.21.1 - k8s.io/cli-runtime v0.21.1 k8s.io/client-go v0.21.1 k8s.io/kubectl v0.21.1 opendev.org/airship/go-redfish v0.0.0-20211004183611-3c3d7c6ba009 diff --git a/go.sum b/go.sum index fe2117a5c..b026f7f62 100644 --- a/go.sum +++ b/go.sum @@ -689,8 +689,8 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -758,8 +758,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= @@ -770,8 +770,9 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/krm-functions/applier/image/main.go b/krm-functions/applier/image/main.go index e887f6f82..91e501a69 100644 --- a/krm-functions/applier/image/main.go +++ b/krm-functions/applier/image/main.go @@ -92,7 +92,7 @@ func (c *Config) Run(nodes []*yaml.RNode) ([]*yaml.RNode, error) { objs = append(objs, &unstructured.Unstructured{Object: m}) } - f := factoryFromKubeConfig(c.Kubeconfig, c.Context) + f := factoryFromKubeConfig("/kubeconfig", c.Context) statusPoller, err := poller.NewStatusPoller(f, c.WaitOptions.Conditions...) if err != nil { return nil, err diff --git a/krm-functions/kubeconfig-manager/Dockerfile b/krm-functions/kubeconfig-manager/Dockerfile new file mode 100644 index 000000000..2b3231b94 --- /dev/null +++ b/krm-functions/kubeconfig-manager/Dockerfile @@ -0,0 +1,32 @@ +ARG GO_IMAGE=quay.io/airshipit/golang:1.16.8-alpine +ARG PLUGINS_RELEASE_IMAGE=quay.io/airshipit/alpine:3.13.5 + +FROM ${GO_IMAGE} as function +ARG GOPROXY="" +# Inject custom root certificate authorities if needed +# Docker does not have a good conditional copy statement and requires that a source file exists +# to complete the copy function without error. Therefore the README.md file will be copied to +# the image every time even if there are no .crt files. +COPY ./certs/* /usr/local/share/ca-certificates/ +RUN update-ca-certificates +ENV PATH "/usr/local/go/bin:$PATH" +ENV CGO_ENABLED=0 +WORKDIR /go/src/ +COPY image/go.mod image/go.sum ./ +RUN go mod download +COPY image/ ./ +RUN go build -v -o /usr/local/bin/config-function ./ + +FROM ${PLUGINS_RELEASE_IMAGE} as release +# Inject custom root certificate authorities if needed +# Docker does not have a good conditional copy statement and requires that a source file exists +# to complete the copy function without error. Therefore the README.md file will be copied to +# the image every time even if there are no .crt files. +RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* +COPY ./certs/* /usr/local/share/ca-certificates/ +RUN update-ca-certificates +COPY --from=function /usr/local/bin/config-function /usr/local/bin/config-function +ENV HOME=/home/kubeconfig-manager +WORKDIR $HOME +RUN chmod -R a+w $HOME +CMD ["config-function"] diff --git a/krm-functions/kubeconfig-manager/README.md b/krm-functions/kubeconfig-manager/README.md new file mode 100644 index 000000000..33b9156fc --- /dev/null +++ b/krm-functions/kubeconfig-manager/README.md @@ -0,0 +1,21 @@ +# Kubeconfig Manager + +This is a KRM function which retrieves and modifies kubeconfig +based on appropriate options. + +## Function implementation + +The function is implemented as an [image](image), and built using `make docker-image-kubeconfig-manager`. + +### Function configuration + +As input options, the KRM function receives a struct with options how to get or modify kubeconfig. +See the `KubeconfigOptions` struct definition in v1alpha1 airshipctl API for the documentation. + +## Function invocation + +The function invoked by airshipctl command via `airshipctl phase run`: + + airshipctl phase run + +if a phase has GenericContainer executor defined along with KubeconfigOptions. diff --git a/krm-functions/kubeconfig-manager/certs/README.md b/krm-functions/kubeconfig-manager/certs/README.md new file mode 100644 index 000000000..7d04f7ec4 --- /dev/null +++ b/krm-functions/kubeconfig-manager/certs/README.md @@ -0,0 +1,6 @@ +# Additional Docker image root certificate authorities +If you require additional certificate authorities for your Docker image: +* Add ASCII PEM encoded .crt files to this directory + * The files will be copied into your docker image at build time. + +To update manually copy the .crt files to /usr/local/share/ca-certificates/ and run sudo update-ca-certificates. \ No newline at end of file diff --git a/krm-functions/kubeconfig-manager/image/go.mod b/krm-functions/kubeconfig-manager/image/go.mod new file mode 100644 index 000000000..7058e87de --- /dev/null +++ b/krm-functions/kubeconfig-manager/image/go.mod @@ -0,0 +1,15 @@ +module opendev.org/airship/airshipctl/krm-functions/kubeconfig-manager/image + +go 1.16 + +require ( + github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect + github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect + github.com/imdario/mergo v0.3.12 + k8s.io/apimachinery v0.21.1 + k8s.io/cli-runtime v0.21.1 + k8s.io/client-go v0.21.1 + k8s.io/kubectl v0.21.1 + sigs.k8s.io/kustomize/api v0.8.11 // indirect + sigs.k8s.io/kustomize/kyaml v0.11.0 +) diff --git a/krm-functions/kubeconfig-manager/image/go.sum b/krm-functions/kubeconfig-manager/image/go.sum new file mode 100644 index 000000000..230bd237e --- /dev/null +++ b/krm-functions/kubeconfig-manager/image/go.sum @@ -0,0 +1,761 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= +github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f h1:8GDPb0tCY8LQ+OJ3dbHb5sA6YZWXFORQYZx5sdsTlMs= +github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.5 h1:Xm0Ao53uqnk9QE/LlYV5DEU09UAgpliA85QoT9LzqPw= +github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/gnostic v0.5.1 h1:A8Yhf6EtqTv9RMsU6MQTyrtV1TjWlR6xU9BsZIwuTCM= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q= +github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 h1:OgUuv8lsRpBibGNbSizVwKWlysjaNzmC9gYMhPVfqFM= +golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 h1:8qxJSnu+7dRq6upnbntrmriWByIakBuct5OM/MdQC1M= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.21.1 h1:94bbZ5NTjdINJEdzOkpS4vdPhkb1VFpTYC9zh43f75c= +k8s.io/api v0.21.1/go.mod h1:FstGROTmsSHBarKc8bylzXih8BLNYTiS3TZcsoEDg2s= +k8s.io/apimachinery v0.21.1 h1:Q6XuHGlj2xc+hlMCvqyYfbv3H7SRGn2c8NycxJquDVs= +k8s.io/apimachinery v0.21.1/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= +k8s.io/cli-runtime v0.21.1 h1:Oj/iZxa7LLXrhzShaLNF4rFJEIEBTDHj0dJw4ra2vX4= +k8s.io/cli-runtime v0.21.1/go.mod h1:TI9Bvl8lQWZB2KqE91QLCp9AZE4l29zNFnj/x4IX4Fw= +k8s.io/client-go v0.21.1 h1:bhblWYLZKUu+pm50plvQF8WpY6TXdRRtcS/K9WauOj4= +k8s.io/client-go v0.21.1/go.mod h1:/kEw4RgW+3xnBGzvp9IWxKSNA+lXn3A7AuH3gdOAzLs= +k8s.io/code-generator v0.21.1/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= +k8s.io/component-base v0.21.1 h1:iLpj2btXbR326s/xNQWmPNGu0gaYSjzn7IN/5i28nQw= +k8s.io/component-base v0.21.1/go.mod h1:NgzFZ2qu4m1juby4TnrmpR8adRk6ka62YdH5DkIIyKA= +k8s.io/component-helpers v0.21.1/go.mod h1:FtC1flbiQlosHQrLrRUulnKxE4ajgWCGy/67fT2GRlQ= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts= +k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kubectl v0.21.1 h1:ySEusoeSgSDSiSBncDMsNrthSa3OSlXqT4R2rf1VFTw= +k8s.io/kubectl v0.21.1/go.mod h1:PMYR88MqESuysBM/MX+Vu4JbX/50nY4d4kny+SPEI2U= +k8s.io/metrics v0.21.1/go.mod h1:pyDVLsLe++FIGDBFU80NcW4xMFsuiVTWL8Zfi7+PpNo= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/kustomize/api v0.8.8/go.mod h1:He1zoK0nk43Pc6NlV085xDXDXTNprtcyKZVm3swsdNY= +sigs.k8s.io/kustomize/api v0.8.11 h1:LzQzlq6Z023b+mBtc6v72N2mSHYmN8x7ssgbf/hv0H8= +sigs.k8s.io/kustomize/api v0.8.11/go.mod h1:a77Ls36JdfCWojpUqR6m60pdGY1AYFix4AH83nJtY1g= +sigs.k8s.io/kustomize/cmd/config v0.9.10/go.mod h1:Mrby0WnRH7hA6OwOYnYpfpiY0WJIMgYrEDfwOeFdMK0= +sigs.k8s.io/kustomize/kustomize/v4 v4.1.2/go.mod h1:PxBvo4WGYlCLeRPL+ziT64wBXqbgfcalOS/SXa/tcyo= +sigs.k8s.io/kustomize/kyaml v0.10.17/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg= +sigs.k8s.io/kustomize/kyaml v0.11.0 h1:9KhiCPKaVyuPcgOLJXkvytOvjMJLoxpjodiycb4gHsA= +sigs.k8s.io/kustomize/kyaml v0.11.0/go.mod h1:GNMwjim4Ypgp/MueD3zXHLRJEjz7RvtPae0AwlvEMFM= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8= +sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/krm-functions/kubeconfig-manager/image/main.go b/krm-functions/kubeconfig-manager/image/main.go new file mode 100644 index 000000000..9a7b027dc --- /dev/null +++ b/krm-functions/kubeconfig-manager/image/main.go @@ -0,0 +1,241 @@ +/* + 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 main + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "time" + + "github.com/imdario/mergo" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/cli-runtime/pkg/genericclioptions" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "sigs.k8s.io/kustomize/kyaml/fn/framework" + "sigs.k8s.io/kustomize/kyaml/fn/framework/command" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +const ( + kubeconfigFile = "/kubeconfig" + defaultTimeout = 30 * time.Second + + kubeconfigAPIVersion = "airshipit.org/v1alpha1" + kubeconfigKind = "KubeConfig" + kubeconfigKey = "config" +) + +// Options holds main options to perform different actions with kubeconfig +type Options struct { + Action string `yaml:"action,omitempty"` + Context string `yaml:"context,omitempty"` + CapiOptions CAPIOptions `yaml:"capiOptions,omitempty"` +} + +// CAPIOptions is used to retrieve cluster's kubeconfig from secret +type CAPIOptions struct { + ManagedClusterName string `yaml:"clusterName,omitempty"` + ParentContext string `yaml:"parentContext,omitempty"` + ManagedClusterNamespace string `yaml:"namespace,omitempty"` + Timeout string `yaml:"timeout,omitempty"` +} + +func factoryFromKubeConfig(path, context, timeout string) cmdutil.Factory { + kf := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag() + kf.KubeConfig = &path + kf.Context = &context + kf.Timeout = &timeout + return cmdutil.NewFactory(cmdutil.NewMatchVersionFlags(kf)) +} + +func coreV1ClientSet(f cmdutil.Factory) (corev1.CoreV1Interface, error) { + clientSet, err := f.KubernetesClientSet() + if err != nil { + return nil, err + } + return clientSet.CoreV1(), nil +} + +// Decrypt reads decrypted input with yaml nodes and extracts kubeconfigs from them +func Decrypt(to *clientcmdapi.Config, nodes []*yaml.RNode) error { + var configs []*clientcmdapi.Config + for _, node := range nodes { + if node.GetApiVersion() == kubeconfigAPIVersion && node.GetKind() == kubeconfigKind { + nodeMap, err := node.Map() + if err != nil { + return err + } + + if cfgData, ok := nodeMap[kubeconfigKey]; ok { + data, err := yaml.Marshal(cfgData) + if err != nil { + return err + } + + cfg, err := clientcmd.Load(data) + if err != nil { + return err + } + + configs = append(configs, cfg) + } + } + } + + return Merge(configs, to) +} + +// Merge merges multiple kubeconfigs into dst one +func Merge(kubeconfigs []*clientcmdapi.Config, dst *clientcmdapi.Config) error { + // first merge all of our maps + mapConfig := clientcmdapi.NewConfig() + for _, kubeconfig := range kubeconfigs { + if err := mergo.Merge(mapConfig, kubeconfig, mergo.WithOverride); err != nil { + return err + } + } + + // merge all of the struct values in the reverse order so that priority is given correctly + // errors are not added to the list the second time + nonMapConfig := clientcmdapi.NewConfig() + for i := len(kubeconfigs) - 1; i >= 0; i-- { + kubeconfig := kubeconfigs[i] + if err := mergo.Merge(nonMapConfig, kubeconfig, mergo.WithOverride); err != nil { + return err + } + } + + // since values are overwritten, but maps values are not, we can merge the non-map config on top of the map config and + // get the values we expect. + config := clientcmdapi.NewConfig() + if err := mergo.Merge(config, mapConfig, mergo.WithOverride); err != nil { + return err + } + if err := mergo.Merge(config, nonMapConfig, mergo.WithOverride); err != nil { + return err + } + + *dst = *config + return nil +} + +// FromSecret returns KubeSource type, uses client interface to kubernetes cluster +func (o *CAPIOptions) FromSecret(to *clientcmdapi.Config) error { + client, err := coreV1ClientSet(factoryFromKubeConfig(kubeconfigFile, o.ParentContext, "30s")) + if err != nil { + return err + } + + if o.ManagedClusterName == "" { + return fmt.Errorf("clusterName is empty") + } + if o.ManagedClusterNamespace == "" { + o.ManagedClusterNamespace = "default" + } + + data, exist, secretName := new([]byte), new(bool), fmt.Sprintf("%s-kubeconfig", o.ManagedClusterName) + fn := func() (bool, error) { + secret, err := client.Secrets(o.ManagedClusterNamespace).Get(context.Background(), secretName, metav1.GetOptions{}) + if err != nil { + fmt.Printf("get kubeconfig from secret failed, retrying, reason: %v", err) + return false, nil + } + + if *data, *exist = secret.Data["value"]; *exist && len(*data) > 0 { + return true, nil + } + return true, fmt.Errorf("malformed config: %s", o.ManagedClusterName) + } + + duration, err := time.ParseDuration(o.Timeout) + if err != nil || duration == 0 { + duration = defaultTimeout + } + + if err = wait.PollImmediate(time.Second, duration, fn); err != nil { + return err + } + + cfg, err := clientcmd.Load(*data) + if err != nil { + return err + } + return Merge([]*clientcmdapi.Config{cfg, to}, to) +} + +func save(config *clientcmdapi.Config) error { + data, err := clientcmd.Write(*config) + if err != nil { + return err + } + return ioutil.WriteFile(kubeconfigFile, data, 0600) +} + +// Run prepares config, applier and performs apply process +func (c *Options) Run(nodes []*yaml.RNode) ([]*yaml.RNode, error) { + config, err := clientcmd.LoadFromFile(kubeconfigFile) + if err != nil { + return nil, err + } + + fmt.Fprintf(os.Stderr, "config: %v\n", *c) + switch c.Action { + case "decrypt": + if err := Decrypt(config, nodes); err != nil { + return nil, err + } + case "get": + if err := c.CapiOptions.FromSecret(config); err != nil { + return nil, err + } + case "show": + if c.Context != "" { + config.CurrentContext = c.Context + if err := clientcmdapi.MinifyConfig(config); err != nil { + return nil, err + } + } + + data, err := clientcmd.Write(*config) + if err != nil { + return nil, err + } + + _, err = fmt.Fprintf(os.Stderr, "%s\n", string(data)) + return nil, err + case "remove": + // TBD + } + return nil, save(config) +} + +func main() { + fmt.Fprintf(os.Stderr, "welcome to kubeconfig manager!\n") + cfg := &Options{} + cmd := command.Build(framework.SimpleProcessor{Filter: kio.FilterFunc(cfg.Run), Config: cfg}, + command.StandaloneDisabled, false) + + if err := cmd.Execute(); err != nil { + _, _ = fmt.Fprintln(cmd.ErrOrStderr()) + os.Exit(1) + } +} diff --git a/manifests/function/phase-helpers/merge_kubeconfig/kubectl_merge_kubeconfig.sh b/krm-functions/kubeconfig-manager/local-resource/example-use.yaml similarity index 58% rename from manifests/function/phase-helpers/merge_kubeconfig/kubectl_merge_kubeconfig.sh rename to krm-functions/kubeconfig-manager/local-resource/example-use.yaml index a9095717f..613e62d05 100644 --- a/manifests/function/phase-helpers/merge_kubeconfig/kubectl_merge_kubeconfig.sh +++ b/krm-functions/kubeconfig-manager/local-resource/example-use.yaml @@ -1,5 +1,3 @@ -#!/bin/sh - # 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 @@ -12,9 +10,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -ex - -KUBECONFIG=${KUBECONFIG}:${TGT_KUBECONFIG} kubectl config view --flatten > /tmp/kubeconfig-trans - -cat /tmp/kubeconfig-trans > ${TGT_KUBECONFIG} - +--- +apiVersion: airshipit.org/v1alpha1 +kind: GenericContainer +metadata: + name: get-kubeconfig + labels: + airshipit.org/deploy-k8s: "false" +spec: + type: krm + image: quay.io/airshipit/kubeconfig-manager:latest + hostNetwork: true + mounts: + - type: bind + src: ~/.kube/config + dst: /kubeconfig + rw: true +config: | + action: get + contextName: target-cluster diff --git a/manifests/function/airshipctl-schemas/airshipit.org_clusterctls.yaml b/manifests/function/airshipctl-schemas/airshipit.org_clusterctls.yaml index 7ec238faa..3127c67c5 100644 --- a/manifests/function/airshipctl-schemas/airshipit.org_clusterctls.yaml +++ b/manifests/function/airshipctl-schemas/airshipit.org_clusterctls.yaml @@ -35,6 +35,8 @@ spec: of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string + context: + type: string images: additionalProperties: description: ImageMeta is part of clusterctl config @@ -73,6 +75,8 @@ spec: object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string + kubeconfig: + type: string metadata: type: object move-options: @@ -82,6 +86,8 @@ spec: description: Namespace where the objects describing the workload cluster exists. If unspecified, the current namespace will be used. type: string + targetContext: + type: string type: object providers: items: diff --git a/manifests/function/airshipctl-schemas/airshipit.org_clustermaps.yaml b/manifests/function/airshipctl-schemas/airshipit.org_clustermaps.yaml deleted file mode 100644 index e6576d542..000000000 --- a/manifests/function/airshipctl-schemas/airshipit.org_clustermaps.yaml +++ /dev/null @@ -1,102 +0,0 @@ - ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.6.2 - creationTimestamp: null - name: clustermaps.airshipit.org -spec: - group: airshipit.org - names: - kind: ClusterMap - listKind: ClusterMapList - plural: clustermaps - singular: clustermap - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: ClusterMap represents cluster defined for this manifest - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - map: - additionalProperties: - description: Cluster uniquely identifies a cluster and its parent cluster - properties: - kubeconfigSources: - description: KubeconfigContext is the context in kubeconfig, default - is equals to clusterMap key - items: - description: KubeconfigSource describes source of the kubeconfig - properties: - bundle: - description: KubeconfigSourceBundle get kubeconfig from bundle - properties: - contextName: - type: string - type: object - clusterAPI: - description: KubeconfigSourceClusterAPI get kubeconfig from - clusterAPI parent cluster - properties: - clusterNamespacedName: - description: NamespacedName is a name combined with namespace - to uniquely identify objects - properties: - name: - type: string - namespace: - type: string - type: object - timeout: - type: string - type: object - filesystem: - description: KubeconfigSourceFilesystem get kubeconfig from - filesystem path - properties: - contextName: - type: string - path: - type: string - type: object - type: - description: KubeconfigSourceType type of source - type: string - required: - - type - type: object - type: array - parent: - description: Parent is a key in ClusterMap.Map that identifies the - name of the parent(management) cluster - type: string - required: - - kubeconfigSources - type: object - description: Keys in this map MUST correspond to context names in kubeconfigs - provided - type: object - metadata: - type: object - type: object - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/manifests/function/phase-helpers/kustomization.yaml b/manifests/function/phase-helpers/kustomization.yaml index e691018c2..2cb6e2738 100644 --- a/manifests/function/phase-helpers/kustomization.yaml +++ b/manifests/function/phase-helpers/kustomization.yaml @@ -12,5 +12,4 @@ resources: - wait_bmh - wait_label_node - check_ingress_ctrl -- merge_kubeconfig - wait_machines_ready diff --git a/manifests/function/phase-helpers/merge_kubeconfig/kustomization.yaml b/manifests/function/phase-helpers/merge_kubeconfig/kustomization.yaml deleted file mode 100644 index 0e466de6b..000000000 --- a/manifests/function/phase-helpers/merge_kubeconfig/kustomization.yaml +++ /dev/null @@ -1,6 +0,0 @@ -configMapGenerator: -- name: merge-kubeconfig - options: - disableNameSuffixHash: true - files: - - script=kubectl_merge_kubeconfig.sh diff --git a/manifests/phases/cluster-map.yaml b/manifests/phases/cluster-map.yaml deleted file mode 100644 index 6ac4d336f..000000000 --- a/manifests/phases/cluster-map.yaml +++ /dev/null @@ -1,32 +0,0 @@ ---- -apiVersion: airshipit.org/v1alpha1 -kind: ClusterMap -metadata: - labels: - airshipit.org/deploy-k8s: "false" - name: main-map -map: - target-cluster: - parent: ephemeral-cluster - kubeconfigSources: - - type: "filesystem" - filesystem: - path: ~/.airship/kubeconfig - contextName: target-cluster - - type: "bundle" - bundle: - contextName: target-cluster - - type: "clusterAPI" - clusterAPI: - clusterNamespacedName: - name: target-cluster - namespace: target-infra - ephemeral-cluster: - kubeconfigSources: - - type: "filesystem" - filesystem: - path: ~/.airship/kubeconfig - contextName: ephemeral-cluster - - type: "bundle" - bundle: - contextName: ephemeral-cluster diff --git a/manifests/phases/executors.yaml b/manifests/phases/executors.yaml index 0a2dc3139..b4561b9f0 100644 --- a/manifests/phases/executors.yaml +++ b/manifests/phases/executors.yaml @@ -78,6 +78,7 @@ metadata: name: clusterctl_move move-options: namespace: target-infra + targetContext: target-cluster action: move --- apiVersion: airshipit.org/v1alpha1 @@ -608,19 +609,19 @@ metadata: airshipit.org/deploy-k8s: "false" spec: type: krm - image: quay.io/airshipit/toolbox:latest + image: quay.io/airshipit/kubeconfig-manager:latest hostNetwork: true - envVars: - - TGT_KUBECONFIG=/tmp-kubeconfig mounts: - type: bind src: ~/.airship/kubeconfig - dst: /tmp-kubeconfig + dst: /kubeconfig rw: true -configRef: - kind: ConfigMap - name: merge-kubeconfig - apiVersion: v1 +config: | + action: get + capiOptions: + clusterName: target-cluster + parentContext: ephemeral-cluster + namespace: target-infra --- apiVersion: airshipit.org/v1alpha1 kind: GenericContainer @@ -636,3 +637,21 @@ configRef: kind: ConfigMap name: wait_machines_ready apiVersion: v1 +--- +apiVersion: airshipit.org/v1alpha1 +kind: GenericContainer +metadata: + name: kubeconfig-decrypt + labels: + airshipit.org/deploy-k8s: "false" +spec: + type: krm + image: quay.io/airshipit/kubeconfig-manager:latest + hostNetwork: true + mounts: + - type: bind + src: ~/.airship/kubeconfig + dst: /kubeconfig + rw: true +config: | + action: decrypt diff --git a/manifests/phases/kustomization.yaml b/manifests/phases/kustomization.yaml index 7f6abb122..bfb44422a 100644 --- a/manifests/phases/kustomization.yaml +++ b/manifests/phases/kustomization.yaml @@ -1,7 +1,6 @@ resources: - phases.yaml - executors.yaml - - cluster-map.yaml - ../function/clusterctl # Scripts for generic containers - ../function/phase-helpers diff --git a/manifests/phases/phases.yaml b/manifests/phases/phases.yaml index 44ab8cbfc..1c87f4d79 100644 --- a/manifests/phases/phases.yaml +++ b/manifests/phases/phases.yaml @@ -12,6 +12,17 @@ config: --- apiVersion: airshipit.org/v1alpha1 kind: Phase +metadata: + name: secret-get +config: + executorRef: + apiVersion: airshipit.org/v1alpha1 + kind: GenericContainer + name: kubeconfig-decrypt + documentEntryPoint: kubeconfig +--- +apiVersion: airshipit.org/v1alpha1 +kind: Phase metadata: name: initinfra-networking-ephemeral clusterName: ephemeral-cluster @@ -123,7 +134,7 @@ apiVersion: airshipit.org/v1alpha1 kind: Phase metadata: name: clusterctl-move - clusterName: target-cluster + clusterName: ephemeral-cluster config: siteWideKubeconfig: true executorRef: @@ -493,7 +504,6 @@ apiVersion: airshipit.org/v1alpha1 kind: Phase metadata: name: kubectl-merge-kubeconfig - clusterName: target-cluster config: executorRef: apiVersion: airshipit.org/v1alpha1 diff --git a/manifests/site/docker-test-site/phases/cluster_map_patch.yaml b/manifests/site/docker-test-site/phases/cluster_map_patch.yaml deleted file mode 100644 index 0126559c6..000000000 --- a/manifests/site/docker-test-site/phases/cluster_map_patch.yaml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: airshipit.org/v1alpha1 -kind: ClusterMap -metadata: - labels: - airshipit.org/deploy-k8s: "false" - name: main-map -map: - target-cluster: - parent: ephemeral-cluster - kubeconfigSources: - - type: "filesystem" - filesystem: - path: ~/.airship/kubeconfig - contextName: target-cluster - - type: "bundle" - bundle: - contextName: target-cluster - - type: "clusterAPI" - clusterAPI: - clusterNamespacedName: - name: target-cluster - namespace: target-infra diff --git a/manifests/site/docker-test-site/phases/kustomization.yaml b/manifests/site/docker-test-site/phases/kustomization.yaml index 03bf4f247..5b91945b3 100644 --- a/manifests/site/docker-test-site/phases/kustomization.yaml +++ b/manifests/site/docker-test-site/phases/kustomization.yaml @@ -10,7 +10,6 @@ patchesJson6902: path: infrastructure-providers.json patchesStrategicMerge: - plan_patch.yaml - - cluster_map_patch.yaml - executor_patch.yaml transformers: - ../../../function/clusterctl/replacements diff --git a/manifests/site/test-site/phases/kustomization.yaml b/manifests/site/test-site/phases/kustomization.yaml index 6a8c3bb0e..e9a8b6493 100644 --- a/manifests/site/test-site/phases/kustomization.yaml +++ b/manifests/site/test-site/phases/kustomization.yaml @@ -1,5 +1,4 @@ resources: - - ../kubeconfig - ../../../type/gating/phases - ../target/catalogues - catalogue.yaml diff --git a/pkg/api/v1alpha1/cluster_map_types.go b/pkg/api/v1alpha1/cluster_map_types.go deleted file mode 100644 index d70f66f2f..000000000 --- a/pkg/api/v1alpha1/cluster_map_types.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - 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 v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +kubebuilder:object:root=true - -// ClusterMap represents cluster defined for this manifest -type ClusterMap struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - // Keys in this map MUST correspond to context names in kubeconfigs provided - Map map[string]*Cluster `json:"map,omitempty"` -} - -// Cluster uniquely identifies a cluster and its parent cluster -type Cluster struct { - // Parent is a key in ClusterMap.Map that identifies the name of the parent(management) cluster - Parent string `json:"parent,omitempty"` - // KubeconfigContext is the context in kubeconfig, default is equals to clusterMap key - Sources []KubeconfigSource `json:"kubeconfigSources"` -} - -// KubeconfigSource describes source of the kubeconfig -type KubeconfigSource struct { - Type KubeconfigSourceType `json:"type"` - FileSystem KubeconfigSourceFilesystem `json:"filesystem,omitempty"` - Bundle KubeconfigSourceBundle `json:"bundle,omitempty"` - ClusterAPI KubeconfigSourceClusterAPI `json:"clusterAPI,omitempty"` -} - -// KubeconfigSourceType type of source -type KubeconfigSourceType string - -const ( - // KubeconfigSourceTypeFilesystem is used when you want kubeconfig to be taken from local filesystem - KubeconfigSourceTypeFilesystem KubeconfigSourceType = "filesystem" - // KubeconfigSourceTypeBundle use config document bundle to get kubeconfig - KubeconfigSourceTypeBundle KubeconfigSourceType = "bundle" - // KubeconfigSourceTypeClusterAPI use ClusterAPI to get kubeconfig, parent cluster must be specified - KubeconfigSourceTypeClusterAPI KubeconfigSourceType = "clusterAPI" -) - -// KubeconfigSourceFilesystem get kubeconfig from filesystem path -type KubeconfigSourceFilesystem struct { - Path string `json:"path,omitempty"` - Context string `json:"contextName,omitempty"` -} - -// KubeconfigSourceClusterAPI get kubeconfig from clusterAPI parent cluster -type KubeconfigSourceClusterAPI struct { - NamespacedName `json:"clusterNamespacedName,omitempty"` - Timeout string `json:"timeout,omitempty"` -} - -// KubeconfigSourceBundle get kubeconfig from bundle -type KubeconfigSourceBundle struct { - Context string `json:"contextName,omitempty"` -} - -// NamespacedName is a name combined with namespace to uniquely identify objects -type NamespacedName struct { - Name string `json:"name,omitempty"` - Namespace string `json:"namespace,omitempty"` -} - -// DefaultClusterMap can be used to safely unmarshal ClusterMap object without nil pointers -func DefaultClusterMap() *ClusterMap { - return &ClusterMap{ - Map: make(map[string]*Cluster), - } -} diff --git a/pkg/api/v1alpha1/clusterctl_types.go b/pkg/api/v1alpha1/clusterctl_types.go index 0e154b161..8ec8b98c6 100644 --- a/pkg/api/v1alpha1/clusterctl_types.go +++ b/pkg/api/v1alpha1/clusterctl_types.go @@ -27,6 +27,8 @@ type Clusterctl struct { Providers []*Provider `json:"providers,omitempty"` Action ActionType `json:"action,omitempty"` + Kubeconfig string `json:"kubeconfig,omitempty"` + Context string `json:"context,omitempty"` InitOptions *InitOptions `json:"init-options,omitempty"` MoveOptions *MoveOptions `json:"move-options,omitempty"` // AdditionalComponentVariables are variables that will be available to clusterctl @@ -98,7 +100,8 @@ const ( type MoveOptions struct { // Namespace where the objects describing the workload cluster exists. If unspecified, the current // namespace will be used. - Namespace string `json:"namespace,omitempty"` + Namespace string `json:"namespace,omitempty"` + TargetContext string `json:"targetContext,omitempty"` } // DefaultClusterctl can be used to safely unmarshal Clusterctl object without nil pointers diff --git a/pkg/api/v1alpha1/groupversion_info.go b/pkg/api/v1alpha1/groupversion_info.go index 8c6a1f41f..0800ae7b7 100644 --- a/pkg/api/v1alpha1/groupversion_info.go +++ b/pkg/api/v1alpha1/groupversion_info.go @@ -47,7 +47,6 @@ func init() { &KubeConfig{}, &KubernetesApply{}, &IsoConfiguration{}, - &ClusterMap{}, &ReplacementTransformer{}, &Templater{}, &BootConfiguration{}, diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go index 40f644a7d..0e2d1d441 100644 --- a/pkg/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -376,66 +376,6 @@ func (in ChartSpec) DeepCopy() ChartSpec { return *out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Cluster) DeepCopyInto(out *Cluster) { - *out = *in - if in.Sources != nil { - in, out := &in.Sources, &out.Sources - *out = make([]KubeconfigSource, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster. -func (in *Cluster) DeepCopy() *Cluster { - if in == nil { - return nil - } - out := new(Cluster) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterMap) DeepCopyInto(out *ClusterMap) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.Map != nil { - in, out := &in.Map, &out.Map - *out = make(map[string]*Cluster, len(*in)) - for key, val := range *in { - var outVal *Cluster - if val == nil { - (*out)[key] = nil - } else { - in, out := &val, &outVal - *out = new(Cluster) - (*in).DeepCopyInto(*out) - } - (*out)[key] = outVal - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterMap. -func (in *ClusterMap) DeepCopy() *ClusterMap { - if in == nil { - return nil - } - out := new(ClusterMap) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterMap) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Clusterctl) DeepCopyInto(out *Clusterctl) { *out = *in @@ -1052,70 +992,6 @@ func (in *KubeConfig) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *KubeconfigSource) DeepCopyInto(out *KubeconfigSource) { - *out = *in - out.FileSystem = in.FileSystem - out.Bundle = in.Bundle - out.ClusterAPI = in.ClusterAPI -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeconfigSource. -func (in *KubeconfigSource) DeepCopy() *KubeconfigSource { - if in == nil { - return nil - } - out := new(KubeconfigSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *KubeconfigSourceBundle) DeepCopyInto(out *KubeconfigSourceBundle) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeconfigSourceBundle. -func (in *KubeconfigSourceBundle) DeepCopy() *KubeconfigSourceBundle { - if in == nil { - return nil - } - out := new(KubeconfigSourceBundle) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *KubeconfigSourceClusterAPI) DeepCopyInto(out *KubeconfigSourceClusterAPI) { - *out = *in - out.NamespacedName = in.NamespacedName -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeconfigSourceClusterAPI. -func (in *KubeconfigSourceClusterAPI) DeepCopy() *KubeconfigSourceClusterAPI { - if in == nil { - return nil - } - out := new(KubeconfigSourceClusterAPI) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *KubeconfigSourceFilesystem) DeepCopyInto(out *KubeconfigSourceFilesystem) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeconfigSourceFilesystem. -func (in *KubeconfigSourceFilesystem) DeepCopy() *KubeconfigSourceFilesystem { - if in == nil { - return nil - } - out := new(KubeconfigSourceFilesystem) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubernetesApply) DeepCopyInto(out *KubernetesApply) { *out = *in @@ -1241,21 +1117,6 @@ func (in *MoveOptions) DeepCopy() *MoveOptions { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NamespacedName) DeepCopyInto(out *NamespacedName) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedName. -func (in *NamespacedName) DeepCopy() *NamespacedName { - if in == nil { - return nil - } - out := new(NamespacedName) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Network) DeepCopyInto(out *Network) { *out = *in diff --git a/pkg/cluster/clustermap/errors.go b/pkg/cluster/clustermap/errors.go deleted file mode 100644 index bfa93e686..000000000 --- a/pkg/cluster/clustermap/errors.go +++ /dev/null @@ -1,51 +0,0 @@ -/* - 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 clustermap - -import ( - "fmt" - - "opendev.org/airship/airshipctl/pkg/api/v1alpha1" -) - -// ErrParentNotFound returned when requested cluster is not defined or doesn't have a parent -type ErrParentNotFound struct { - Child string - Map *v1alpha1.ClusterMap -} - -func (e ErrParentNotFound) Error() string { - return fmt.Sprintf("failed to find a parent for cluster %s in cluster map %v", e.Child, e.Map) -} - -// ErrClusterNotInMap returned when requested cluster is not defined in cluster map -type ErrClusterNotInMap struct { - Child string - Map *v1alpha1.ClusterMap -} - -func (e ErrClusterNotInMap) Error() string { - return fmt.Sprintf("cluster '%s' is not defined in cluster map %v", e.Child, e.Map) -} - -// ErrClusterCircularDependency returned for circular dependencies -type ErrClusterCircularDependency struct { - Parent string - Map *v1alpha1.ClusterMap -} - -func (e ErrClusterCircularDependency) Error() string { - return fmt.Sprintf("%v contains cluster referenced as both parent and child: %s", e.Map, e.Parent) -} diff --git a/pkg/cluster/clustermap/map.go b/pkg/cluster/clustermap/map.go deleted file mode 100644 index c5b55dd01..000000000 --- a/pkg/cluster/clustermap/map.go +++ /dev/null @@ -1,148 +0,0 @@ -/* - 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 clustermap - -import ( - "fmt" - "io" - "os" - "text/tabwriter" - - "opendev.org/airship/airshipctl/pkg/api/v1alpha1" -) - -// DefaultClusterAPIObjNamespace is a default namespace used for cluster-api cluster object -const DefaultClusterAPIObjNamespace = "target-infra" - -// WriteOptions has format in which we want to print the output(table/yaml/cluster name) -type WriteOptions struct { - Format string -} - -// ClusterMap interface that allows to list all clusters, find its parent, namespace, -// check if dynamic kubeconfig is enabled. -// TODO use typed cluster names -type ClusterMap interface { - ParentCluster(string) (string, error) - ValidateClusterMap() error - AllClusters() []string - ClusterKubeconfigContext(string) (string, error) - Sources(string) ([]v1alpha1.KubeconfigSource, error) - Write(io.Writer, WriteOptions) error -} - -// clusterMap allows to view clusters and relationship between them -type clusterMap struct { - apiMap *v1alpha1.ClusterMap -} - -var _ ClusterMap = clusterMap{} - -// NewClusterMap returns ClusterMap interface -func NewClusterMap(cMap *v1alpha1.ClusterMap) ClusterMap { - return clusterMap{apiMap: cMap} -} - -// ParentCluster finds a parent cluster for provided child -func (cm clusterMap) ParentCluster(child string) (string, error) { - currentCluster, exists := cm.apiMap.Map[child] - if !exists { - return "", ErrClusterNotInMap{Child: child, Map: cm.apiMap} - } - if currentCluster.Parent == "" { - return "", ErrParentNotFound{Child: child, Map: cm.apiMap} - } - return currentCluster.Parent, nil -} - -// Validates a clustermap has valid parent-child map structure -func (cm clusterMap) ValidateClusterMap() error { - clusterMap := cm.AllClusters() - for _, childCluster := range clusterMap { - var parentClusters []string - var currentChild string = childCluster - for { - currentCluster, _ := cm.apiMap.Map[currentChild] - for _, c := range parentClusters { - if c == currentCluster.Parent { - // Quit on parent whos also child - return ErrClusterCircularDependency{Parent: childCluster, Map: cm.apiMap} - } - } - // Quit loop once top level of current cluster is reached - if currentCluster.Parent == "" { - break - } - parentClusters = append(parentClusters, currentCluster.Parent) - currentChild = currentCluster.Parent - } - } - - // Return success if there are no conflicts - return nil -} - -// AllClusters returns all clusters in a map -func (cm clusterMap) AllClusters() []string { - clusters := []string{} - for k := range cm.apiMap.Map { - clusters = append(clusters, k) - } - return clusters -} - -// ClusterKubeconfigContext returns name of the context in kubeconfig corresponding to a given cluster -func (cm clusterMap) ClusterKubeconfigContext(clusterName string) (string, error) { - _, exists := cm.apiMap.Map[clusterName] - - if !exists { - return "", ErrClusterNotInMap{Map: cm.apiMap, Child: clusterName} - } - - return clusterName, nil -} - -func (cm clusterMap) Sources(clusterName string) ([]v1alpha1.KubeconfigSource, error) { - cluster, ok := cm.apiMap.Map[clusterName] - if !ok { - return nil, ErrClusterNotInMap{Child: clusterName, Map: cm.apiMap} - } - return cluster.Sources, nil -} - -// Write prints the cluster list in table/name output format -func (cm clusterMap) Write(writer io.Writer, wo WriteOptions) error { - if wo.Format == "table" { - w := tabwriter.NewWriter(os.Stdout, 20, 8, 1, ' ', 0) - fmt.Fprintf(w, "NAME\tKUBECONFIG CONTEXT\tPARENT CLUSTER\n") - for clustername, cluster := range cm.apiMap.Map { - kubeconfig, err := cm.ClusterKubeconfigContext(clustername) - if err != nil { - return err - } - fmt.Fprintf(w, "%s\t%s\t%s\n", - clustername, kubeconfig, cluster.Parent) - } - w.Flush() - } else if wo.Format == "name" { - clusterList := cm.AllClusters() - for _, clusterName := range clusterList { - if _, err := writer.Write([]byte(clusterName + "\n")); err != nil { - return err - } - } - } - return nil -} diff --git a/pkg/cluster/clustermap/map_test.go b/pkg/cluster/clustermap/map_test.go deleted file mode 100644 index 009ab7781..000000000 --- a/pkg/cluster/clustermap/map_test.go +++ /dev/null @@ -1,209 +0,0 @@ -/* - 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 clustermap_test - -import ( - "bufio" - "bytes" - "io" - "io/ioutil" - "os" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "opendev.org/airship/airshipctl/pkg/api/v1alpha1" - "opendev.org/airship/airshipctl/pkg/cluster/clustermap" -) - -func TestClusterMap(t *testing.T) { - targetCluster := "target" - ephemeraCluster := "ephemeral" - workloadCluster := "workload" - workloadClusterNoParent := "workload without parent" - workloadClusterAPIRefName := "workload-cluster-api" - workloadClusterAPIRefNamespace := "some-namespace" - apiMap := &v1alpha1.ClusterMap{ - Map: map[string]*v1alpha1.Cluster{ - targetCluster: { - Parent: ephemeraCluster, - Sources: []v1alpha1.KubeconfigSource{ - { - Type: v1alpha1.KubeconfigSourceTypeBundle, - }, - }, - }, - ephemeraCluster: {}, - workloadCluster: { - Parent: targetCluster, - Sources: []v1alpha1.KubeconfigSource{ - { - Type: v1alpha1.KubeconfigSourceTypeClusterAPI, - ClusterAPI: v1alpha1.KubeconfigSourceClusterAPI{ - NamespacedName: v1alpha1.NamespacedName{ - Name: workloadClusterAPIRefName, - Namespace: workloadClusterAPIRefNamespace, - }, - }, - }, - }, - }, - workloadClusterNoParent: { - Sources: []v1alpha1.KubeconfigSource{ - { - Type: v1alpha1.KubeconfigSourceTypeClusterAPI, - }, - }, - }, - }, - } - cMap := clustermap.NewClusterMap(apiMap) - require.NotNil(t, cMap) - - t.Run("ephemeral parent", func(t *testing.T) { - parent, err := cMap.ParentCluster(targetCluster) - assert.NoError(t, err) - assert.Equal(t, ephemeraCluster, parent) - }) - - t.Run("no cluster found", func(t *testing.T) { - parent, err := cMap.ParentCluster("does not exist") - assert.Error(t, err) - assert.Equal(t, "", parent) - }) - - t.Run("target parent", func(t *testing.T) { - parent, err := cMap.ParentCluster(workloadCluster) - assert.NoError(t, err) - assert.Equal(t, targetCluster, parent) - }) - - t.Run("ephemeral no parent", func(t *testing.T) { - parent, err := cMap.ParentCluster(ephemeraCluster) - assert.Error(t, err) - assert.Equal(t, "", parent) - }) - - t.Run("Validate Circular Clustermap", func(t *testing.T) { - // Create new map with circular dependency - circularAPIMap := &v1alpha1.ClusterMap{ - Map: map[string]*v1alpha1.Cluster{}, - } - for key, value := range apiMap.Map { - newValue := *value - circularAPIMap.Map[key] = &newValue - } - circularAPIMap.Map["ephemeral"].Parent = "workload" - cMapCircular := clustermap.NewClusterMap(circularAPIMap) - err := cMapCircular.ValidateClusterMap() - assert.Error(t, err) - }) - - t.Run("Validate all Clustermaps", func(t *testing.T) { - // Check child clusterID against map of parent clusterID map - err := cMap.ValidateClusterMap() - assert.NoError(t, err) - }) - - t.Run("all clusters", func(t *testing.T) { - clusters := cMap.AllClusters() - assert.Len(t, clusters, 4) - }) - - t.Run("kubeconfig context", func(t *testing.T) { - kubeContext, err := cMap.ClusterKubeconfigContext(targetCluster) - assert.NoError(t, err) - assert.Equal(t, targetCluster, kubeContext) - }) - - t.Run("kubeconfig context error", func(t *testing.T) { - _, err := cMap.ClusterKubeconfigContext("does not exist") - assert.Error(t, err) - }) - - t.Run("sources match", func(t *testing.T) { - sources, err := cMap.Sources(workloadCluster) - assert.NoError(t, err) - expectedSources := apiMap.Map[workloadCluster].Sources - assert.Equal(t, expectedSources, sources) - }) - - t.Run("sources no cluster found", func(t *testing.T) { - _, err := cMap.Sources("does not exist") - assert.Error(t, err) - }) -} - -func Test_clusterMap_Write(t *testing.T) { - var b bytes.Buffer - wr := bufio.NewWriter(&b) - targetCluster := "target" - ephemeraCluster := "ephemeral" - apiMap := &v1alpha1.ClusterMap{ - Map: map[string]*v1alpha1.Cluster{ - targetCluster: { - Parent: ephemeraCluster, - }, - }, - } - tests := []struct { - name string - wo clustermap.WriteOptions - wantWriter string - expectedOut string - expectedErr string - writer io.Writer - }{ - { - name: "success table", - wo: clustermap.WriteOptions{Format: "table"}, - expectedOut: "NAME KUBECONFIG CONTEXT PARENT CLUSTER" + - "\ntarget target ephemeral\n", - writer: wr, - }, - { - name: "writer nil", - wo: clustermap.WriteOptions{Format: "table"}, - writer: nil, - expectedOut: "", - }, - } - rStdout := os.Stdout - r, w, err := os.Pipe() - if err != nil { - require.Error(t, err) - } - os.Stdout = w - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cMap := clustermap.NewClusterMap(apiMap) - err := cMap.Write(tt.writer, tt.wo) - w.Close() - if tt.expectedErr != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.expectedErr) - } else { - assert.NoError(t, err) - } - out, err := ioutil.ReadAll(r) - if err != nil { - require.Error(t, err) - } - os.Stdout = rStdout - assert.Equal(t, tt.expectedOut, string(out)) - }) - } -} diff --git a/pkg/cluster/command.go b/pkg/cluster/command.go deleted file mode 100755 index 1aefcef6e..000000000 --- a/pkg/cluster/command.go +++ /dev/null @@ -1,67 +0,0 @@ -/* - 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 cluster - -import ( - "io" - - "opendev.org/airship/airshipctl/pkg/config" - "opendev.org/airship/airshipctl/pkg/k8s/kubeconfig" - "opendev.org/airship/airshipctl/pkg/phase" -) - -// GetKubeconfigCommand holds options for get kubeconfig command -type GetKubeconfigCommand struct { - ClusterNames []string - File string - Merge bool -} - -// RunE creates new kubeconfig interface object from secret, options hold the writer and merge(bool) -// to merge the kubeconfig. Writer in options is ignored if a file is provided. -func (cmd *GetKubeconfigCommand) RunE(cfgFactory config.Factory, writer io.Writer) error { - cfg, err := cfgFactory() - if err != nil { - return err - } - - helper, err := phase.NewHelper(cfg) - if err != nil { - return err - } - - cMap, err := helper.ClusterMap() - if err != nil { - return err - } - - var siteWide bool - if len(cmd.ClusterNames) == 0 { - siteWide = true - } - - kubeconf := kubeconfig.NewBuilder(). - WithBundle(helper.PhaseConfigBundle()). - WithClusterMap(cMap). - WithClusterNames(cmd.ClusterNames...). - WithTempRoot(helper.WorkDir()). - SiteWide(siteWide). - Build() - - if cmd.File != "" { - return kubeconf.WriteFile(cmd.File, kubeconfig.WriteOptions{Merge: cmd.Merge}) - } - return kubeconf.Write(writer) -} diff --git a/pkg/config/options.go b/pkg/config/options.go index 8c5044e21..0fe257f9e 100644 --- a/pkg/config/options.go +++ b/pkg/config/options.go @@ -20,8 +20,9 @@ import ( "sort" "strings" - "k8s.io/cli-runtime/pkg/printers" "sigs.k8s.io/yaml" + + "opendev.org/airship/airshipctl/pkg/util" ) // ContextOptions holds all configurable options for context @@ -104,7 +105,7 @@ func (o *ContextOptions) Print(cfg *Config, w io.Writer) error { } fmt.Fprintf(w, string(data)) case "table": - out := printers.GetNewTabWriter(w) + out := util.GetNewTabWriter(w) defer out.Flush() toPrint := []string{} diff --git a/pkg/container/api_test.go b/pkg/container/api_test.go index 7d904a5d6..520910eae 100644 --- a/pkg/container/api_test.go +++ b/pkg/container/api_test.go @@ -36,16 +36,6 @@ import ( const ( singleSecretBundleOutput = `--- -apiVersion: airshipit.org/v1alpha1 -kind: ClusterMap -metadata: - labels: - airshipit.org/deploy-k8s: "false" - name: main-map -map: - testCluster: {} -... ---- apiVersion: v1 kind: Secret metadata: diff --git a/pkg/k8s/kubeconfig/builder.go b/pkg/k8s/kubeconfig/builder.go deleted file mode 100644 index 1e0055c88..000000000 --- a/pkg/k8s/kubeconfig/builder.go +++ /dev/null @@ -1,316 +0,0 @@ -/* - 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 kubeconfig - -import ( - "fmt" - - corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/tools/clientcmd/api" - - "opendev.org/airship/airshipctl/pkg/api/v1alpha1" - "opendev.org/airship/airshipctl/pkg/cluster/clustermap" - "opendev.org/airship/airshipctl/pkg/document" - "opendev.org/airship/airshipctl/pkg/fs" - "opendev.org/airship/airshipctl/pkg/k8s/utils" - "opendev.org/airship/airshipctl/pkg/log" -) - -// NewBuilder returns instance of kubeconfig builder. -func NewBuilder() *Builder { - return &Builder{ - siteKubeconf: emptyConfig(), - } -} - -// Builder is an object that allows to build a kubeconfig based on various provided sources -// such as path to kubeconfig, path to bundle that should contain kubeconfig and parent cluster -type Builder struct { - siteWide bool - clusterNames []string - root string - - bundle document.Bundle - client corev1.CoreV1Interface - clusterMap clustermap.ClusterMap - fs fs.FileSystem - siteKubeconf *api.Config -} - -// WithBundle allows to set document.Bundle object that should contain kubeconfig api object -func (b *Builder) WithBundle(bundle document.Bundle) *Builder { - b.bundle = bundle - return b -} - -// WithClusterMap allows to set a parent cluster, that can be used to extract kubeconfig for target cluster -func (b *Builder) WithClusterMap(cMap clustermap.ClusterMap) *Builder { - b.clusterMap = cMap - return b -} - -// WithClusterNames allows to reach to a cluster to download kubeconfig from there -func (b *Builder) WithClusterNames(clusterNames ...string) *Builder { - b.clusterNames = clusterNames - return b -} - -// WithTempRoot allows to set temp root for kubeconfig -func (b *Builder) WithTempRoot(root string) *Builder { - b.root = root - return b -} - -// WithFilesystem allows to set filesystem -func (b *Builder) WithFilesystem(fs fs.FileSystem) *Builder { - b.fs = fs - return b -} - -// WithCoreV1Client allows to set core v1 client, use for unit tests only -func (b *Builder) WithCoreV1Client(c corev1.CoreV1Interface) *Builder { - b.client = c - return b -} - -// SiteWide allows to build kubeconfig for the entire site. -// If set to true ClusterName will be ignored, since all clusters are requested. -func (b *Builder) SiteWide(t bool) *Builder { - b.siteWide = t - return b -} - -// Build site kubeconfig, ignores, but logs, errors that happen when building individual -// kubeconfigs. We need this behavior because, some clusters may not yet be deployed -// and their kubeconfig is inaccessible yet, but will be accessible at later phases -// If builder can't build kubeconfig for specific cluster, its context will not be present -// in final kubeconfig. User of kubeconfig, will receive error stating that context doesn't exist -// To request site-wide kubeconfig use builder method SiteWide(true). -// To request a single cluster kubeconfig use methods WithClusterName("my-cluster").SiteWide(false) -// ClusterName is ignored if SiteWide(true) is used. -func (b *Builder) Build() Interface { - return NewKubeConfig(b.build, InjectFileSystem(b.fs), InjectTempRoot(b.root)) -} - -func (b *Builder) build() ([]byte, error) { - if err := b.buildKubeconfig(); err != nil { - return nil, err - } - - return clientcmd.Write(*b.siteKubeconf) -} - -func (b *Builder) buildKubeconfig() error { - log.Debugf("Getting requested kubeconfig") - for _, clusterID := range (map[bool][]string{true: b.clusterMap.AllClusters(), false: b.clusterNames})[b.siteWide] { - log.Debugf("Getting kubeconfig for cluster '%s'", clusterID) - // buildOne merges context into site kubeconfig - ctx, _, err := b.buildOne(clusterID) - if !b.siteWide && len(b.clusterNames) == 1 { - b.siteKubeconf.CurrentContext = ctx - } - if IsErrAllSourcesFailedErr(err) { - log.Debugf("All kubeconfig sources failed for cluster '%s', error '%v', skipping it", - clusterID, err) - continue - } else if err != nil { - return err - } - } - return nil -} - -func (b *Builder) buildOne(clusterID string) (string, *api.Config, error) { - destContext, err := b.clusterMap.ClusterKubeconfigContext(clusterID) - if err != nil { - return "", nil, err - } - - // use already built kubeconfig context, to avoid doing work multiple times - built, oneKubeconf := b.alreadyBuilt(destContext) - if built { - log.Debugf("Kubeconfig for cluster '%s' is already built, using it", clusterID) - return destContext, oneKubeconf, nil - } - - sources, err := b.clusterMap.Sources(clusterID) - if err != nil { - return "", nil, err - } - - for _, source := range sources { - oneKubeconf, sourceErr := b.trySource(clusterID, destContext, source) - if sourceErr == nil { - // Merge source context into site kubeconfig - log.Debugf("Merging kubecontext for cluster '%s', into site kubeconfig", clusterID) - if err = mergeContextAPI(destContext, destContext, b.siteKubeconf, oneKubeconf); err != nil { - return "", nil, err - } - return destContext, oneKubeconf, err - } - // if error, log it and ignore it. missing problem with one kubeconfig should not - // effect other clusters, which don't depend on it. If they do depend on it, their calls - // will fail because the context will be missing. Combination with a log message will make - // it clear where the problem is. - log.Debugf("Received error while trying kubeconfig source for cluster '%s', source type '%s', error '%v'", - clusterID, source.Type, sourceErr) - } - // return empty not nil kubeconfig without error. - return "", nil, &ErrAllSourcesFailed{ClusterName: clusterID} -} - -func (b *Builder) trySource(clusterID, dstContext string, source v1alpha1.KubeconfigSource) (*api.Config, error) { - var getter KubeSourceFunc - // TODO add sourceContext defaults - var sourceContext string - switch source.Type { - case v1alpha1.KubeconfigSourceTypeFilesystem: - getter = FromFile(source.FileSystem.Path, b.fs) - sourceContext = source.FileSystem.Context - case v1alpha1.KubeconfigSourceTypeBundle: - getter = FromBundle(b.bundle) - sourceContext = source.Bundle.Context - case v1alpha1.KubeconfigSourceTypeClusterAPI: - getter = b.fromClusterAPI(clusterID, source.ClusterAPI) - default: - // TODO add validation for fast fails to clustermap interface instead of this - return nil, &ErrUnknownKubeconfigSourceType{Type: string(source.Type)} - } - kubeBytes, err := getter() - if err != nil { - return nil, err - } - return extractContext(dstContext, sourceContext, kubeBytes) -} - -func (b *Builder) fromClusterAPI(clusterName string, ref v1alpha1.KubeconfigSourceClusterAPI) KubeSourceFunc { - return func() ([]byte, error) { - log.Debugf("Getting kubeconfig from cluster API for cluster '%s'", clusterName) - parentCluster, err := b.clusterMap.ParentCluster(clusterName) - if err != nil { - return nil, err - } - - parentContext, parentKubeconf, err := b.buildOne(parentCluster) - if err != nil { - return nil, err - } - - parentKubeconfig := NewKubeConfig(FromConfig(parentKubeconf), InjectFileSystem(b.fs)) - - f, cleanup, err := parentKubeconfig.GetFile() - if err != nil { - return nil, err - } - defer cleanup() - - if b.client == nil { - clientSet, err := utils.FactoryFromKubeConfig(f, parentContext, utils.SetTimeout("30s")).KubernetesClientSet() - if err != nil { - return nil, err - } - b.client = clientSet.CoreV1() - } - - log.Debugf("Getting child kubeconfig from parent, parent context '%s', parent kubeconfig '%s'", - parentContext, f) - return FromSecret(b.client, &v1alpha1.GetKubeconfigOptions{ - Timeout: ref.Timeout, - ManagedClusterNamespace: ref.Namespace, - ManagedClusterName: ref.Name, - })() - } -} - -func (b *Builder) alreadyBuilt(clusterContext string) (bool, *api.Config) { - kubeconfBytes, err := clientcmd.Write(*b.siteKubeconf) - if err != nil { - log.Debugf("Received error when converting kubeconfig to bytes, ignoring kubeconfig. Error: %v", err) - return false, nil - } - - // resulting and existing context names must be the same, otherwise error will be returned - clusterKubeconfig, err := extractContext(clusterContext, clusterContext, kubeconfBytes) - if err != nil { - log.Debugf("Received error when extracting context, ignoring kubeconfig. Error: %v", err) - return false, nil - } - - return true, clusterKubeconfig -} - -func extractContext(destContext, sourceContext string, src []byte) (*api.Config, error) { - srcKubeconf, err := clientcmd.Load(src) - if err != nil { - return nil, err - } - dstKubeconf := emptyConfig() - return dstKubeconf, mergeContextAPI(destContext, sourceContext, dstKubeconf, srcKubeconf) -} - -// merges two kubeconfigs -func mergeContextAPI(destContext, sourceContext string, dst, src *api.Config) error { - if len(src.Contexts) > 1 && sourceContext == "" { - // When more than one context, we don't know which to choose - return &ErrKubeconfigMergeFailed{ - Message: "kubeconfig has multiple contexts, don't know which to choose, " + - "please specify contextName in clusterMap cluster kubeconfig source", - } - } - - var context *api.Context - context, exists := src.Contexts[sourceContext] - switch { - case exists: - case sourceContext == "" && len(src.Contexts) == 1: - for _, context = range src.Contexts { - log.Debugf("Using context '%v' to merge kubeconfig", context) - } - default: - return &ErrKubeconfigMergeFailed{ - Message: fmt.Sprintf("source context '%s' does not exist in source kubeconfig", sourceContext), - } - } - dst.Contexts[destContext] = context - - // TODO design logic to make authinfo keys unique, they can overlap, or human error can occur - user, exists := src.AuthInfos[context.AuthInfo] - if !exists { - return &ErrKubeconfigMergeFailed{ - Message: fmt.Sprintf("user '%s' does not exist in source kubeconfig", context.AuthInfo), - } - } - dst.AuthInfos[context.AuthInfo] = user - - // TODO design logic to make cluster keys unique, they can overlap, or human error can occur - cluster, exists := src.Clusters[context.Cluster] - if !exists { - return &ErrKubeconfigMergeFailed{ - Message: fmt.Sprintf("cluster '%s' does not exist in source kubeconfig", context.Cluster), - } - } - dst.Clusters[context.Cluster] = cluster - - return nil -} - -func emptyConfig() *api.Config { - return &api.Config{ - Contexts: make(map[string]*api.Context), - AuthInfos: make(map[string]*api.AuthInfo), - Clusters: make(map[string]*api.Cluster), - } -} diff --git a/pkg/k8s/kubeconfig/builder_test.go b/pkg/k8s/kubeconfig/builder_test.go deleted file mode 100644 index a91a5f220..000000000 --- a/pkg/k8s/kubeconfig/builder_test.go +++ /dev/null @@ -1,313 +0,0 @@ -/* - 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 kubeconfig_test - -import ( - "bytes" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/tools/clientcmd" - - "opendev.org/airship/airshipctl/pkg/api/v1alpha1" - "opendev.org/airship/airshipctl/pkg/cluster/clustermap" - "opendev.org/airship/airshipctl/pkg/document" - "opendev.org/airship/airshipctl/pkg/fs" - "opendev.org/airship/airshipctl/pkg/k8s/kubeconfig" - testfs "opendev.org/airship/airshipctl/testutil/fs" -) - -const ( - testKubeconfigString = `apiVersion: v1 -kind: Config -clusters: -- cluster: - certificate-authority-data: c29tZWNlcnQK - server: https://10.23.25.101:6443 - name: child_cluster -contexts: -- context: - cluster: child_cluster - user: child_user - name: child -current-context: dummy_cluster -preferences: {} -users: -- name: child_user - user: - client-certificate-data: c29tZWNlcnQK - client-key-data: c29tZWNlcnQK` - testKubeconfigStringSecond = `apiVersion: v1 -kind: Config -clusters: -- cluster: - certificate-authority-data: c29tZWNlcnQK - server: https://10.23.25.101:6443 - name: parent_cluster -contexts: -- context: - cluster: parent_cluster - user: parent_admin - name: parent-context -current-context: dummy_cluster -preferences: {} -users: -- name: parent_admin - user: - client-certificate-data: c29tZWNlcnQK - client-key-data: c29tZWNlcnQK` -) - -func TestBuilderClusterctl(t *testing.T) { - childClusterID := "child" - parentClusterID := "parent" - parentParentClusterID := "parent-parent" - // these are values in kubeconfig.cluster - parentCluster := "parent_cluster" - parentParentCluster := "parent_parent_cluster" - childCluster := "child_cluster" - parentUser := "parent_admin" - parentParentUser := "parent_parent_admin" - childUser := "child_user" - testBundle, err := document.NewBundleByPath("testdata") - require.NoError(t, err) - kubeconfigPath := filepath.Join("testdata", "kubeconfig-12341234") - - tests := []struct { - name string - errString string - requestedClusterName string - tempRoot string - siteWide bool - - expectedContexts, expectedClusters, expectedAuthInfos []string - clusterMap clustermap.ClusterMap - client corev1.CoreV1Interface - fs fs.FileSystem - }{ - { - name: "success cluster-api not reachable", - expectedContexts: []string{parentClusterID}, - expectedClusters: []string{parentParentCluster}, - expectedAuthInfos: []string{parentParentUser}, - siteWide: true, - clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{ - Map: map[string]*v1alpha1.Cluster{ - childClusterID: { - Parent: parentClusterID, - Sources: []v1alpha1.KubeconfigSource{ - { - Type: v1alpha1.KubeconfigSourceTypeClusterAPI, - }, - }, - }, - parentClusterID: { - Sources: []v1alpha1.KubeconfigSource{ - { - Type: v1alpha1.KubeconfigSourceTypeBundle, - Bundle: v1alpha1.KubeconfigSourceBundle{ - Context: "parent_parent_context", - }, - }, - }, - }, - }, - }), - }, - { - name: "success two clusters", - expectedContexts: []string{parentClusterID, parentParentClusterID}, - expectedClusters: []string{"dummycluster_ephemeral", parentParentCluster}, - expectedAuthInfos: []string{"kubernetes-admin", parentParentUser}, - siteWide: true, - clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{ - Map: map[string]*v1alpha1.Cluster{ - parentParentClusterID: { - Sources: []v1alpha1.KubeconfigSource{ - { - Type: v1alpha1.KubeconfigSourceTypeBundle, - Bundle: v1alpha1.KubeconfigSourceBundle{ - Context: "parent_parent_context", - }, - }, - }, - }, - parentClusterID: { - Sources: []v1alpha1.KubeconfigSource{ - { - Type: v1alpha1.KubeconfigSourceTypeFilesystem, - FileSystem: v1alpha1.KubeconfigSourceFilesystem{ - Path: "testdata/kubeconfig", - Context: "dummy_cluster", - }, - }, - }, - }, - }, - }), - }, - { - name: "success three clusters cluster-api", - expectedContexts: []string{parentClusterID, childClusterID, parentParentClusterID}, - expectedClusters: []string{parentCluster, parentParentCluster, childCluster}, - expectedAuthInfos: []string{parentUser, parentParentUser, childUser}, - siteWide: true, - clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{ - Map: map[string]*v1alpha1.Cluster{ - childClusterID: { - Parent: parentClusterID, - Sources: []v1alpha1.KubeconfigSource{ - { - Type: v1alpha1.KubeconfigSourceTypeClusterAPI, - ClusterAPI: v1alpha1.KubeconfigSourceClusterAPI{ - NamespacedName: v1alpha1.NamespacedName{ - Name: childClusterID, - Namespace: "target-infra", - }, - }, - }, - }, - }, - parentClusterID: { - Sources: []v1alpha1.KubeconfigSource{ - { - Type: v1alpha1.KubeconfigSourceTypeClusterAPI, - ClusterAPI: v1alpha1.KubeconfigSourceClusterAPI{ - NamespacedName: v1alpha1.NamespacedName{ - Name: parentClusterID, - Namespace: "target-infra", - }, - }, - }, - }, - Parent: parentParentClusterID, - }, - parentParentClusterID: { - Sources: []v1alpha1.KubeconfigSource{ - { - Type: v1alpha1.KubeconfigSourceTypeBundle, - }, - }, - }, - }, - }), - tempRoot: "testdata", - fs: testfs.MockFileSystem{ - MockRemoveAll: func() error { return nil }, - MockTempFile: func(s1, s2 string) (fs.File, error) { - return testfs.TestFile{ - MockName: func() string { return kubeconfigPath }, - MockWrite: func([]byte) (int, error) { return 0, nil }, - MockClose: func() error { return nil }, - }, nil - }, - }, - client: func() MockCoreV1Interface { - ms := &SecretMockInterface{ - Mock: mock.Mock{}, - } - ms.On("Get", parentClusterID+"-kubeconfig", metav1.GetOptions{}). - Once().Return(&apiv1.Secret{Data: map[string][]byte{"value": []byte(testKubeconfigString)}}, nil) - ms.On("Get", childClusterID+"-kubeconfig", metav1.GetOptions{}). - Once().Return(&apiv1.Secret{Data: map[string][]byte{"value": []byte(testKubeconfigStringSecond)}}, nil) - mc := MockCoreV1Interface{MockSecrets: func(s string) corev1.SecretInterface { - return ms - }} - return mc - }(), - }, - { - name: "error requested cluster doesn't exist", - errString: "is not defined in cluster map", - requestedClusterName: "non-existent-cluster", - clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{ - Map: map[string]*v1alpha1.Cluster{ - parentParentClusterID: { - Sources: []v1alpha1.KubeconfigSource{ - { - Type: v1alpha1.KubeconfigSourceTypeBundle, - Bundle: v1alpha1.KubeconfigSourceBundle{ - Context: "parent_parent_context", - }, - }, - }, - }, - }, - }), - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - kube := kubeconfig.NewBuilder(). - WithClusterMap(tt.clusterMap). - WithClusterNames(tt.requestedClusterName). - WithBundle(testBundle). - WithTempRoot(tt.tempRoot). - WithCoreV1Client(tt.client). - WithFilesystem(tt.fs). - SiteWide(tt.siteWide). - Build() - require.NotNil(t, kube) - filePath, cleanup, err := kube.GetFile() - // This is needed to avoid leftovers on test failures - if cleanup != nil { - defer cleanup() - } - - if tt.errString != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.errString) - assert.Equal(t, "", filePath) - } else { - require.NoError(t, err) - assert.NotEqual(t, "", filePath) - assert.NotNil(t, cleanup) - buf := bytes.NewBuffer([]byte{}) - err := kube.Write(buf) - require.NoError(t, err) - compareResults(t, tt.expectedContexts, tt.expectedClusters, tt.expectedAuthInfos, buf.Bytes()) - } - }) - } -} - -func compareResults(t *testing.T, contexts, clusters, authInfos []string, kubeconfBytes []byte) { - t.Helper() - resultKubeconf, err := clientcmd.Load(kubeconfBytes) - require.NoError(t, err) - - assert.Len(t, resultKubeconf.Contexts, len(contexts)) - for _, name := range contexts { - assert.Contains(t, resultKubeconf.Contexts, name) - } - - assert.Len(t, resultKubeconf.AuthInfos, len(authInfos)) - for _, name := range authInfos { - assert.Contains(t, resultKubeconf.AuthInfos, name) - } - - assert.Len(t, resultKubeconf.Clusters, len(clusters)) - for _, name := range clusters { - assert.Contains(t, resultKubeconf.Clusters, name) - } -} diff --git a/pkg/k8s/kubeconfig/errors.go b/pkg/k8s/kubeconfig/errors.go deleted file mode 100644 index 81e4a8123..000000000 --- a/pkg/k8s/kubeconfig/errors.go +++ /dev/null @@ -1,67 +0,0 @@ -/* - 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 kubeconfig - -import "fmt" - -// ErrAllSourcesFailed returned when kubeconfig path is not specified -type ErrAllSourcesFailed struct { - ClusterName string -} - -func (e *ErrAllSourcesFailed) Error() string { - return fmt.Sprintf("all kubeconfig sources failed for cluster '%s'", e.ClusterName) -} - -// ErrKubeconfigMergeFailed is returned when builder doesn't know which context to merge -type ErrKubeconfigMergeFailed struct { - Message string -} - -func (e *ErrKubeconfigMergeFailed) Error() string { - return fmt.Sprintf("failed merging kubeconfig: %s", e.Message) -} - -// IsErrAllSourcesFailedErr returns true if error is of type ErrAllSourcesFailedErr -func IsErrAllSourcesFailedErr(err error) bool { - _, ok := err.(*ErrAllSourcesFailed) - return ok -} - -// ErrUnknownKubeconfigSourceType returned type of kubeconfig source is unknown -type ErrUnknownKubeconfigSourceType struct { - Type string -} - -func (e *ErrUnknownKubeconfigSourceType) Error() string { - return fmt.Sprintf("unknown source type %s", e.Type) -} - -// ErrClusterNameEmpty returned when cluster name is not provided -type ErrClusterNameEmpty struct { -} - -func (e ErrClusterNameEmpty) Error() string { - return "cluster name is not defined" -} - -// ErrMalformedKubeconfig error returned if kubeconfig is empty -type ErrMalformedKubeconfig struct { - ClusterName string -} - -func (e ErrMalformedKubeconfig) Error() string { - return fmt.Sprintf("retrieved kubeconfig for cluster '%s' is empty", e.ClusterName) -} diff --git a/pkg/k8s/kubeconfig/kubeconfig.go b/pkg/k8s/kubeconfig/kubeconfig.go deleted file mode 100644 index 96fcacdf0..000000000 --- a/pkg/k8s/kubeconfig/kubeconfig.go +++ /dev/null @@ -1,307 +0,0 @@ -/* - 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 kubeconfig - -import ( - "context" - "fmt" - "io" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" - corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/tools/clientcmd/api" - - "sigs.k8s.io/yaml" - - "opendev.org/airship/airshipctl/pkg/api/v1alpha1" - "opendev.org/airship/airshipctl/pkg/document" - "opendev.org/airship/airshipctl/pkg/fs" - "opendev.org/airship/airshipctl/pkg/log" - "opendev.org/airship/airshipctl/pkg/util" -) - -const ( - // Prefix is a prefix that is added when writing temporary kubeconfig files - Prefix = "kubeconfig-" - defaultTimeout = 30 * time.Second -) - -// Interface provides a uniform way to interact with kubeconfig file -type Interface interface { - // GetFile returns path to kubeconfig file and a function to remove it - // if error is returned cleanup is not needed - GetFile() (string, Cleanup, error) - // Write will write kubeconfig to the provided writer - Write(w io.Writer) error - // WriteFile will write kubeconfig data to specified path - // WriteOptions holds additional option when writing kubeconfig to file - WriteFile(path string, options WriteOptions) error - // WriteTempFile writes a file a temporary file, returns path to it, cleanup function and error - // it is responsibility of the caller to use the cleanup function to make sure that there are no leftovers - WriteTempFile(dumpRoot string) (string, Cleanup, error) -} - -var _ Interface = &kubeConfig{} - -type kubeConfig struct { - path string - dumpRoot string - savedByes []byte - - fileSystem fs.FileSystem - sourceFunc KubeSourceFunc -} - -// WriteOptions holds additional option while writing kubeconfig to the file -type WriteOptions struct { - Merge bool -} - -// NewKubeConfig serves as a constructor for kubeconfig Interface -// first argument is a function that should return bytes with kubeconfig and error -// see FromByte() FromAPIalphaV1() FromFile() functions or extend with your own -// second argument are options that can be used to inject various supported options into it -// see InjectTempRoot(), InjectFileSystem(), InjectFilePath() functions for more info -func NewKubeConfig(source KubeSourceFunc, options ...Option) Interface { - kf := &kubeConfig{} - for _, o := range options { - o(kf) - } - kf.sourceFunc = source - if kf.fileSystem == nil { - kf.fileSystem = fs.NewDocumentFs() - } - return kf -} - -// Option is a function that allows to modify kubeConfig object -type Option func(*kubeConfig) - -// KubeSourceFunc is a function which returns bytes array to construct new kubeConfig object -type KubeSourceFunc func() ([]byte, error) - -// Cleanup is a function which cleans up kubeconfig file from filesystem -type Cleanup func() - -// FromByte returns KubeSource type, uses plain bytes array as source to construct kubeconfig object -func FromByte(b []byte) KubeSourceFunc { - return func() ([]byte, error) { - return b, nil - } -} - -// FromAPIalphaV1 returns KubeSource type, uses API Config array as source to construct kubeconfig object -func FromAPIalphaV1(apiObj *v1alpha1.KubeConfig) KubeSourceFunc { - return func() ([]byte, error) { - return yaml.Marshal(apiObj.Config) - } -} - -// FromSecret returns KubeSource type, uses client interface to kubernetes cluster -func FromSecret(c corev1.CoreV1Interface, o *v1alpha1.GetKubeconfigOptions) KubeSourceFunc { - return func() ([]byte, error) { - if o.ManagedClusterName == "" { - return nil, ErrClusterNameEmpty{} - } - if o.ManagedClusterNamespace == "" { - o.ManagedClusterNamespace = "default" - } - - data, exist, secretName := new([]byte), new(bool), fmt.Sprintf("%s-kubeconfig", o.ManagedClusterName) - fn := func() (bool, error) { - secret, err := c.Secrets(o.ManagedClusterNamespace).Get(context.Background(), secretName, metav1.GetOptions{}) - if err != nil { - log.Printf("get kubeconfig from secret failed, retrying, reason: %v", err) - return false, nil - } - - if *data, *exist = secret.Data["value"]; *exist && len(*data) > 0 { - return true, nil - } - return true, ErrMalformedKubeconfig{ClusterName: o.ManagedClusterName} - } - - duration, err := time.ParseDuration(o.Timeout) - if err != nil || duration == 0 { - duration = defaultTimeout - } - - if err = wait.PollImmediate(time.Second, duration, fn); err != nil { - return nil, err - } - - return *data, nil - } -} - -// FromFile returns KubeSource type, uses path to kubeconfig on FS as source to construct kubeconfig object -func FromFile(path string, fSys fs.FileSystem) KubeSourceFunc { - return func() ([]byte, error) { - expandedPath := util.ExpandTilde(path) - if fSys == nil { - fSys = fs.NewDocumentFs() - } - return fSys.ReadFile(expandedPath) - } -} - -// FromBundle returns KubeSource type, uses path to document bundle to find kubeconfig -func FromBundle(bundle document.Bundle) KubeSourceFunc { - return func() ([]byte, error) { - config := &v1alpha1.KubeConfig{} - selector, err := document.NewSelector().ByObject(config, v1alpha1.Scheme) - if err != nil { - return nil, err - } - - doc, err := bundle.SelectOne(selector) - if err != nil { - return nil, err - } - - if err := doc.ToAPIObject(config, v1alpha1.Scheme); err != nil { - return nil, err - } - - return yaml.Marshal(config.Config) - } -} - -// FromConfig returns KubeSource type, write passed config as bytes -func FromConfig(cfg *api.Config) KubeSourceFunc { - return func() ([]byte, error) { - return clientcmd.Write(*cfg) - } -} - -// InjectFileSystem sets fileSystem to be used, mostly to be used for tests -func InjectFileSystem(fSys fs.FileSystem) Option { - return func(k *kubeConfig) { - k.fileSystem = fSys - } -} - -// InjectTempRoot sets root for temporary file system, if not set default OS temp dir will be used -func InjectTempRoot(dumpRoot string) Option { - return func(k *kubeConfig) { - k.dumpRoot = dumpRoot - } -} - -// InjectFilePath enables setting kubeconfig path, useful when you have kubeconfig -// from the actual filesystem, if this option is used, please also make sure that -// FromFile option is also used as a first argument in NewKubeConfig function -func InjectFilePath(path string, fSys fs.FileSystem) Option { - return func(k *kubeConfig) { - k.path = path - k.fileSystem = fSys - } -} - -func (k *kubeConfig) WriteFile(path string, options WriteOptions) error { - var data []byte - var err error - if options.Merge && path != "" { - data, err = k.mergedBytes(path) - } else { - data, err = k.bytes() - } - if err != nil { - return err - } - return k.fileSystem.WriteFile(path, data) -} - -func (k *kubeConfig) Write(w io.Writer) (err error) { - data, err := k.bytes() - if err != nil { - return err - } - _, err = w.Write(data) - return err -} - -// WriteTempFile implements kubeconfig Interface -func (k *kubeConfig) WriteTempFile(root string) (string, Cleanup, error) { - data, err := k.bytes() - if err != nil { - return "", nil, err - } - file, err := k.fileSystem.TempFile(root, Prefix) - if err != nil { - log.Printf("Failed to write temporary file, error %v", err) - return "", nil, err - } - defer file.Close() - fName := file.Name() - _, err = file.Write(data) - if err != nil { - // delete the temp file that was created and return write error - cleanup(fName, k.fileSystem)() - return "", nil, err - } - return fName, cleanup(fName, k.fileSystem), nil -} - -func (k *kubeConfig) bytes() ([]byte, error) { - var err error - if len(k.savedByes) == 0 { - k.savedByes, err = k.sourceFunc() - } - return k.savedByes, err -} - -// mergedBytes takes the file path and return byte data of the kubeconfig file to be written -func (k *kubeConfig) mergedBytes(path string) ([]byte, error) { - kFile, cleanup, err := k.WriteTempFile(k.dumpRoot) - if err != nil { - return []byte{}, err - } - defer cleanup() - - rules := clientcmd.ClientConfigLoadingRules{ - Precedence: []string{path, kFile}, - } - mergedConfig, err := rules.Load() - if err != nil { - return []byte{}, err - } - return clientcmd.Write(*mergedConfig) -} - -// GetFile checks if path to kubeconfig is already set and returns it no cleanup is necessary, -// and Cleanup() method will do nothing. -// If path is not set kubeconfig will be written to temporary file system, returned path will -// point to it and Cleanup() function will remove this file from the filesystem. -func (k *kubeConfig) GetFile() (string, Cleanup, error) { - if k.path != "" { - return k.path, func() {}, nil - } - return k.WriteTempFile(k.dumpRoot) -} - -func cleanup(path string, fSys fs.FileSystem) Cleanup { - if path == "" { - return func() {} - } - return func() { - if err := fSys.RemoveAll(path); err != nil { - log.Fatalf("Failed to cleanup kubeconfig file %s, error: %v", path, err) - } - } -} diff --git a/pkg/k8s/kubeconfig/kubeconfig_test.go b/pkg/k8s/kubeconfig/kubeconfig_test.go deleted file mode 100644 index eedc4b5bd..000000000 --- a/pkg/k8s/kubeconfig/kubeconfig_test.go +++ /dev/null @@ -1,605 +0,0 @@ -/* - 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 kubeconfig_test - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "io/ioutil" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/apimachinery/pkg/watch" - applycorev1 "k8s.io/client-go/applyconfigurations/core/v1" - corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - v1 "k8s.io/client-go/tools/clientcmd/api/v1" - kustfs "sigs.k8s.io/kustomize/api/filesys" - - "opendev.org/airship/airshipctl/pkg/api/v1alpha1" - "opendev.org/airship/airshipctl/pkg/document" - "opendev.org/airship/airshipctl/pkg/fs" - "opendev.org/airship/airshipctl/pkg/k8s/kubeconfig" - testfs "opendev.org/airship/airshipctl/testutil/fs" -) - -const ( - testValidKubeconfig = `apiVersion: v1 -clusters: -- cluster: - certificate-authority-data: ca-data - server: https://10.0.1.7:6443 - name: kubernetes_target -contexts: -- context: - cluster: kubernetes_target - user: kubernetes-admin - name: kubernetes-admin@kubernetes -current-context: "" -kind: Config -preferences: {} -users: -- name: kubernetes-admin - user: - client-certificate-data: cert-data - client-key-data: client-keydata -` - testValidKubeconfigTwo = ` -apiVersion: v1 -clusters: -- cluster: - server: https://10.0.1.7:6443 - name: kubernetes_target -contexts: -- context: - cluster: kubernetes_target - user: kubernetes-admin - name: kubernetes-admin@kubernetes -current-context: "" -kind: Config -preferences: {} -users: -- name: kubernetes-admin - user: {} -` - //testMergedValidKubeconfig tests to confirm whether two kubeconfig got merged or not - testMergedValidKubeconfig = ` -apiVersion: v1 -clusters: -- cluster: - server: https://10.23.25.101:6443 - name: dummycluster_ephemeral -- cluster: - server: https://10.0.1.7:6443 - name: kubernetes_target -contexts: -- context: - cluster: dummycluster_ephemeral - user: kubernetes-admin - name: dummy_cluster -- context: - cluster: kubernetes_target - user: kubernetes-admin - name: kubernetes-admin@kubernetes -current-context: dummy_cluster -kind: Config -preferences: {} -users: -- name: kubernetes-admin - user: - client-certificate-data: dGVzdAo= - client-key-data: dGVzdAo= -` -) - -var ( - errTempFile = fmt.Errorf("tempFile Error") - errSourceFunc = fmt.Errorf("source func error") - errWriter = fmt.Errorf("writer error") - testValidKubeconfigAPI = &v1alpha1.KubeConfig{ - Config: v1.Config{ - CurrentContext: "test", - Clusters: []v1.NamedCluster{ - { - Name: "some-cluster", - Cluster: v1.Cluster{ - CertificateAuthority: "ca", - Server: "https://10.0.1.7:6443", - }, - }, - }, - APIVersion: "v1", - Contexts: []v1.NamedContext{ - { - Name: "test", - Context: v1.Context{ - Cluster: "some-cluster", - AuthInfo: "some-user", - }, - }, - }, - AuthInfos: []v1.NamedAuthInfo{ - { - Name: "some-user", - AuthInfo: v1.AuthInfo{ - ClientCertificate: "cert-data", - ClientKey: "client-key", - }, - }, - }, - }, - } -) - -func TestKubeconfigContent(t *testing.T) { - expectedData := []byte(testValidKubeconfig) - fSys := fs.NewDocumentFs() - kubeconf := kubeconfig.NewKubeConfig( - kubeconfig.FromByte(expectedData), - kubeconfig.InjectFileSystem(fSys), - kubeconfig.InjectTempRoot(".")) - path, clean, err := kubeconf.GetFile() - require.NoError(t, err) - defer clean() - actualData, err := fSys.ReadFile(path) - require.NoError(t, err) - assert.Equal(t, expectedData, actualData) -} - -type MockCoreV1Interface struct { - MockSecrets func(string) corev1.SecretInterface - corev1.CoreV1Interface -} - -func (c MockCoreV1Interface) Secrets(n string) corev1.SecretInterface { - return c.MockSecrets(n) -} - -var _ corev1.SecretInterface = &SecretMockInterface{} - -type SecretMockInterface struct { - mock.Mock -} - -func (s *SecretMockInterface) Create(_ context.Context, _ *apiv1.Secret, - _ metav1.CreateOptions) (*apiv1.Secret, error) { - panic("implement me") -} - -func (s *SecretMockInterface) Update(_ context.Context, _ *apiv1.Secret, - _ metav1.UpdateOptions) (*apiv1.Secret, error) { - panic("implement me") -} - -func (s *SecretMockInterface) Delete(_ context.Context, _ string, _ metav1.DeleteOptions) error { - panic("implement me") -} - -func (s *SecretMockInterface) DeleteCollection(_ context.Context, _ metav1.DeleteOptions, - _ metav1.ListOptions) error { - panic("implement me") -} - -func (s *SecretMockInterface) Apply(_ context.Context, _ *applycorev1.SecretApplyConfiguration, - _ metav1.ApplyOptions) (*apiv1.Secret, error) { - panic("implement me") -} - -func (s *SecretMockInterface) Get(_ context.Context, name string, options metav1.GetOptions) (*apiv1.Secret, error) { - args := s.Called(name, options) - expectedResult, ok := args.Get(0).(*apiv1.Secret) - if !ok { - return nil, fmt.Errorf("wrong input") - } - return expectedResult, args.Error(1) -} - -func (s *SecretMockInterface) List(_ context.Context, _ metav1.ListOptions) (*apiv1.SecretList, error) { - panic("implement me") -} - -func (s *SecretMockInterface) Watch(_ context.Context, _ metav1.ListOptions) (watch.Interface, error) { - panic("implement me") -} - -func (s *SecretMockInterface) Patch(_ context.Context, _ string, _ types.PatchType, _ []byte, - _ metav1.PatchOptions, _ ...string) (*apiv1.Secret, error) { - panic("implement me") -} - -func TestFromSecret(t *testing.T) { - tests := []struct { - name string - options *v1alpha1.GetKubeconfigOptions - getSecret *apiv1.Secret - getErr error - expectedData []byte - expectedErr error - }{ - { - name: "empty cluster name", - options: &v1alpha1.GetKubeconfigOptions{}, - expectedErr: kubeconfig.ErrClusterNameEmpty{}, - }, - { - name: "multiple retries and error", - options: &v1alpha1.GetKubeconfigOptions{ManagedClusterName: "cluster", Timeout: "1s"}, - getErr: errors.New("error"), - expectedErr: wait.ErrWaitTimeout, - }, - { - name: "empty secret object", - options: &v1alpha1.GetKubeconfigOptions{ManagedClusterName: "cluster"}, - getSecret: &apiv1.Secret{}, - expectedErr: kubeconfig.ErrMalformedKubeconfig{ClusterName: "cluster"}, - }, - { - name: "empty data value", - options: &v1alpha1.GetKubeconfigOptions{ManagedClusterName: "cluster"}, - getSecret: &apiv1.Secret{Data: map[string][]byte{"value": {}}}, - expectedErr: kubeconfig.ErrMalformedKubeconfig{ClusterName: "cluster"}, - }, - { - name: "successfully get kubeconfig", - options: &v1alpha1.GetKubeconfigOptions{ManagedClusterName: "cluster"}, - getSecret: &apiv1.Secret{Data: map[string][]byte{"value": []byte(testValidKubeconfig)}}, - expectedData: []byte(testValidKubeconfig), - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - sm := &SecretMockInterface{ - Mock: mock.Mock{}, - } - sm.On("Get", tt.options.ManagedClusterName+"-kubeconfig", metav1.GetOptions{}). - Return(tt.getSecret, tt.getErr) - - coreV1Interface := MockCoreV1Interface{ - MockSecrets: func(s string) corev1.SecretInterface { - return sm - }, - } - - kubeconf, err := kubeconfig.FromSecret(coreV1Interface, tt.options)() - if tt.expectedErr != nil { - require.Error(t, err) - require.Equal(t, tt.expectedErr, err) - require.Nil(t, kubeconf) - } else { - require.NoError(t, err) - require.Equal(t, tt.expectedData, kubeconf) - } - }) - } -} - -func TestFromBundle(t *testing.T) { - tests := []struct { - name string - rootPath string - expectedContains string - shouldFail bool - }{ - { - name: "valid kubeconfig", - rootPath: "testdata", - shouldFail: false, - expectedContains: "parent_parent_context", - }, - { - name: "wrong path", - rootPath: "wrong/path", - shouldFail: true, - }, - { - name: "kubeconfig not found", - rootPath: "testdata_fail", - shouldFail: true, - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - bundle, err := document.NewBundleByPath(tt.rootPath) - if tt.shouldFail { - require.Error(t, err) - return - } - kubeconf, err := kubeconfig.FromBundle(bundle)() - require.NoError(t, err) - assert.Contains(t, string(kubeconf), tt.expectedContains) - }) - } -} - -func TestNewKubeConfig(t *testing.T) { - tests := []struct { - shouldPanic bool - name string - expectedPathContains string - expectedErrorContains string - src kubeconfig.KubeSourceFunc - options []kubeconfig.Option - }{ - { - name: "write to temp file", - src: kubeconfig.FromByte([]byte(testValidKubeconfig)), - options: []kubeconfig.Option{ - kubeconfig.InjectFileSystem( - testfs.MockFileSystem{ - MockTempFile: func(root, pattern string) (fs.File, error) { - return testfs.TestFile{ - MockName: func() string { return "kubeconfig-142398" }, - MockWrite: func([]byte) (int, error) { return 0, nil }, - MockClose: func() error { return nil }, - }, nil - }, - MockRemoveAll: func() error { return nil }, - }, - ), - }, - expectedPathContains: "kubeconfig-142398", - }, - { - name: "cleanup with dump root", - expectedPathContains: "kubeconfig-142398", - src: kubeconfig.FromByte([]byte(testValidKubeconfig)), - options: []kubeconfig.Option{ - kubeconfig.InjectTempRoot("/my-unique-root"), - kubeconfig.InjectFileSystem( - testfs.MockFileSystem{ - MockTempFile: func(root, _ string) (fs.File, error) { - // check if root path is passed to the TempFile interface - if root != "/my-unique-root" { - return nil, errTempFile - } - return testfs.TestFile{ - MockName: func() string { return "kubeconfig-142398" }, - MockWrite: func([]byte) (int, error) { return 0, nil }, - MockClose: func() error { return nil }, - }, nil - }, - MockRemoveAll: func() error { return nil }, - }, - ), - }, - }, - { - name: "from file, and fs option", - src: kubeconfig.FromFile("/my/kubeconfig", fsWithFile(t, "/my/kubeconfig")), - options: []kubeconfig.Option{ - kubeconfig.InjectFilePath("/my/kubeconfig", fsWithFile(t, "/my/kubeconfig")), - }, - expectedPathContains: "/my/kubeconfig", - }, - { - name: "write to real fs", - src: kubeconfig.FromAPIalphaV1(testValidKubeconfigAPI), - expectedPathContains: "kubeconfig-", - }, - { - name: "from file, use SourceFile", - src: kubeconfig.FromFile("/my/kubeconfig", fsWithFile(t, "/my/kubeconfig")), - expectedPathContains: "kubeconfig-", - }, - { - name: "temp file error", - src: kubeconfig.FromAPIalphaV1(testValidKubeconfigAPI), - expectedErrorContains: errTempFile.Error(), - options: []kubeconfig.Option{ - kubeconfig.InjectFileSystem( - testfs.MockFileSystem{ - MockTempFile: func(string, string) (fs.File, error) { - return nil, errTempFile - }, - MockRemoveAll: func() error { return nil }, - }, - ), - }, - }, - { - name: "source func error", - src: func() ([]byte, error) { return nil, errSourceFunc }, - expectedPathContains: "kubeconfig-", - expectedErrorContains: errSourceFunc.Error(), - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - kubeconf := kubeconfig.NewKubeConfig(tt.src, tt.options...) - path, clean, err := kubeconf.GetFile() - if tt.expectedErrorContains != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.expectedErrorContains) - } else { - require.NoError(t, err) - actualPath := path - assert.Contains(t, actualPath, tt.expectedPathContains) - clean() - } - }) - } -} - -func TestKubeConfigWrite(t *testing.T) { - tests := []struct { - name string - expectedContent string - expectedErrorContains string - - readWrite io.ReadWriter - options []kubeconfig.Option - src kubeconfig.KubeSourceFunc - }{ - { - name: "Basic write", - src: kubeconfig.FromByte([]byte(testValidKubeconfig)), - expectedContent: testValidKubeconfig, - readWrite: bytes.NewBuffer([]byte{}), - }, - { - name: "Source error", - src: func() ([]byte, error) { return nil, errSourceFunc }, - expectedErrorContains: errSourceFunc.Error(), - }, - { - name: "Writer error", - src: kubeconfig.FromByte([]byte(testValidKubeconfig)), - expectedErrorContains: errWriter.Error(), - readWrite: fakeReaderWriter{writeErr: errWriter}, - }, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - kubeconf := kubeconfig.NewKubeConfig(tt.src, tt.options...) - err := kubeconf.Write(tt.readWrite) - if tt.expectedErrorContains != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.expectedErrorContains) - } else { - require.NoError(t, err) - assert.Equal(t, tt.expectedContent, read(t, tt.readWrite)) - } - }) - } -} - -func TestKubeConfigWriteFile(t *testing.T) { - tests := []struct { - name string - expectedContent string - path string - expectedErrorContains string - merge bool - - fs fs.FileSystem - src kubeconfig.KubeSourceFunc - }{ - { - name: "Basic write file", - src: kubeconfig.FromByte([]byte(testValidKubeconfig)), - expectedContent: testValidKubeconfig, - merge: false, - fs: fsWithFile(t, "/test-path"), - path: "/test-path", - }, - { - name: "Basic merge write file", - src: kubeconfig.FromByte([]byte(testValidKubeconfigTwo)), - expectedContent: testMergedValidKubeconfig, - merge: true, - fs: fs.NewDocumentFs(), - path: "testdata/kubeconfig-test", - }, - { - name: "Source error", - src: func() ([]byte, error) { return nil, errSourceFunc }, - expectedErrorContains: errSourceFunc.Error(), - merge: false, - }, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - kubeconf := kubeconfig.NewKubeConfig(tt.src, - kubeconfig.InjectTempRoot("testdata/"), - kubeconfig.InjectFileSystem(tt.fs)) - options := kubeconfig.WriteOptions{ - Merge: tt.merge, - } - if tt.merge { - _, clean, err := kubeconf.GetFile() - require.NoError(t, err) - defer clean() - original, err := ioutil.ReadFile(tt.path) - require.NoError(t, err) - defer func() { - err = ioutil.WriteFile(tt.path, original, 0600) - require.NoError(t, err) - }() - } - err := kubeconf.WriteFile(tt.path, options) - if tt.expectedErrorContains != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.expectedErrorContains) - } else { - require.NoError(t, err) - assert.YAMLEq(t, tt.expectedContent, readFile(t, tt.path, tt.fs)) - } - }) - } -} - -func readFile(t *testing.T, path string, fSys fs.FileSystem) string { - b, err := fSys.ReadFile(path) - require.NoError(t, err) - return string(b) -} - -func read(t *testing.T, r io.Reader) string { - b, err := ioutil.ReadAll(r) - require.NoError(t, err) - return string(b) -} - -func fsWithFile(t *testing.T, path string) fs.FileSystem { - memFs := kustfs.MakeFsInMemory() - fSys := testfs.MockFileSystem{ - FileSystem: memFs, - MockRemoveAll: func() error { - return nil - }, - MockTempFile: func(root, pattern string) (fs.File, error) { - return testfs.TestFile{ - MockName: func() string { return "kubeconfig-142398" }, - MockWrite: func(b []byte) (int, error) { return 0, memFs.WriteFile("kubeconfig-142398", b) }, - MockClose: func() error { return nil }, - }, nil - }, - } - err := fSys.WriteFile(path, []byte(testValidKubeconfig)) - require.NoError(t, err) - return fSys -} - -type fakeReaderWriter struct { - readErr error - writeErr error -} - -var _ io.Reader = fakeReaderWriter{} -var _ io.Writer = fakeReaderWriter{} - -func (f fakeReaderWriter) Read(_ []byte) (n int, err error) { - return 0, f.readErr -} - -func (f fakeReaderWriter) Write(_ []byte) (n int, err error) { - return 0, f.writeErr -} diff --git a/pkg/k8s/kubeconfig/testdata/kubeconfig b/pkg/k8s/kubeconfig/testdata/kubeconfig deleted file mode 100644 index ea466e382..000000000 --- a/pkg/k8s/kubeconfig/testdata/kubeconfig +++ /dev/null @@ -1,19 +0,0 @@ - apiVersion: v1 - kind: Config - clusters: - - cluster: - certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1USXlOakE0TWpneU5Gb1hEVEk1TVRJeU16QTRNamd5TkZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTTFSClM0d3lnajNpU0JBZjlCR0JUS1p5VTFwYmdDaGQ2WTdJektaZWRoakM2K3k1ZEJpWm81ZUx6Z2tEc2gzOC9YQ1MKenFPS2V5cE5RcDN5QVlLdmJKSHg3ODZxSFZZNjg1ZDVYVDNaOHNyVVRzVDR5WmNzZHAzV3lHdDM0eXYzNi9BSQoxK1NlUFErdU5JemN6bzNEdWhXR0ZoQjk3VjZwRitFUTBlVWN5bk05c2hkL3AwWVFzWDR1ZlhxaENENVpzZnZUCnBka3UvTWkyWnVGUldUUUtNeGpqczV3Z2RBWnBsNnN0L2ZkbmZwd1Q5cC9WTjRuaXJnMEsxOURTSFFJTHVrU2MKb013bXNBeDJrZmxITWhPazg5S3FpMEloL2cyczRFYTRvWURZemt0Y2JRZ24wd0lqZ2dmdnVzM3pRbEczN2lwYQo4cVRzS2VmVGdkUjhnZkJDNUZNQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFJek9BL00xWmRGUElzd2VoWjFuemJ0VFNURG4KRHMyVnhSV0VnclFFYzNSYmV3a1NkbTlBS3MwVGR0ZHdEbnBEL2tRYkNyS2xEeFF3RWg3NFZNSFZYYkFadDdsVwpCSm90T21xdXgxYThKYklDRTljR0FHRzFvS0g5R29jWERZY0JzOTA3ckxIdStpVzFnL0xVdG5hN1dSampqZnBLCnFGelFmOGdJUHZIM09BZ3B1RVVncUx5QU8ya0VnelZwTjZwQVJxSnZVRks2TUQ0YzFmMnlxWGxwNXhrN2dFSnIKUzQ4WmF6d0RmWUVmV3Jrdld1YWdvZ1M2SktvbjVEZ0Z1ZHhINXM2Snl6R3lPVnZ0eG1TY2FvOHNxaCs3UXkybgoyLzFVcU5ZK0hlN0x4d04rYkhwYkIxNUtIMTU5ZHNuS3BRbjRORG1jSTZrVnJ3MDVJMUg5ZGRBbGF0bz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= - server: https://10.23.25.101:6443 - name: dummycluster_ephemeral - contexts: - - context: - cluster: dummycluster_ephemeral - user: kubernetes-admin - name: dummy_cluster - current-context: dummy_cluster - preferences: {} - users: - - name: kubernetes-admin - user: - client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQwRENDQXJnQ0ZFdFBveEZYSjVrVFNWTXQ0OVlqcHBQL3hCYnlNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1CVXgKRXpBUkJnTlZCQU1UQ210MVltVnlibVYwWlhNd0hoY05NakF3TVRJME1Ua3hOVEV3V2hjTk1qa3hNakF5TVRreApOVEV3V2pBME1Sa3dGd1lEVlFRRERCQnJkV0psY201bGRHVnpMV0ZrYldsdU1SY3dGUVlEVlFRS0RBNXplWE4wClpXMDZiV0Z6ZEdWeWN6Q0NBaUl3RFFZSktvWklodmNOQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0lCQU1iaFhUUmsKVjZiZXdsUjBhZlpBdTBGYWVsOXRtRThaSFEvaGtaSHhuTjc2bDZUUFltcGJvaDRvRjNGMFFqbzROS1o5NVRuWgo0OWNoV240eFJiZVlPU25EcDBpV0Qzd0pXUlZ5aVFvVUFyYTlNcHVPNkVFU1FpbFVGNXNxc0VXUVdVMjBETStBCkdxK1k0Z2c3eDJ1Q0hTdk1GUmkrNEw5RWlXR2xnRDIvb1hXUm5NWEswNExQajZPb3Vkb2Zid2RmT3J6dTBPVkUKUzR0eGtuS1BCY1BUU3YxMWVaWVhja0JEVjNPbExENEZ3dTB3NTcwcnczNzAraEpYdlZxd3Zjb2RjZjZEL1BXWQowamlnd2ppeUJuZ2dXYW04UVFjd1Nud3o0d05sV3hKOVMyWUJFb1ptdWxVUlFaWVk5ZXRBcEpBdFMzTjlUNlQ2ClovSlJRdEdhZDJmTldTYkxEck5qdU1OTGhBYWRMQnhJUHpBNXZWWk5aalJkdEMwU25pMlFUMTVpSFp4d1RxcjQKakRQQ0pYRXU3KytxcWpQVldUaUZLK3JqcVNhS1pqVWZVaUpHQkJWcm5RZkJENHNtRnNkTjB5cm9tYTZOYzRMNQpKS21RV1NHdmd1aG0zbW5sYjFRaVRZanVyZFJQRFNmdmwrQ0NHbnA1QkkvZ1pwMkF1SHMvNUpKVTJlc1ZvL0xsCkVPdHdSOXdXd3dXcTAvZjhXS3R4bVRrMTUyOUp2dFBGQXQweW1CVjhQbHZlYnVwYmJqeW5pL2xWbTJOYmV6dWUKeCtlMEpNbGtWWnFmYkRSS243SjZZSnJHWW1CUFV0QldoSVkzb1pJVTFEUXI4SUlIbkdmYlZoWlR5ME1IMkFCQQp1dlVQcUtSVk80UGkxRTF4OEE2eWVPeVRDcnB4L0pBazVyR2RBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFECmdnRUJBSWNFM1BxZHZDTVBIMnJzMXJESk9ESHY3QWk4S01PVXZPRi90RjlqR2EvSFBJbkh3RlVFNEltbldQeDYKVUdBMlE1bjFsRDFGQlU0T0M4eElZc3VvS1VQVHk1T0t6SVNMNEZnL0lEcG54STlrTXlmNStMR043aG8rblJmawpCZkpJblVYb0tERW1neHZzSWFGd1h6bGtSTDJzL1lKYUZRRzE1Uis1YzFyckJmd2dJOFA5Tkd6aEM1cXhnSmovCm04K3hPMGhXUmJIYklrQ21NekRib2pCSWhaL00rb3VYR1doei9TakpodXhZTVBnek5MZkFGcy9PMTVaSjd3YXcKZ3ZoSGc3L2E5UzRvUCtEYytPa3VrMkV1MUZjL0E5WHpWMzc5aWhNWW5ub3RQMldWeFZ3b0ZZQUg0NUdQcDZsUApCQmwyNnkxc2JMbjl6aGZYUUJIMVpFN0EwZVE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K - client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBdXpHSll2UFo2RG9pNDIxREs4V0phWkNuTkFkMnF6NXAvMDQyb0Z6UVBickFnekUyCnFZVmt6T0w4eEFWZVI3U041d1dvVFdFeUY4RVY3cnIvNCtIaEhHaXE1UG1xdUlGeXp6bjYvSVpjOGpVOXhFZnoKdmlrY2lyTGZVNHZSWEpRd1Z3Z0FTTmwyQVdBSWgyZERkVHJqQkNmaUtXTUh5ajBSYkhhbHNCek9wZ1QvSFR2MwpHUXpuVVF6Rkt2MmRqNVYxTmtTL0RIanlSUkorRUw2UUJZbUdzZWc1UDROYkM5ZWJ1aXBtTVRBcS9KdW1Pb29kCitGakwyblpxTDZmSTZmQnRGNU9HbHBDQjFZSjhmekN0R1FRWjdIVEliZGIydHA0M0ZWT2h5UWJWY0hxVEEwNFAKSjE1KzBXQXltVUpVejhYQTU0NHIvYnY3NEpjSlVSRmhhclppUXdJREFRQUJBb0lCQVFDU0pycjlaeVpiQ2dqegpSL3VKMFZEWCt2aVF4c01BTUZyUjJsOE1GV3NBeHk1SFA4Vk4xYmc5djN0YUVGYnI1U3hsa3lVMFJRNjNQU25DCm1uM3ZqZ3dVQWlScllnTEl5MGk0UXF5VFBOU1V4cnpTNHRxTFBjM3EvSDBnM2FrNGZ2cSsrS0JBUUlqQnloamUKbnVFc1JpMjRzT3NESlM2UDE5NGlzUC9yNEpIM1M5bFZGbkVuOGxUR2c0M1kvMFZoMXl0cnkvdDljWjR5ZUNpNwpjMHFEaTZZcXJZaFZhSW9RRW1VQjdsbHRFZkZzb3l4VDR6RTE5U3pVbkRoMmxjYTF1TzhqcmI4d2xHTzBoQ2JyClB1R1l2WFFQa3Q0VlNmalhvdGJ3d2lBNFRCVERCRzU1bHp6MmNKeS9zSS8zSHlYbEMxcTdXUmRuQVhhZ1F0VzkKOE9DZGRkb0JBb0dCQU5NcUNtSW94REtyckhZZFRxT1M1ZFN4cVMxL0NUN3ZYZ0pScXBqd2Y4WHA2WHo0KzIvTAozVXFaVDBEL3dGTkZkc1Z4eFYxMnNYMUdwMHFWZVlKRld5OVlCaHVSWGpTZ0ZEWldSY1Z1Y01sNVpPTmJsbmZGCjVKQ0xnNXFMZ1g5VTNSRnJrR3A0R241UDQxamg4TnhKVlhzZG5xWE9xNTFUK1RRT1UzdkpGQjc1QW9HQkFPTHcKalp1cnZtVkZyTHdaVGgvRDNpWll5SVV0ZUljZ2NKLzlzbTh6L0pPRmRIbFd4dGRHUFVzYVd1MnBTNEhvckFtbgpqTm4vSTluUXd3enZ3MWUzVVFPbUhMRjVBczk4VU5hbk5TQ0xNMW1yaXZHRXJ1VHFnTDM1bU41eFZPdTUxQU5JCm4yNkFtODBJT2JDeEtLa0R0ZXJSaFhHd3g5c1pONVJCbG9VRThZNGJBb0dBQ3ZsdVhMZWRxcng5VkE0bDNoNXUKVDJXRVUxYjgxZ1orcmtRc1I1S0lNWEw4cllBTElUNUpHKzFuendyN3BkaEFXZmFWdVV2SDRhamdYT0h6MUs5aQpFODNSVTNGMG9ldUg0V01PY1RwU0prWm0xZUlXcWRiaEVCb1FGdUlWTXRib1BsV0d4ZUhFRHJoOEtreGp4aThSCmdEcUQyajRwY1IzQ0g5QjJ5a0lqQjVFQ2dZRUExc0xXLys2enE1c1lNSm14K1JXZThhTXJmL3pjQnVTSU1LQWgKY0dNK0wwMG9RSHdDaUU4TVNqcVN1ajV3R214YUFuanhMb3ZwSFlRV1VmUEVaUW95UE1YQ2VhRVBLOU4xbk8xMwp0V2lHRytIZkIxaU5PazFCc0lhNFNDbndOM1FRVTFzeXBaeEgxT3hueS9LYmkvYmEvWEZ5VzNqMGFUK2YvVWxrCmJGV1ZVdWtDZ1lFQTBaMmRTTFlmTjV5eFNtYk5xMWVqZXdWd1BjRzQxR2hQclNUZEJxdHFac1doWGE3aDdLTWEKeHdvamh5SXpnTXNyK2tXODdlajhDQ2h0d21sQ1p5QU92QmdOZytncnJ1cEZLM3FOSkpKeU9YREdHckdpbzZmTQp5aXB3Q2tZVGVxRThpZ1J6UkI5QkdFUGY4eVpjMUtwdmZhUDVhM0lRZmxiV0czbGpUemNNZVZjPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= diff --git a/pkg/k8s/kubeconfig/testdata/kubeconfig-test b/pkg/k8s/kubeconfig/testdata/kubeconfig-test deleted file mode 100644 index f956153eb..000000000 --- a/pkg/k8s/kubeconfig/testdata/kubeconfig-test +++ /dev/null @@ -1,18 +0,0 @@ - apiVersion: v1 - kind: Config - clusters: - - cluster: - server: https://10.23.25.101:6443 - name: dummycluster_ephemeral - contexts: - - context: - cluster: dummycluster_ephemeral - user: kubernetes-admin - name: dummy_cluster - current-context: dummy_cluster - preferences: {} - users: - - name: kubernetes-admin - user: - client-certificate-data: dGVzdAo= - client-key-data: dGVzdAo= diff --git a/pkg/k8s/kubeconfig/testdata/kubeconfig.yaml b/pkg/k8s/kubeconfig/testdata/kubeconfig.yaml deleted file mode 100644 index e7053e4a5..000000000 --- a/pkg/k8s/kubeconfig/testdata/kubeconfig.yaml +++ /dev/null @@ -1,23 +0,0 @@ -apiVersion: airshipit.org/v1alpha1 -kind: KubeConfig -metadata: - name: default -config: - apiVersion: v1 - kind: Config - clusters: - - cluster: - certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1USXlOakE0TWpneU5Gb1hEVEk1TVRJeU16QTRNamd5TkZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTTFSClM0d3lnajNpU0JBZjlCR0JUS1p5VTFwYmdDaGQ2WTdJektaZWRoakM2K3k1ZEJpWm81ZUx6Z2tEc2gzOC9YQ1MKenFPS2V5cE5RcDN5QVlLdmJKSHg3ODZxSFZZNjg1ZDVYVDNaOHNyVVRzVDR5WmNzZHAzV3lHdDM0eXYzNi9BSQoxK1NlUFErdU5JemN6bzNEdWhXR0ZoQjk3VjZwRitFUTBlVWN5bk05c2hkL3AwWVFzWDR1ZlhxaENENVpzZnZUCnBka3UvTWkyWnVGUldUUUtNeGpqczV3Z2RBWnBsNnN0L2ZkbmZwd1Q5cC9WTjRuaXJnMEsxOURTSFFJTHVrU2MKb013bXNBeDJrZmxITWhPazg5S3FpMEloL2cyczRFYTRvWURZemt0Y2JRZ24wd0lqZ2dmdnVzM3pRbEczN2lwYQo4cVRzS2VmVGdkUjhnZkJDNUZNQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFJek9BL00xWmRGUElzd2VoWjFuemJ0VFNURG4KRHMyVnhSV0VnclFFYzNSYmV3a1NkbTlBS3MwVGR0ZHdEbnBEL2tRYkNyS2xEeFF3RWg3NFZNSFZYYkFadDdsVwpCSm90T21xdXgxYThKYklDRTljR0FHRzFvS0g5R29jWERZY0JzOTA3ckxIdStpVzFnL0xVdG5hN1dSampqZnBLCnFGelFmOGdJUHZIM09BZ3B1RVVncUx5QU8ya0VnelZwTjZwQVJxSnZVRks2TUQ0YzFmMnlxWGxwNXhrN2dFSnIKUzQ4WmF6d0RmWUVmV3Jrdld1YWdvZ1M2SktvbjVEZ0Z1ZHhINXM2Snl6R3lPVnZ0eG1TY2FvOHNxaCs3UXkybgoyLzFVcU5ZK0hlN0x4d04rYkhwYkIxNUtIMTU5ZHNuS3BRbjRORG1jSTZrVnJ3MDVJMUg5ZGRBbGF0bz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= - server: https://10.23.25.101:6443 - name: parent_parent_cluster - contexts: - - context: - cluster: parent_parent_cluster - user: parent_parent_admin - name: parent_parent_context - preferences: {} - users: - - name: parent_parent_admin - user: - client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQwRENDQXJnQ0ZFdFBveEZYSjVrVFNWTXQ0OVlqcHBQL3hCYnlNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1CVXgKRXpBUkJnTlZCQU1UQ210MVltVnlibVYwWlhNd0hoY05NakF3TVRJME1Ua3hOVEV3V2hjTk1qa3hNakF5TVRreApOVEV3V2pBME1Sa3dGd1lEVlFRRERCQnJkV0psY201bGRHVnpMV0ZrYldsdU1SY3dGUVlEVlFRS0RBNXplWE4wClpXMDZiV0Z6ZEdWeWN6Q0NBaUl3RFFZSktvWklodmNOQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0lCQU1iaFhUUmsKVjZiZXdsUjBhZlpBdTBGYWVsOXRtRThaSFEvaGtaSHhuTjc2bDZUUFltcGJvaDRvRjNGMFFqbzROS1o5NVRuWgo0OWNoV240eFJiZVlPU25EcDBpV0Qzd0pXUlZ5aVFvVUFyYTlNcHVPNkVFU1FpbFVGNXNxc0VXUVdVMjBETStBCkdxK1k0Z2c3eDJ1Q0hTdk1GUmkrNEw5RWlXR2xnRDIvb1hXUm5NWEswNExQajZPb3Vkb2Zid2RmT3J6dTBPVkUKUzR0eGtuS1BCY1BUU3YxMWVaWVhja0JEVjNPbExENEZ3dTB3NTcwcnczNzAraEpYdlZxd3Zjb2RjZjZEL1BXWQowamlnd2ppeUJuZ2dXYW04UVFjd1Nud3o0d05sV3hKOVMyWUJFb1ptdWxVUlFaWVk5ZXRBcEpBdFMzTjlUNlQ2ClovSlJRdEdhZDJmTldTYkxEck5qdU1OTGhBYWRMQnhJUHpBNXZWWk5aalJkdEMwU25pMlFUMTVpSFp4d1RxcjQKakRQQ0pYRXU3KytxcWpQVldUaUZLK3JqcVNhS1pqVWZVaUpHQkJWcm5RZkJENHNtRnNkTjB5cm9tYTZOYzRMNQpKS21RV1NHdmd1aG0zbW5sYjFRaVRZanVyZFJQRFNmdmwrQ0NHbnA1QkkvZ1pwMkF1SHMvNUpKVTJlc1ZvL0xsCkVPdHdSOXdXd3dXcTAvZjhXS3R4bVRrMTUyOUp2dFBGQXQweW1CVjhQbHZlYnVwYmJqeW5pL2xWbTJOYmV6dWUKeCtlMEpNbGtWWnFmYkRSS243SjZZSnJHWW1CUFV0QldoSVkzb1pJVTFEUXI4SUlIbkdmYlZoWlR5ME1IMkFCQQp1dlVQcUtSVk80UGkxRTF4OEE2eWVPeVRDcnB4L0pBazVyR2RBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFECmdnRUJBSWNFM1BxZHZDTVBIMnJzMXJESk9ESHY3QWk4S01PVXZPRi90RjlqR2EvSFBJbkh3RlVFNEltbldQeDYKVUdBMlE1bjFsRDFGQlU0T0M4eElZc3VvS1VQVHk1T0t6SVNMNEZnL0lEcG54STlrTXlmNStMR043aG8rblJmawpCZkpJblVYb0tERW1neHZzSWFGd1h6bGtSTDJzL1lKYUZRRzE1Uis1YzFyckJmd2dJOFA5Tkd6aEM1cXhnSmovCm04K3hPMGhXUmJIYklrQ21NekRib2pCSWhaL00rb3VYR1doei9TakpodXhZTVBnek5MZkFGcy9PMTVaSjd3YXcKZ3ZoSGc3L2E5UzRvUCtEYytPa3VrMkV1MUZjL0E5WHpWMzc5aWhNWW5ub3RQMldWeFZ3b0ZZQUg0NUdQcDZsUApCQmwyNnkxc2JMbjl6aGZYUUJIMVpFN0EwZVE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K - client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBdXpHSll2UFo2RG9pNDIxREs4V0phWkNuTkFkMnF6NXAvMDQyb0Z6UVBickFnekUyCnFZVmt6T0w4eEFWZVI3U041d1dvVFdFeUY4RVY3cnIvNCtIaEhHaXE1UG1xdUlGeXp6bjYvSVpjOGpVOXhFZnoKdmlrY2lyTGZVNHZSWEpRd1Z3Z0FTTmwyQVdBSWgyZERkVHJqQkNmaUtXTUh5ajBSYkhhbHNCek9wZ1QvSFR2MwpHUXpuVVF6Rkt2MmRqNVYxTmtTL0RIanlSUkorRUw2UUJZbUdzZWc1UDROYkM5ZWJ1aXBtTVRBcS9KdW1Pb29kCitGakwyblpxTDZmSTZmQnRGNU9HbHBDQjFZSjhmekN0R1FRWjdIVEliZGIydHA0M0ZWT2h5UWJWY0hxVEEwNFAKSjE1KzBXQXltVUpVejhYQTU0NHIvYnY3NEpjSlVSRmhhclppUXdJREFRQUJBb0lCQVFDU0pycjlaeVpiQ2dqegpSL3VKMFZEWCt2aVF4c01BTUZyUjJsOE1GV3NBeHk1SFA4Vk4xYmc5djN0YUVGYnI1U3hsa3lVMFJRNjNQU25DCm1uM3ZqZ3dVQWlScllnTEl5MGk0UXF5VFBOU1V4cnpTNHRxTFBjM3EvSDBnM2FrNGZ2cSsrS0JBUUlqQnloamUKbnVFc1JpMjRzT3NESlM2UDE5NGlzUC9yNEpIM1M5bFZGbkVuOGxUR2c0M1kvMFZoMXl0cnkvdDljWjR5ZUNpNwpjMHFEaTZZcXJZaFZhSW9RRW1VQjdsbHRFZkZzb3l4VDR6RTE5U3pVbkRoMmxjYTF1TzhqcmI4d2xHTzBoQ2JyClB1R1l2WFFQa3Q0VlNmalhvdGJ3d2lBNFRCVERCRzU1bHp6MmNKeS9zSS8zSHlYbEMxcTdXUmRuQVhhZ1F0VzkKOE9DZGRkb0JBb0dCQU5NcUNtSW94REtyckhZZFRxT1M1ZFN4cVMxL0NUN3ZYZ0pScXBqd2Y4WHA2WHo0KzIvTAozVXFaVDBEL3dGTkZkc1Z4eFYxMnNYMUdwMHFWZVlKRld5OVlCaHVSWGpTZ0ZEWldSY1Z1Y01sNVpPTmJsbmZGCjVKQ0xnNXFMZ1g5VTNSRnJrR3A0R241UDQxamg4TnhKVlhzZG5xWE9xNTFUK1RRT1UzdkpGQjc1QW9HQkFPTHcKalp1cnZtVkZyTHdaVGgvRDNpWll5SVV0ZUljZ2NKLzlzbTh6L0pPRmRIbFd4dGRHUFVzYVd1MnBTNEhvckFtbgpqTm4vSTluUXd3enZ3MWUzVVFPbUhMRjVBczk4VU5hbk5TQ0xNMW1yaXZHRXJ1VHFnTDM1bU41eFZPdTUxQU5JCm4yNkFtODBJT2JDeEtLa0R0ZXJSaFhHd3g5c1pONVJCbG9VRThZNGJBb0dBQ3ZsdVhMZWRxcng5VkE0bDNoNXUKVDJXRVUxYjgxZ1orcmtRc1I1S0lNWEw4cllBTElUNUpHKzFuendyN3BkaEFXZmFWdVV2SDRhamdYT0h6MUs5aQpFODNSVTNGMG9ldUg0V01PY1RwU0prWm0xZUlXcWRiaEVCb1FGdUlWTXRib1BsV0d4ZUhFRHJoOEtreGp4aThSCmdEcUQyajRwY1IzQ0g5QjJ5a0lqQjVFQ2dZRUExc0xXLys2enE1c1lNSm14K1JXZThhTXJmL3pjQnVTSU1LQWgKY0dNK0wwMG9RSHdDaUU4TVNqcVN1ajV3R214YUFuanhMb3ZwSFlRV1VmUEVaUW95UE1YQ2VhRVBLOU4xbk8xMwp0V2lHRytIZkIxaU5PazFCc0lhNFNDbndOM1FRVTFzeXBaeEgxT3hueS9LYmkvYmEvWEZ5VzNqMGFUK2YvVWxrCmJGV1ZVdWtDZ1lFQTBaMmRTTFlmTjV5eFNtYk5xMWVqZXdWd1BjRzQxR2hQclNUZEJxdHFac1doWGE3aDdLTWEKeHdvamh5SXpnTXNyK2tXODdlajhDQ2h0d21sQ1p5QU92QmdOZytncnJ1cEZLM3FOSkpKeU9YREdHckdpbzZmTQp5aXB3Q2tZVGVxRThpZ1J6UkI5QkdFUGY4eVpjMUtwdmZhUDVhM0lRZmxiV0czbGpUemNNZVZjPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= diff --git a/pkg/k8s/kubeconfig/testdata/kustomization.yaml b/pkg/k8s/kubeconfig/testdata/kustomization.yaml deleted file mode 100644 index f0775f3a6..000000000 --- a/pkg/k8s/kubeconfig/testdata/kustomization.yaml +++ /dev/null @@ -1,2 +0,0 @@ -resources: - - kubeconfig.yaml diff --git a/pkg/k8s/kubeconfig/testdata/result-kubeconf b/pkg/k8s/kubeconfig/testdata/result-kubeconf deleted file mode 100644 index d3bcf7f2c..000000000 --- a/pkg/k8s/kubeconfig/testdata/result-kubeconf +++ /dev/null @@ -1,43 +0,0 @@ -apiVersion: v1 -clusters: -- cluster: - certificate-authority-data: c29tZWNlcnQK - server: https://10.23.25.101:6443 - name: child_cluster -- cluster: - certificate-authority-data: c29tZWNlcnQK - server: https://10.23.25.101:6443 - name: parent_cluster -- cluster: - certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1USXlOakE0TWpneU5Gb1hEVEk1TVRJeU16QTRNamd5TkZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTTFSClM0d3lnajNpU0JBZjlCR0JUS1p5VTFwYmdDaGQ2WTdJektaZWRoakM2K3k1ZEJpWm81ZUx6Z2tEc2gzOC9YQ1MKenFPS2V5cE5RcDN5QVlLdmJKSHg3ODZxSFZZNjg1ZDVYVDNaOHNyVVRzVDR5WmNzZHAzV3lHdDM0eXYzNi9BSQoxK1NlUFErdU5JemN6bzNEdWhXR0ZoQjk3VjZwRitFUTBlVWN5bk05c2hkL3AwWVFzWDR1ZlhxaENENVpzZnZUCnBka3UvTWkyWnVGUldUUUtNeGpqczV3Z2RBWnBsNnN0L2ZkbmZwd1Q5cC9WTjRuaXJnMEsxOURTSFFJTHVrU2MKb013bXNBeDJrZmxITWhPazg5S3FpMEloL2cyczRFYTRvWURZemt0Y2JRZ24wd0lqZ2dmdnVzM3pRbEczN2lwYQo4cVRzS2VmVGdkUjhnZkJDNUZNQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFJek9BL00xWmRGUElzd2VoWjFuemJ0VFNURG4KRHMyVnhSV0VnclFFYzNSYmV3a1NkbTlBS3MwVGR0ZHdEbnBEL2tRYkNyS2xEeFF3RWg3NFZNSFZYYkFadDdsVwpCSm90T21xdXgxYThKYklDRTljR0FHRzFvS0g5R29jWERZY0JzOTA3ckxIdStpVzFnL0xVdG5hN1dSampqZnBLCnFGelFmOGdJUHZIM09BZ3B1RVVncUx5QU8ya0VnelZwTjZwQVJxSnZVRks2TUQ0YzFmMnlxWGxwNXhrN2dFSnIKUzQ4WmF6d0RmWUVmV3Jrdld1YWdvZ1M2SktvbjVEZ0Z1ZHhINXM2Snl6R3lPVnZ0eG1TY2FvOHNxaCs3UXkybgoyLzFVcU5ZK0hlN0x4d04rYkhwYkIxNUtIMTU5ZHNuS3BRbjRORG1jSTZrVnJ3MDVJMUg5ZGRBbGF0bz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= - server: https://10.23.25.101:6443 - name: parent_parent_cluster -contexts: -- context: - cluster: child_cluster - user: child_user - name: child -- context: - cluster: parent_cluster - user: parent_admin - name: parent-custom -- context: - cluster: parent_parent_cluster - user: parent_parent_admin - name: parent_parent_context -current-context: dummy_cluster -kind: Config -preferences: {} -users: -- name: child_user - user: - client-certificate-data: c29tZWNlcnQK - client-key-data: c29tZWNlcnQK -- name: parent_admin - user: - client-certificate-data: c29tZWNlcnQK - client-key-data: c29tZWNlcnQK -- name: parent_parent_admin - user: - client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQwRENDQXJnQ0ZFdFBveEZYSjVrVFNWTXQ0OVlqcHBQL3hCYnlNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1CVXgKRXpBUkJnTlZCQU1UQ210MVltVnlibVYwWlhNd0hoY05NakF3TVRJME1Ua3hOVEV3V2hjTk1qa3hNakF5TVRreApOVEV3V2pBME1Sa3dGd1lEVlFRRERCQnJkV0psY201bGRHVnpMV0ZrYldsdU1SY3dGUVlEVlFRS0RBNXplWE4wClpXMDZiV0Z6ZEdWeWN6Q0NBaUl3RFFZSktvWklodmNOQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0lCQU1iaFhUUmsKVjZiZXdsUjBhZlpBdTBGYWVsOXRtRThaSFEvaGtaSHhuTjc2bDZUUFltcGJvaDRvRjNGMFFqbzROS1o5NVRuWgo0OWNoV240eFJiZVlPU25EcDBpV0Qzd0pXUlZ5aVFvVUFyYTlNcHVPNkVFU1FpbFVGNXNxc0VXUVdVMjBETStBCkdxK1k0Z2c3eDJ1Q0hTdk1GUmkrNEw5RWlXR2xnRDIvb1hXUm5NWEswNExQajZPb3Vkb2Zid2RmT3J6dTBPVkUKUzR0eGtuS1BCY1BUU3YxMWVaWVhja0JEVjNPbExENEZ3dTB3NTcwcnczNzAraEpYdlZxd3Zjb2RjZjZEL1BXWQowamlnd2ppeUJuZ2dXYW04UVFjd1Nud3o0d05sV3hKOVMyWUJFb1ptdWxVUlFaWVk5ZXRBcEpBdFMzTjlUNlQ2ClovSlJRdEdhZDJmTldTYkxEck5qdU1OTGhBYWRMQnhJUHpBNXZWWk5aalJkdEMwU25pMlFUMTVpSFp4d1RxcjQKakRQQ0pYRXU3KytxcWpQVldUaUZLK3JqcVNhS1pqVWZVaUpHQkJWcm5RZkJENHNtRnNkTjB5cm9tYTZOYzRMNQpKS21RV1NHdmd1aG0zbW5sYjFRaVRZanVyZFJQRFNmdmwrQ0NHbnA1QkkvZ1pwMkF1SHMvNUpKVTJlc1ZvL0xsCkVPdHdSOXdXd3dXcTAvZjhXS3R4bVRrMTUyOUp2dFBGQXQweW1CVjhQbHZlYnVwYmJqeW5pL2xWbTJOYmV6dWUKeCtlMEpNbGtWWnFmYkRSS243SjZZSnJHWW1CUFV0QldoSVkzb1pJVTFEUXI4SUlIbkdmYlZoWlR5ME1IMkFCQQp1dlVQcUtSVk80UGkxRTF4OEE2eWVPeVRDcnB4L0pBazVyR2RBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFECmdnRUJBSWNFM1BxZHZDTVBIMnJzMXJESk9ESHY3QWk4S01PVXZPRi90RjlqR2EvSFBJbkh3RlVFNEltbldQeDYKVUdBMlE1bjFsRDFGQlU0T0M4eElZc3VvS1VQVHk1T0t6SVNMNEZnL0lEcG54STlrTXlmNStMR043aG8rblJmawpCZkpJblVYb0tERW1neHZzSWFGd1h6bGtSTDJzL1lKYUZRRzE1Uis1YzFyckJmd2dJOFA5Tkd6aEM1cXhnSmovCm04K3hPMGhXUmJIYklrQ21NekRib2pCSWhaL00rb3VYR1doei9TakpodXhZTVBnek5MZkFGcy9PMTVaSjd3YXcKZ3ZoSGc3L2E5UzRvUCtEYytPa3VrMkV1MUZjL0E5WHpWMzc5aWhNWW5ub3RQMldWeFZ3b0ZZQUg0NUdQcDZsUApCQmwyNnkxc2JMbjl6aGZYUUJIMVpFN0EwZVE9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K - client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBdXpHSll2UFo2RG9pNDIxREs4V0phWkNuTkFkMnF6NXAvMDQyb0Z6UVBickFnekUyCnFZVmt6T0w4eEFWZVI3U041d1dvVFdFeUY4RVY3cnIvNCtIaEhHaXE1UG1xdUlGeXp6bjYvSVpjOGpVOXhFZnoKdmlrY2lyTGZVNHZSWEpRd1Z3Z0FTTmwyQVdBSWgyZERkVHJqQkNmaUtXTUh5ajBSYkhhbHNCek9wZ1QvSFR2MwpHUXpuVVF6Rkt2MmRqNVYxTmtTL0RIanlSUkorRUw2UUJZbUdzZWc1UDROYkM5ZWJ1aXBtTVRBcS9KdW1Pb29kCitGakwyblpxTDZmSTZmQnRGNU9HbHBDQjFZSjhmekN0R1FRWjdIVEliZGIydHA0M0ZWT2h5UWJWY0hxVEEwNFAKSjE1KzBXQXltVUpVejhYQTU0NHIvYnY3NEpjSlVSRmhhclppUXdJREFRQUJBb0lCQVFDU0pycjlaeVpiQ2dqegpSL3VKMFZEWCt2aVF4c01BTUZyUjJsOE1GV3NBeHk1SFA4Vk4xYmc5djN0YUVGYnI1U3hsa3lVMFJRNjNQU25DCm1uM3ZqZ3dVQWlScllnTEl5MGk0UXF5VFBOU1V4cnpTNHRxTFBjM3EvSDBnM2FrNGZ2cSsrS0JBUUlqQnloamUKbnVFc1JpMjRzT3NESlM2UDE5NGlzUC9yNEpIM1M5bFZGbkVuOGxUR2c0M1kvMFZoMXl0cnkvdDljWjR5ZUNpNwpjMHFEaTZZcXJZaFZhSW9RRW1VQjdsbHRFZkZzb3l4VDR6RTE5U3pVbkRoMmxjYTF1TzhqcmI4d2xHTzBoQ2JyClB1R1l2WFFQa3Q0VlNmalhvdGJ3d2lBNFRCVERCRzU1bHp6MmNKeS9zSS8zSHlYbEMxcTdXUmRuQVhhZ1F0VzkKOE9DZGRkb0JBb0dCQU5NcUNtSW94REtyckhZZFRxT1M1ZFN4cVMxL0NUN3ZYZ0pScXBqd2Y4WHA2WHo0KzIvTAozVXFaVDBEL3dGTkZkc1Z4eFYxMnNYMUdwMHFWZVlKRld5OVlCaHVSWGpTZ0ZEWldSY1Z1Y01sNVpPTmJsbmZGCjVKQ0xnNXFMZ1g5VTNSRnJrR3A0R241UDQxamg4TnhKVlhzZG5xWE9xNTFUK1RRT1UzdkpGQjc1QW9HQkFPTHcKalp1cnZtVkZyTHdaVGgvRDNpWll5SVV0ZUljZ2NKLzlzbTh6L0pPRmRIbFd4dGRHUFVzYVd1MnBTNEhvckFtbgpqTm4vSTluUXd3enZ3MWUzVVFPbUhMRjVBczk4VU5hbk5TQ0xNMW1yaXZHRXJ1VHFnTDM1bU41eFZPdTUxQU5JCm4yNkFtODBJT2JDeEtLa0R0ZXJSaFhHd3g5c1pONVJCbG9VRThZNGJBb0dBQ3ZsdVhMZWRxcng5VkE0bDNoNXUKVDJXRVUxYjgxZ1orcmtRc1I1S0lNWEw4cllBTElUNUpHKzFuendyN3BkaEFXZmFWdVV2SDRhamdYT0h6MUs5aQpFODNSVTNGMG9ldUg0V01PY1RwU0prWm0xZUlXcWRiaEVCb1FGdUlWTXRib1BsV0d4ZUhFRHJoOEtreGp4aThSCmdEcUQyajRwY1IzQ0g5QjJ5a0lqQjVFQ2dZRUExc0xXLys2enE1c1lNSm14K1JXZThhTXJmL3pjQnVTSU1LQWgKY0dNK0wwMG9RSHdDaUU4TVNqcVN1ajV3R214YUFuanhMb3ZwSFlRV1VmUEVaUW95UE1YQ2VhRVBLOU4xbk8xMwp0V2lHRytIZkIxaU5PazFCc0lhNFNDbndOM1FRVTFzeXBaeEgxT3hueS9LYmkvYmEvWEZ5VzNqMGFUK2YvVWxrCmJGV1ZVdWtDZ1lFQTBaMmRTTFlmTjV5eFNtYk5xMWVqZXdWd1BjRzQxR2hQclNUZEJxdHFac1doWGE3aDdLTWEKeHdvamh5SXpnTXNyK2tXODdlajhDQ2h0d21sQ1p5QU92QmdOZytncnJ1cEZLM3FOSkpKeU9YREdHckdpbzZmTQp5aXB3Q2tZVGVxRThpZ1J6UkI5QkdFUGY4eVpjMUtwdmZhUDVhM0lRZmxiV0czbGpUemNNZVZjPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= diff --git a/pkg/k8s/kubeconfig/testdata_fail/kustomization.yaml b/pkg/k8s/kubeconfig/testdata_fail/kustomization.yaml deleted file mode 100644 index f0775f3a6..000000000 --- a/pkg/k8s/kubeconfig/testdata_fail/kustomization.yaml +++ /dev/null @@ -1,2 +0,0 @@ -resources: - - kubeconfig.yaml diff --git a/pkg/k8s/utils/utils.go b/pkg/k8s/utils/utils.go deleted file mode 100644 index cc8069415..000000000 --- a/pkg/k8s/utils/utils.go +++ /dev/null @@ -1,43 +0,0 @@ -/* - 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 utils - -import ( - "k8s.io/cli-runtime/pkg/genericclioptions" - cmdutil "k8s.io/kubectl/pkg/cmd/util" -) - -// ClientOption is a function that allows to modify ConfigFlags object which is used to create Client -type ClientOption func(*genericclioptions.ConfigFlags) - -// SetTimeout sets Timeout option in ConfigFlags object -func SetTimeout(timeout string) ClientOption { - return func(co *genericclioptions.ConfigFlags) { - *co.Timeout = timeout - } -} - -// FactoryFromKubeConfig returns a factory with the -// default Kubernetes resources for the given kube config path and context -func FactoryFromKubeConfig(path, context string, opts ...ClientOption) cmdutil.Factory { - kf := genericclioptions.NewConfigFlags(false) - kf.KubeConfig = &path - kf.Context = &context - for _, o := range opts { - o(kf) - } - - return cmdutil.NewFactory(cmdutil.NewMatchVersionFlags(kf)) -} diff --git a/pkg/phase/client.go b/pkg/phase/client.go index 0468e9eeb..a15fa5a00 100644 --- a/pkg/phase/client.go +++ b/pkg/phase/client.go @@ -27,7 +27,6 @@ import ( "opendev.org/airship/airshipctl/pkg/container" "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/events" - "opendev.org/airship/airshipctl/pkg/k8s/kubeconfig" "opendev.org/airship/airshipctl/pkg/log" "opendev.org/airship/airshipctl/pkg/phase/errors" "opendev.org/airship/airshipctl/pkg/phase/executors" @@ -81,25 +80,10 @@ func (p *phase) Executor() (ifc.Executor, error) { return nil, executorerrors.ErrExecutorNotFound{GVK: refGVK} } - cMap, err := p.helper.ClusterMap() - if err != nil { - return nil, err - } - - kubeconf := kubeconfig.NewBuilder(). - WithBundle(p.helper.PhaseConfigBundle()). - WithClusterMap(cMap). - WithTempRoot(p.helper.WorkDir()). - WithClusterNames(p.apiObj.ClusterName). - SiteWide(p.apiObj.Config.SiteWideKubeconfig). - Build() - return executorFactory( ifc.ExecutorConfig{ - ClusterMap: cMap, BundleFactory: document.BundleFactoryFromDocRoot(p.DocumentRoot), PhaseName: p.apiObj.Name, - KubeConfig: kubeconf, ExecutorDocument: executorDoc, ClusterName: p.apiObj.ClusterName, PhaseConfigBundle: p.helper.PhaseConfigBundle(), diff --git a/pkg/phase/command.go b/pkg/phase/command.go index 78f471ddc..95a0145ec 100644 --- a/pkg/phase/command.go +++ b/pkg/phase/command.go @@ -21,7 +21,6 @@ import ( "strings" "time" - "opendev.org/airship/airshipctl/pkg/cluster/clustermap" "opendev.org/airship/airshipctl/pkg/config" "opendev.org/airship/airshipctl/pkg/document" phaseerrors "opendev.org/airship/airshipctl/pkg/phase/errors" @@ -39,8 +38,10 @@ const ( // GenericRunFlags generic options for run command type GenericRunFlags struct { - DryRun bool - Timeout time.Duration + Kubeconfig string + Context string + DryRun bool + Timeout time.Duration } // RunFlags options for phase run command @@ -232,37 +233,6 @@ func (c *PlanRunCommand) RunE() error { return plan.Run(c.Options) } -// ClusterListCommand options for cluster list command -type ClusterListCommand struct { - Factory config.Factory - Writer io.Writer - Format string -} - -// RunE executes cluster list command -func (c *ClusterListCommand) RunE() error { - if c.Format != TableOutputFormat && c.Format != "name" { - return phaseerrors.ErrInvalidOutputFormat{RequestedFormat: c.Format} - } - cfg, err := c.Factory() - if err != nil { - return err - } - helper, err := NewHelper(cfg) - if err != nil { - return err - } - clusterMap, err := helper.ClusterMap() - if err != nil { - return err - } - err = clusterMap.Write(c.Writer, clustermap.WriteOptions{Format: c.Format}) - if err != nil { - return err - } - return nil -} - // ValidateFlags options for phase validate command type ValidateFlags struct { PhaseID ifc.ID diff --git a/pkg/phase/command_test.go b/pkg/phase/command_test.go index df26e912a..8a62114c5 100644 --- a/pkg/phase/command_test.go +++ b/pkg/phase/command_test.go @@ -499,80 +499,6 @@ func TestPlanRunCommand(t *testing.T) { } } -func TestClusterListCommand_RunE(t *testing.T) { - testErr := fmt.Errorf(testFactoryErr) - testCases := []struct { - name string - factory config.Factory - expectedErr string - Format string - }{ - { - name: "Error config factory", - factory: func() (*config.Config, error) { - return nil, testErr - }, - expectedErr: testFactoryErr, - Format: "name", - }, - { - name: "Error new helper", - factory: func() (*config.Config, error) { - return &config.Config{ - CurrentContext: "does not exist", - Contexts: make(map[string]*config.Context), - }, nil - }, - expectedErr: "missing configuration: context with name 'does not exist'", - Format: "name", - }, - { - name: "No error", - Format: "name", - factory: func() (*config.Config, error) { - conf := config.NewConfig() - conf.Manifests = map[string]*config.Manifest{ - "manifest": { - MetadataPath: "metadata.yaml", - TargetPath: "testdata", - PhaseRepositoryName: config.DefaultTestPhaseRepo, - Repositories: map[string]*config.Repository{ - config.DefaultTestPhaseRepo: { - URLString: "", - }, - }, - }, - } - conf.CurrentContext = defaultCurrentContext - conf.Contexts = map[string]*config.Context{ - "context": { - Manifest: "manifest", - }, - } - return conf, nil - }, - expectedErr: "", - }, - } - for _, tc := range testCases { - tt := tc - t.Run(tt.name, func(t *testing.T) { - cmd := phase.ClusterListCommand{ - Factory: tt.factory, - Format: tt.Format, - Writer: bytes.NewBuffer(nil), - } - err := cmd.RunE() - if tt.expectedErr != "" { - require.Error(t, err) - assert.Equal(t, tt.expectedErr, err.Error()) - } else { - assert.NoError(t, err) - } - }) - } -} - func TestValidateCommand(t *testing.T) { tests := []struct { name string diff --git a/pkg/phase/executors/clusterctl.go b/pkg/phase/executors/clusterctl.go index 471138168..9b6e83a28 100644 --- a/pkg/phase/executors/clusterctl.go +++ b/pkg/phase/executors/clusterctl.go @@ -26,12 +26,10 @@ import ( "sigs.k8s.io/kustomize/kyaml/yaml" airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1" - "opendev.org/airship/airshipctl/pkg/cluster/clustermap" "opendev.org/airship/airshipctl/pkg/container" "opendev.org/airship/airshipctl/pkg/document" airerrors "opendev.org/airship/airshipctl/pkg/errors" "opendev.org/airship/airshipctl/pkg/events" - "opendev.org/airship/airshipctl/pkg/k8s/kubeconfig" "opendev.org/airship/airshipctl/pkg/log" phaseerrors "opendev.org/airship/airshipctl/pkg/phase/errors" "opendev.org/airship/airshipctl/pkg/phase/executors/errors" @@ -47,9 +45,7 @@ type ClusterctlExecutor struct { clusterName string targetPath string - clusterMap clustermap.ClusterMap options *airshipv1.Clusterctl - kubecfg kubeconfig.Interface execObj *airshipv1.GenericContainer clientFunc container.ClientV1Alpha1FactoryFunc cctlOpts *airshipv1.ClusterctlOptions @@ -95,8 +91,6 @@ func NewClusterctlExecutor(cfg ifc.ExecutorConfig) (ifc.Executor, error) { clusterName: cfg.ClusterName, options: options, cctlOpts: cctlOpts, - kubecfg: cfg.KubeConfig, - clusterMap: cfg.ClusterMap, targetPath: cfg.TargetPath, execObj: apiObj, clientFunc: clientFunc, @@ -159,6 +153,14 @@ func (c *ClusterctlExecutor) Run(evtCh chan events.Event, opts ifc.RunOptions) { c.cctlOpts.CmdOptions = append(c.cctlOpts.CmdOptions, "-v5") } + c.options.Kubeconfig = map[bool]string{true: opts.Kubeconfig, + false: map[bool]string{true: KubeConfigFile, + false: c.options.Kubeconfig}[c.options.Kubeconfig == ""]}[opts.Kubeconfig != ""] + + c.options.Context = map[bool]string{true: opts.Context, + false: map[bool]string{true: c.clusterName, + false: c.options.Context}[c.options.Context == ""]}[opts.Context != ""] + cctlConfig := map[string]interface{}{ "providers": c.options.Providers, "images": c.options.ImageMetas, @@ -173,6 +175,13 @@ func (c *ClusterctlExecutor) Run(evtCh chan events.Event, opts ifc.RunOptions) { handleError(evtCh, err) } + c.execObj.Spec.StorageMounts = append(c.execObj.Spec.StorageMounts, airshipv1.StorageMount{ + MountType: "bind", + Src: c.options.Kubeconfig, + DstPath: KubeConfigMount, + ReadWriteMode: false, + }) + switch c.options.Action { case airshipv1.Init: c.init(evtCh) @@ -192,44 +201,16 @@ func (c *ClusterctlExecutor) run() error { return c.clientFunc("", &bytes.Buffer{}, os.Stdout, c.execObj, c.targetPath).Run() } -func (c *ClusterctlExecutor) getKubeconfig() (string, string, func(), error) { - kubeConfigFile, cleanup, err := c.kubecfg.GetFile() - if err != nil { - return "", "", nil, err - } - - context, err := c.clusterMap.ClusterKubeconfigContext(c.clusterName) - if err != nil { - cleanup() - return "", "", nil, err - } - - c.execObj.Spec.StorageMounts = append(c.execObj.Spec.StorageMounts, airshipv1.StorageMount{ - MountType: "bind", - Src: kubeConfigFile, - DstPath: kubeConfigFile, - ReadWriteMode: false, - }) - return kubeConfigFile, context, cleanup, nil -} - func (c *ClusterctlExecutor) init(evtCh chan events.Event) { evtCh <- events.NewEvent().WithClusterctlEvent(events.ClusterctlEvent{ Operation: events.ClusterctlInitStart, Message: "starting clusterctl init executor", }) - kubecfg, context, cleanup, err := c.getKubeconfig() - if err != nil { - handleError(evtCh, err) - return - } - defer cleanup() - c.cctlOpts.CmdOptions = append(c.cctlOpts.CmdOptions, "init", - "--kubeconfig", kubecfg, - "--kubeconfig-context", context, + "--kubeconfig", KubeConfigMount, + "--kubeconfig-context", c.options.Context, ) initMap := map[string]string{ @@ -244,7 +225,7 @@ func (c *ClusterctlExecutor) init(evtCh chan events.Event) { } } - if err = c.run(); err != nil { + if err := c.run(); err != nil { handleError(evtCh, err) return } @@ -261,31 +242,14 @@ func (c *ClusterctlExecutor) move(dryRun bool, evtCh chan events.Event) { Operation: events.ClusterctlMoveStart, Message: "starting clusterctl move executor", }) - kubecfg, context, cleanup, err := c.getKubeconfig() - if err != nil { - handleError(evtCh, err) - return - } - defer cleanup() - - fromCluster, err := c.clusterMap.ParentCluster(c.clusterName) - if err != nil { - handleError(evtCh, err) - return - } - fromContext, err := c.clusterMap.ClusterKubeconfigContext(fromCluster) - if err != nil { - handleError(evtCh, err) - return - } c.cctlOpts.CmdOptions = append( c.cctlOpts.CmdOptions, "move", - "--kubeconfig", kubecfg, - "--kubeconfig-context", fromContext, - "--to-kubeconfig", kubecfg, - "--to-kubeconfig-context", context, + "--kubeconfig", KubeConfigMount, + "--kubeconfig-context", c.options.Context, + "--to-kubeconfig", KubeConfigMount, + "--to-kubeconfig-context", c.options.MoveOptions.TargetContext, "--namespace", c.options.MoveOptions.Namespace, ) @@ -296,7 +260,7 @@ func (c *ClusterctlExecutor) move(dryRun bool, evtCh chan events.Event) { ) } - if err = c.run(); err != nil { + if err := c.run(); err != nil { handleError(evtCh, err) return } diff --git a/pkg/phase/executors/clusterctl_test.go b/pkg/phase/executors/clusterctl_test.go index 8a038e29e..31080a08b 100644 --- a/pkg/phase/executors/clusterctl_test.go +++ b/pkg/phase/executors/clusterctl_test.go @@ -16,7 +16,6 @@ package executors_test import ( "bytes" - goerrors "errors" "fmt" "io" "testing" @@ -26,11 +25,9 @@ import ( "github.com/stretchr/testify/require" "opendev.org/airship/airshipctl/pkg/api/v1alpha1" - "opendev.org/airship/airshipctl/pkg/cluster/clustermap" "opendev.org/airship/airshipctl/pkg/container" "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/events" - "opendev.org/airship/airshipctl/pkg/k8s/kubeconfig" "opendev.org/airship/airshipctl/pkg/phase/executors" "opendev.org/airship/airshipctl/pkg/phase/executors/errors" "opendev.org/airship/airshipctl/pkg/phase/ifc" @@ -199,37 +196,6 @@ providers: } } -var _ clustermap.ClusterMap = &ClusterMapMockInterface{} - -type ClusterMapMockInterface struct { - MockClusterKubeconfigContext func(string) (string, error) - MockParentCluster func(string) (string, error) -} - -func (c ClusterMapMockInterface) ValidateClusterMap() error { - panic("implement me") -} - -func (c ClusterMapMockInterface) ParentCluster(s string) (string, error) { - return c.MockParentCluster(s) -} - -func (c ClusterMapMockInterface) AllClusters() []string { - panic("implement me") -} - -func (c ClusterMapMockInterface) ClusterKubeconfigContext(s string) (string, error) { - return c.MockClusterKubeconfigContext(s) -} - -func (c ClusterMapMockInterface) Sources(_ string) ([]v1alpha1.KubeconfigSource, error) { - panic("implement me") -} - -func (c ClusterMapMockInterface) Write(_ io.Writer, _ clustermap.WriteOptions) error { - panic("implement me") -} - var _ container.ClientV1Alpha1 = &MockClientFuncInterface{} type MockClientFuncInterface struct { @@ -241,15 +207,10 @@ func (c MockClientFuncInterface) Run() error { } func TestClusterctlExecutorRun(t *testing.T) { - errTmpFile := goerrors.New("TmpFile error") - errCtx := goerrors.New("context error") - errParent := goerrors.New("parent cluster error") testCases := []struct { name string cfgDoc document.Document - kubecfg kubeconfig.Interface expectedEvt []events.Event - clusterMap clustermap.ClusterMap clientFunc container.ClientV1Alpha1FactoryFunc }{ { @@ -258,96 +219,10 @@ func TestClusterctlExecutorRun(t *testing.T) { expectedEvt: []events.Event{ wrapError(errors.ErrUnknownExecutorAction{Action: "someAction", ExecutorName: "clusterctl"}), }, - clusterMap: clustermap.NewClusterMap(v1alpha1.DefaultClusterMap()), - }, - { - name: "Failed get kubeconfig file - init", - cfgDoc: executorDoc(t, fmt.Sprintf(executorConfigTmplGood, "init")), - kubecfg: fakeKubeConfig{getFile: func() (string, kubeconfig.Cleanup, error) { - return "", nil, errTmpFile - }}, - expectedEvt: []events.Event{ - events.NewEvent().WithClusterctlEvent(events.ClusterctlEvent{ - Operation: events.ClusterctlInitStart, - }), - wrapError(errTmpFile), - }, - clusterMap: clustermap.NewClusterMap(v1alpha1.DefaultClusterMap()), - }, - { - name: "Failed get kubeconfig file - move", - cfgDoc: executorDoc(t, fmt.Sprintf(executorConfigTmplGood, "move")), - kubecfg: fakeKubeConfig{getFile: func() (string, kubeconfig.Cleanup, error) { - return "", nil, errTmpFile - }}, - expectedEvt: []events.Event{ - events.NewEvent().WithClusterctlEvent(events.ClusterctlEvent{ - Operation: events.ClusterctlMoveStart, - }), - wrapError(errTmpFile), - }, - clusterMap: clustermap.NewClusterMap(v1alpha1.DefaultClusterMap()), - }, - { - name: "Failed get kubeconfig context - init", - cfgDoc: executorDoc(t, fmt.Sprintf(executorConfigTmplGood, "init")), - kubecfg: fakeKubeConfig{getFile: func() (string, kubeconfig.Cleanup, error) { - return "", func() {}, nil - }}, - expectedEvt: []events.Event{ - events.NewEvent().WithClusterctlEvent(events.ClusterctlEvent{ - Operation: events.ClusterctlInitStart, - }), - wrapError(errCtx), - }, - clusterMap: ClusterMapMockInterface{MockClusterKubeconfigContext: func(s string) (string, error) { - return "", errCtx - }}, - }, - { - name: "Failed get kubeconfig context - move", - cfgDoc: executorDoc(t, fmt.Sprintf(executorConfigTmplGood, "move")), - kubecfg: fakeKubeConfig{getFile: func() (string, kubeconfig.Cleanup, error) { - return "", func() {}, nil - }}, - expectedEvt: []events.Event{ - events.NewEvent().WithClusterctlEvent(events.ClusterctlEvent{ - Operation: events.ClusterctlMoveStart, - }), - wrapError(errCtx), - }, - clusterMap: ClusterMapMockInterface{MockClusterKubeconfigContext: func(s string) (string, error) { - return "", errCtx - }}, - }, - { - name: "Failed get parent cluster", - cfgDoc: executorDoc(t, fmt.Sprintf(executorConfigTmplGood, "move")), - kubecfg: fakeKubeConfig{getFile: func() (string, kubeconfig.Cleanup, error) { - return "", func() {}, nil - }}, - expectedEvt: []events.Event{ - events.NewEvent().WithClusterctlEvent(events.ClusterctlEvent{ - Operation: events.ClusterctlMoveStart, - }), - wrapError(errParent), - }, - clusterMap: ClusterMapMockInterface{MockClusterKubeconfigContext: func(s string) (string, error) { - return "ctx", nil - }, - MockParentCluster: func(s string) (string, error) { - return "", errParent - }}, }, { name: "Regular Run init", cfgDoc: executorDoc(t, fmt.Sprintf(executorConfigTmplGood, "init")), - kubecfg: fakeKubeConfig{getFile: func() (string, kubeconfig.Cleanup, error) { - return "", func() {}, nil - }}, - clusterMap: ClusterMapMockInterface{MockClusterKubeconfigContext: func(s string) (string, error) { - return "cluster", nil - }}, clientFunc: func(_ string, _ io.Reader, _ io.Writer, _ *v1alpha1.GenericContainer, _ string) container.ClientV1Alpha1 { return MockClientFuncInterface{MockRun: func() error { @@ -366,15 +241,6 @@ func TestClusterctlExecutorRun(t *testing.T) { { name: "Regular Run move", cfgDoc: executorDoc(t, fmt.Sprintf(executorConfigTmplGood, "move")), - kubecfg: fakeKubeConfig{getFile: func() (string, kubeconfig.Cleanup, error) { - return "", func() {}, nil - }}, - clusterMap: ClusterMapMockInterface{MockClusterKubeconfigContext: func(s string) (string, error) { - return "cluster", nil - }, - MockParentCluster: func(s string) (string, error) { - return "parentCluster", nil - }}, clientFunc: func(_ string, _ io.Reader, _ io.Writer, _ *v1alpha1.GenericContainer, _ string) container.ClientV1Alpha1 { return MockClientFuncInterface{MockRun: func() error { @@ -399,8 +265,6 @@ func TestClusterctlExecutorRun(t *testing.T) { TargetPath: "testdata", PhaseConfigBundle: executorBundle(t, krmExecDoc), ExecutorDocument: tt.cfgDoc, - KubeConfig: tt.kubecfg, - ClusterMap: tt.clusterMap, ContainerFunc: tt.clientFunc, }) require.NoError(t, err) diff --git a/pkg/phase/executors/common.go b/pkg/phase/executors/common.go index 9043f37e1..cf5df1eb6 100644 --- a/pkg/phase/executors/common.go +++ b/pkg/phase/executors/common.go @@ -30,6 +30,9 @@ const ( GenericContainer = "generic-container" Ephemeral = "ephemeral" BMHManager = "BaremetalManager" + + KubeConfigFile = "~/.kube/config" + KubeConfigMount = "/kubeconfig" ) // RegisterExecutor adds executor to phase executor registry diff --git a/pkg/phase/executors/common_test.go b/pkg/phase/executors/common_test.go index ca7bfbc3d..d08150dd0 100644 --- a/pkg/phase/executors/common_test.go +++ b/pkg/phase/executors/common_test.go @@ -17,9 +17,6 @@ package executors_test import ( "testing" - "opendev.org/airship/airshipctl/pkg/api/v1alpha1" - "opendev.org/airship/airshipctl/pkg/cluster/clustermap" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/runtime/schema" @@ -112,13 +109,3 @@ func wrapError(err error) events.Event { Error: err, }) } - -func testClusterMap(t *testing.T) clustermap.ClusterMap { - doc, err := document.NewDocumentFromBytes([]byte(singleExecutorClusterMap)) - require.NoError(t, err) - require.NotNil(t, doc) - apiObj := v1alpha1.DefaultClusterMap() - err = doc.ToAPIObject(apiObj, v1alpha1.Scheme) - require.NoError(t, err) - return clustermap.NewClusterMap(apiObj) -} diff --git a/pkg/phase/executors/container.go b/pkg/phase/executors/container.go index a3489d401..702e6189d 100644 --- a/pkg/phase/executors/container.go +++ b/pkg/phase/executors/container.go @@ -26,7 +26,6 @@ import ( "opendev.org/airship/airshipctl/pkg/document" commonerrors "opendev.org/airship/airshipctl/pkg/errors" "opendev.org/airship/airshipctl/pkg/events" - "opendev.org/airship/airshipctl/pkg/k8s/kubeconfig" "opendev.org/airship/airshipctl/pkg/log" "opendev.org/airship/airshipctl/pkg/phase/errors" "opendev.org/airship/airshipctl/pkg/phase/ifc" @@ -74,10 +73,9 @@ func NewContainerExecutor(cfg ifc.ExecutorConfig) (ifc.Executor, error) { MountBasePath: cfg.TargetPath, ExecutorBundle: bundle, ExecutorDocument: cfg.ExecutorDocument, - // TODO extend tests with proper client, make it interface - ClientFunc: container.NewClientV1Alpha1, - Container: apiObj, - Options: cfg, + ClientFunc: container.NewClientV1Alpha1, + Container: apiObj, + Options: cfg, }, nil } @@ -91,12 +89,15 @@ func (c *ContainerExecutor) Run(evtCh chan events.Event, opts ifc.RunOptions) { }) if c.Options.ClusterName != "" { - cleanup, err := c.SetKubeConfig() + if opts.Kubeconfig == "" { + opts.Kubeconfig = KubeConfigFile + } + + err := c.SetKubeConfig(opts.Kubeconfig, c.Options.ClusterName) if err != nil { handleError(evtCh, err) return } - defer cleanup() } input, err := bundleReader(c.ExecutorBundle) @@ -138,24 +139,16 @@ func (c *ContainerExecutor) Run(evtCh chan events.Event, opts ifc.RunOptions) { } // SetKubeConfig adds env variable and mounts kubeconfig to container -func (c *ContainerExecutor) SetKubeConfig() (kubeconfig.Cleanup, error) { - context, err := c.Options.ClusterMap.ClusterKubeconfigContext(c.Options.ClusterName) - if err != nil { - return nil, err - } - kubeConfigSrc, cleanup, err := c.Options.KubeConfig.GetFile() - if err != nil { - return nil, err - } +func (c *ContainerExecutor) SetKubeConfig(kubeconfig, context string) error { c.Container.Spec.StorageMounts = append(c.Container.Spec.StorageMounts, v1alpha1.StorageMount{ MountType: "bind", - Src: kubeConfigSrc, - DstPath: v1alpha1.KubeConfigPath, + Src: kubeconfig, + DstPath: KubeConfigMount, }) envs := []string{v1alpha1.KubeConfigEnv, v1alpha1.KubeConfigEnvKeyContext + "=" + context} c.Container.Spec.EnvVars = append(c.Container.Spec.EnvVars, envs...) - return cleanup, nil + return nil } // bundleReader sets input for function diff --git a/pkg/phase/executors/container_test.go b/pkg/phase/executors/container_test.go index ae2a39e3d..749581bdb 100644 --- a/pkg/phase/executors/container_test.go +++ b/pkg/phase/executors/container_test.go @@ -15,8 +15,6 @@ package executors_test import ( "bytes" goerrors "errors" - "fmt" - "io" "testing" "github.com/stretchr/testify/assert" @@ -28,7 +26,6 @@ import ( "opendev.org/airship/airshipctl/pkg/container" "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/events" - "opendev.org/airship/airshipctl/pkg/k8s/kubeconfig" "opendev.org/airship/airshipctl/pkg/phase/errors" "opendev.org/airship/airshipctl/pkg/phase/executors" "opendev.org/airship/airshipctl/pkg/phase/ifc" @@ -65,16 +62,6 @@ const ( cmd: encrypt unencrypted-regex: '^(kind|apiVersion|group|metadata)$'` - singleExecutorClusterMap = `apiVersion: airshipit.org/v1alpha1 -kind: ClusterMap -metadata: - labels: - airshipit.org/deploy-k8s: "false" - name: main-map -map: - testCluster: {} -` - refConfig = `apiVersion: v1 kind: Secret metadata: @@ -241,13 +228,7 @@ func TestGenericContainer(t *testing.T) { Container: tt.containerAPI, ClientFunc: tt.clientFunc, Options: ifc.ExecutorConfig{ - ClusterName: "testCluster", - KubeConfig: fakeKubeConfig{ - getFile: func() (string, kubeconfig.Cleanup, error) { - return "testPath", func() {}, nil - }, - }, - ClusterMap: testClusterMap(t), + ClusterName: "testCluster", PhaseConfigBundle: tt.phaseConfigBundle, }, } @@ -277,7 +258,6 @@ func TestGenericContainer(t *testing.T) { } func TestSetKubeConfig(t *testing.T) { - getFileErr := fmt.Errorf("failed to get file") testCases := []struct { name string opts ifc.ExecutorConfig @@ -287,27 +267,8 @@ func TestSetKubeConfig(t *testing.T) { name: "Set valid kubeconfig", opts: ifc.ExecutorConfig{ ClusterName: "testCluster", - KubeConfig: fakeKubeConfig{ - getFile: func() (string, kubeconfig.Cleanup, error) { - return "testPath", func() {}, nil - }, - }, - ClusterMap: testClusterMap(t), }, }, - { - name: "Failed to get kubeconfig file", - opts: ifc.ExecutorConfig{ - ClusterName: "testCluster", - KubeConfig: fakeKubeConfig{ - getFile: func() (string, kubeconfig.Cleanup, error) { - return "", func() {}, getFileErr - }, - }, - ClusterMap: testClusterMap(t), - }, - expectedErr: getFileErr, - }, } for _, tc := range testCases { @@ -317,7 +278,7 @@ func TestSetKubeConfig(t *testing.T) { Options: tt.opts, Container: &v1alpha1.GenericContainer{}, } - _, err := e.SetKubeConfig() + err := e.SetKubeConfig("", tt.opts.ClusterName) assert.Equal(t, tt.expectedErr, err) }) } @@ -372,14 +333,3 @@ metadata: }) } } - -type fakeKubeConfig struct { - getFile func() (string, kubeconfig.Cleanup, error) -} - -func (k fakeKubeConfig) GetFile() (string, kubeconfig.Cleanup, error) { return k.getFile() } -func (k fakeKubeConfig) Write(_ io.Writer) error { return nil } -func (k fakeKubeConfig) WriteFile(_ string, _ kubeconfig.WriteOptions) error { return nil } -func (k fakeKubeConfig) WriteTempFile(_ string) (string, kubeconfig.Cleanup, error) { - return k.getFile() -} diff --git a/pkg/phase/executors/k8s_applier.go b/pkg/phase/executors/k8s_applier.go index 92e936ce0..b1704513d 100644 --- a/pkg/phase/executors/k8s_applier.go +++ b/pkg/phase/executors/k8s_applier.go @@ -22,12 +22,10 @@ import ( "sigs.k8s.io/kustomize/kyaml/yaml" airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1" - "opendev.org/airship/airshipctl/pkg/cluster/clustermap" "opendev.org/airship/airshipctl/pkg/container" "opendev.org/airship/airshipctl/pkg/document" airerrors "opendev.org/airship/airshipctl/pkg/errors" "opendev.org/airship/airshipctl/pkg/events" - "opendev.org/airship/airshipctl/pkg/k8s/kubeconfig" "opendev.org/airship/airshipctl/pkg/log" "opendev.org/airship/airshipctl/pkg/phase/errors" "opendev.org/airship/airshipctl/pkg/phase/ifc" @@ -43,9 +41,7 @@ type KubeApplierExecutor struct { targetPath string apiObject *airshipv1.KubernetesApply - clusterMap clustermap.ClusterMap clusterName string - kubeconfig kubeconfig.Interface clientFunc container.ClientV1Alpha1FactoryFunc execObj *airshipv1.GenericContainer } @@ -83,9 +79,7 @@ func NewKubeApplierExecutor(cfg ifc.ExecutorConfig) (ifc.Executor, error) { BundleName: cfg.PhaseName, ExecutorDocument: cfg.ExecutorDocument, apiObject: apiObj, - clusterMap: cfg.ClusterMap, clusterName: cfg.ClusterName, - kubeconfig: cfg.KubeConfig, clientFunc: clientFunc, execObj: cObj, targetPath: cfg.TargetPath, @@ -99,19 +93,18 @@ func (e *KubeApplierExecutor) Run(ch chan events.Event, runOpts ifc.RunOptions) e.apiObject.Config.Debug = log.DebugEnabled() e.apiObject.Config.PhaseName = e.BundleName - if e.apiObject.Config.Kubeconfig == "" { - kcfg, ctx, cleanup, err := e.getKubeconfig() - if err != nil { - handleError(ch, err) - return - } - defer cleanup() - e.apiObject.Config.Kubeconfig, e.apiObject.Config.Context = kcfg, ctx - } + e.apiObject.Config.Kubeconfig = map[bool]string{true: runOpts.Kubeconfig, + false: map[bool]string{true: KubeConfigFile, + false: e.apiObject.Config.Kubeconfig}[e.apiObject.Config.Kubeconfig == ""]}[runOpts.Kubeconfig != ""] + + e.apiObject.Config.Context = map[bool]string{true: runOpts.Context, + false: map[bool]string{true: e.clusterName, + false: e.apiObject.Config.Context}[e.apiObject.Config.Context == ""]}[runOpts.Context != ""] + e.execObj.Spec.StorageMounts = append(e.execObj.Spec.StorageMounts, airshipv1.StorageMount{ MountType: "bind", Src: e.apiObject.Config.Kubeconfig, - DstPath: e.apiObject.Config.Kubeconfig, + DstPath: KubeConfigMount, ReadWriteMode: false, }) log.Printf("using kubeconfig at '%s' and context '%s'", e.apiObject.Config.Kubeconfig, e.apiObject.Config.Context) @@ -141,20 +134,6 @@ func (e *KubeApplierExecutor) Run(ch chan events.Event, runOpts ifc.RunOptions) } } -func (e *KubeApplierExecutor) getKubeconfig() (string, string, func(), error) { - log.Debug("Getting kubeconfig context name from cluster map") - ctx, err := e.clusterMap.ClusterKubeconfigContext(e.clusterName) - if err != nil { - return "", "", nil, err - } - log.Debug("Getting kubeconfig file information from kubeconfig provider") - path, cleanup, err := e.kubeconfig.GetFile() - if err != nil { - return "", "", nil, err - } - return path, ctx, cleanup, nil -} - func (e *KubeApplierExecutor) prepareDocuments() (io.Reader, error) { log.Debug("Filtering out documents that shouldn't be applied to kubernetes from document bundle") filteredBundle, err := e.ExecutorBundle.SelectBundle(document.NewDeployToK8sSelector()) diff --git a/pkg/phase/executors/k8s_applier_test.go b/pkg/phase/executors/k8s_applier_test.go index 07738b0db..265884eb7 100644 --- a/pkg/phase/executors/k8s_applier_test.go +++ b/pkg/phase/executors/k8s_applier_test.go @@ -25,16 +25,12 @@ import ( "github.com/stretchr/testify/require" "opendev.org/airship/airshipctl/pkg/api/v1alpha1" - "opendev.org/airship/airshipctl/pkg/cluster/clustermap" "opendev.org/airship/airshipctl/pkg/container" "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/events" - "opendev.org/airship/airshipctl/pkg/fs" - "opendev.org/airship/airshipctl/pkg/k8s/kubeconfig" "opendev.org/airship/airshipctl/pkg/phase/executors" "opendev.org/airship/airshipctl/pkg/phase/ifc" testdoc "opendev.org/airship/airshipctl/testutil/document" - testfs "opendev.org/airship/airshipctl/testutil/fs" ) const ( @@ -223,78 +219,30 @@ func TestKubeApplierExecutorRun(t *testing.T) { containsErr string clusterName string - kubeconf kubeconfig.Interface execDoc document.Document bundleFactory document.BundleFactoryFunc - clusterMap clustermap.ClusterMap clientFunc container.ClientV1Alpha1FactoryFunc }{ - { - name: "unable to get kubeconfig context", - containsErr: "cluster 'foo' is not defined in cluster map", - bundleFactory: testApplierBundleFactory(t, "", nil), - kubeconf: testKubeconfig(`invalid kubeconfig`), - execDoc: executorDoc(t, ValidExecutorDoc), - clusterName: "foo", - clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{ - Map: map[string]*v1alpha1.Cluster{ - "ephemeral-cluster": {}, - }, - }), - }, - { - name: "unable to get kubeconfig file", - containsErr: "failed to get kubeconfig", - bundleFactory: testApplierBundleFactory(t, "", nil), - kubeconf: fakeKubeConfig{getFile: func() (string, kubeconfig.Cleanup, error) { - return "", nil, errors.New("failed to get kubeconfig") - }}, - execDoc: executorDoc(t, ValidExecutorDoc), - clusterName: "ephemeral-cluster", - clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{ - Map: map[string]*v1alpha1.Cluster{ - "ephemeral-cluster": {}, - }, - }), - }, { name: "unable to select bundle", containsErr: "error selecting bundle", bundleFactory: testApplierBundleFactorySelectBundleError(), - kubeconf: testKubeconfig("kubeconfig"), execDoc: executorDoc(t, ValidExecutorDoc), clusterName: "ephemeral-cluster", - clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{ - Map: map[string]*v1alpha1.Cluster{ - "ephemeral-cluster": {}, - }, - }), }, { name: "unable to write bundle", containsErr: "error writing bundle", bundleFactory: testApplierBundleFactoryWriteError(), - kubeconf: testKubeconfig("kubeconfig"), execDoc: executorDoc(t, ValidExecutorDoc), clusterName: "ephemeral-cluster", - clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{ - Map: map[string]*v1alpha1.Cluster{ - "ephemeral-cluster": {}, - }, - }), }, { name: "unsuccessful run", containsErr: "applier failure", bundleFactory: testApplierBundleFactoryNoError(), - kubeconf: testKubeconfig("kubeconfig"), execDoc: executorDoc(t, ValidExecutorDoc), clusterName: "ephemeral-cluster", - clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{ - Map: map[string]*v1alpha1.Cluster{ - "ephemeral-cluster": {}, - }, - }), clientFunc: func(_ string, _ io.Reader, _ io.Writer, _ *v1alpha1.GenericContainer, _ string) container.ClientV1Alpha1 { return MockClientFuncInterface{MockRun: func() error { @@ -306,14 +254,8 @@ func TestKubeApplierExecutorRun(t *testing.T) { name: "successful run", containsErr: "", bundleFactory: testApplierBundleFactoryNoError(), - kubeconf: testKubeconfig("kubeconfig"), execDoc: executorDoc(t, ValidExecutorDoc), clusterName: "ephemeral-cluster", - clusterMap: clustermap.NewClusterMap(&v1alpha1.ClusterMap{ - Map: map[string]*v1alpha1.Cluster{ - "ephemeral-cluster": {}, - }, - }), clientFunc: func(_ string, _ io.Reader, _ io.Writer, _ *v1alpha1.GenericContainer, _ string) container.ClientV1Alpha1 { return MockClientFuncInterface{MockRun: func() error { @@ -329,8 +271,6 @@ func TestKubeApplierExecutorRun(t *testing.T) { ifc.ExecutorConfig{ ExecutorDocument: tt.execDoc, BundleFactory: tt.bundleFactory, - KubeConfig: tt.kubeconf, - ClusterMap: tt.clusterMap, ClusterName: tt.clusterName, PhaseConfigBundle: executorBundle(t, applierKRMDoc), ContainerFunc: tt.clientFunc, @@ -369,23 +309,6 @@ func TestRender(t *testing.T) { assert.Equal(t, content, result) } -func testKubeconfig(stringData string) kubeconfig.Interface { - return kubeconfig.NewKubeConfig( - kubeconfig.FromByte([]byte(stringData)), - kubeconfig.InjectFileSystem( - testfs.MockFileSystem{ - MockTempFile: func(root, pattern string) (fs.File, error) { - return testfs.TestFile{ - MockName: func() string { return "kubeconfig-142398" }, - MockWrite: func([]byte) (int, error) { return 0, nil }, - MockClose: func() error { return nil }, - }, nil - }, - MockRemoveAll: func() error { return nil }, - }, - )) -} - func TestKubeApplierExecutor_Validate(t *testing.T) { tests := []struct { name string diff --git a/pkg/phase/helper.go b/pkg/phase/helper.go index b93c5989f..b7e6901f4 100644 --- a/pkg/phase/helper.go +++ b/pkg/phase/helper.go @@ -21,7 +21,6 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "opendev.org/airship/airshipctl/pkg/api/v1alpha1" - "opendev.org/airship/airshipctl/pkg/cluster/clustermap" "opendev.org/airship/airshipctl/pkg/config" "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/document/metadata" @@ -243,34 +242,6 @@ func (helper *Helper) ListPlans() ([]*v1alpha1.PhasePlan, error) { return plans, nil } -// ClusterMapAPIobj associated with the the manifest -func (helper *Helper) ClusterMapAPIobj() (*v1alpha1.ClusterMap, error) { - cMap := v1alpha1.DefaultClusterMap() - selector, err := document.NewSelector().ByObject(cMap, v1alpha1.Scheme) - if err != nil { - return nil, err - } - - doc, err := helper.phaseConfigBundle.SelectOne(selector) - if err != nil { - return nil, err - } - - if err = doc.ToAPIObject(cMap, v1alpha1.Scheme); err != nil { - return nil, err - } - return cMap, nil -} - -// ClusterMap associated with the the manifest -func (helper *Helper) ClusterMap() (clustermap.ClusterMap, error) { - cMap, err := helper.ClusterMapAPIobj() - if err != nil { - return nil, err - } - return clustermap.NewClusterMap(cMap), nil -} - // ExecutorDoc returns executor document associated with phase func (helper *Helper) ExecutorDoc(phaseID ifc.ID) (document.Document, error) { phaseObj, err := helper.Phase(phaseID) diff --git a/pkg/phase/helper_test.go b/pkg/phase/helper_test.go index 493a4f3d2..1d7e86dd5 100644 --- a/pkg/phase/helper_test.go +++ b/pkg/phase/helper_test.go @@ -331,128 +331,6 @@ func TestHelperListPlans(t *testing.T) { } } -func TestHelperClusterMapAPI(t *testing.T) { - testCases := []struct { - name string - errContains string - helperErr bool - expectedCMap *v1alpha1.ClusterMap - config func(t *testing.T) *config.Config - }{ - { - name: "Success cluster map", - expectedCMap: &airshipv1.ClusterMap{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "airshipit.org/v1alpha1", - Kind: "ClusterMap", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "clusterctl-v1", - }, - Map: map[string]*airshipv1.Cluster{ - "target": { - Parent: "ephemeral", - Sources: []airshipv1.KubeconfigSource{ - { - Type: airshipv1.KubeconfigSourceTypeBundle, - }, - }, - }, - "ephemeral": { - Sources: []airshipv1.KubeconfigSource{ - { - Type: airshipv1.KubeconfigSourceTypeBundle, - }, - }, - }, - }, - }, - config: testConfig, - }, - { - name: "Error bundle path doesn't exist", - config: func(t *testing.T) *config.Config { - conf := testConfig(t) - conf.Manifests["dummy_manifest"].MetadataPath = brokenMetaPath - return conf - }, - errContains: "no such file or directory", - helperErr: true, - }, - { - name: "Error no cluster map", - config: func(t *testing.T) *config.Config { - conf := testConfig(t) - conf.Manifests["dummy_manifest"].MetadataPath = noPlanMetaPath - return conf - }, - errContains: "found no documents", - }, - } - - for _, test := range testCases { - tt := test - t.Run(tt.name, func(t *testing.T) { - helper, err := phase.NewHelper(tt.config(t)) - if tt.helperErr { - require.Error(t, err) - return - } - require.NoError(t, err) - require.NotNil(t, helper) - - actualCMap, actualErr := helper.ClusterMapAPIobj() - if tt.errContains != "" { - require.Error(t, actualErr) - assert.Contains(t, actualErr.Error(), tt.errContains) - } else { - require.NoError(t, actualErr) - assert.Equal(t, tt.expectedCMap, actualCMap) - } - }) - } -} - -func TestHelperClusterMap(t *testing.T) { - testCases := []struct { - name string - errContains string - config func(t *testing.T) *config.Config - }{ - { - name: "Success phase list", - config: testConfig, - }, - { - name: "Error no cluster map", - config: func(t *testing.T) *config.Config { - conf := testConfig(t) - conf.Manifests["dummy_manifest"].MetadataPath = noPlanMetaPath - return conf - }, - errContains: "found no documents", - }, - } - - for _, test := range testCases { - tt := test - t.Run(tt.name, func(t *testing.T) { - helper, err := phase.NewHelper(tt.config(t)) - require.NoError(t, err) - require.NotNil(t, helper) - - actualCMap, actualErr := helper.ClusterMap() - if tt.errContains != "" { - require.Error(t, actualErr) - assert.Contains(t, actualErr.Error(), tt.errContains) - } else { - require.NoError(t, actualErr) - assert.NotNil(t, actualCMap) - } - }) - } -} - func TestHelperExecutorDoc(t *testing.T) { testCases := []struct { name string diff --git a/pkg/phase/ifc/executor.go b/pkg/phase/ifc/executor.go index 4de3d2719..905973c00 100644 --- a/pkg/phase/ifc/executor.go +++ b/pkg/phase/ifc/executor.go @@ -18,12 +18,10 @@ import ( "io" "time" - "opendev.org/airship/airshipctl/pkg/cluster/clustermap" "opendev.org/airship/airshipctl/pkg/container" "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/events" inventoryifc "opendev.org/airship/airshipctl/pkg/inventory/ifc" - "opendev.org/airship/airshipctl/pkg/k8s/kubeconfig" ) // Executor interface should be implemented by each runner @@ -39,8 +37,10 @@ type ExecutorStatus struct{} // RunOptions holds options for run method type RunOptions struct { - DryRun bool - Timeout *time.Duration + Kubeconfig string + Context string + DryRun bool + Timeout *time.Duration } // RenderOptions holds options for render method @@ -58,9 +58,7 @@ type ExecutorConfig struct { SinkBasePath string TargetPath string - ClusterMap clustermap.ClusterMap ExecutorDocument document.Document - KubeConfig kubeconfig.Interface BundleFactory document.BundleFactoryFunc PhaseConfigBundle document.Bundle Inventory inventoryifc.Inventory diff --git a/pkg/phase/ifc/helper.go b/pkg/phase/ifc/helper.go index 7c81dec40..7334d4566 100644 --- a/pkg/phase/ifc/helper.go +++ b/pkg/phase/ifc/helper.go @@ -16,7 +16,6 @@ package ifc import ( "opendev.org/airship/airshipctl/pkg/api/v1alpha1" - "opendev.org/airship/airshipctl/pkg/cluster/clustermap" "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/inventory/ifc" ) @@ -31,8 +30,6 @@ type Helper interface { Plan(planID ID) (*v1alpha1.PhasePlan, error) ListPhases(o ListPhaseOptions) ([]*v1alpha1.Phase, error) ListPlans() ([]*v1alpha1.PhasePlan, error) - ClusterMapAPIobj() (*v1alpha1.ClusterMap, error) - ClusterMap() (clustermap.ClusterMap, error) ExecutorDoc(phaseID ID) (document.Document, error) PhaseBundleRoot() string Inventory() ifc.Inventory diff --git a/pkg/phase/ifc/phase.go b/pkg/phase/ifc/phase.go index 827215fdc..a4b38e11c 100644 --- a/pkg/phase/ifc/phase.go +++ b/pkg/phase/ifc/phase.go @@ -18,7 +18,6 @@ import ( "io" "opendev.org/airship/airshipctl/pkg/api/v1alpha1" - "opendev.org/airship/airshipctl/pkg/cluster/clustermap" ) // Phase provides a way to interact with a phase @@ -67,5 +66,4 @@ type Client interface { PhaseByID(ID) (Phase, error) PlanByID(ID) (Plan, error) PhaseByAPIObj(*v1alpha1.Phase) (Phase, error) - ClusterMap() (clustermap.ClusterMap, error) } diff --git a/pkg/phase/render_test.go b/pkg/phase/render_test.go index ce32693a4..28df66930 100644 --- a/pkg/phase/render_test.go +++ b/pkg/phase/render_test.go @@ -155,5 +155,4 @@ func TestRenderConfigBundle(t *testing.T) { assert.NoError(t, err) // check that it contains phases and cluster map assert.Contains(t, buf.String(), "kind: Phase") - assert.Contains(t, buf.String(), "kind: ClusterMap") } diff --git a/pkg/phase/testdata/invalid_validation_site/phases/cluster_map.yaml b/pkg/phase/testdata/invalid_validation_site/phases/cluster_map.yaml deleted file mode 100755 index 47e720022..000000000 --- a/pkg/phase/testdata/invalid_validation_site/phases/cluster_map.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: airshipit.org/v1alpha1 -kind: ClusterMap -metadata: - name: clusterctl-v1 -map: - target: - parent: ephemeral - kubeconfigSources: - - type: bundle - ephemeral: - kubeconfigSources: - - type: bundle diff --git a/pkg/phase/testdata/invalid_validation_site/phases/kustomization.yaml b/pkg/phase/testdata/invalid_validation_site/phases/kustomization.yaml index 5ca14108a..dad2b5a3d 100755 --- a/pkg/phase/testdata/invalid_validation_site/phases/kustomization.yaml +++ b/pkg/phase/testdata/invalid_validation_site/phases/kustomization.yaml @@ -1,4 +1,3 @@ resources: - kube_apply.yaml - kubernetes_apply.yaml - - cluster_map.yaml diff --git a/pkg/phase/testdata/phases/cluster-map.yaml b/pkg/phase/testdata/phases/cluster-map.yaml deleted file mode 100755 index 0ec889670..000000000 --- a/pkg/phase/testdata/phases/cluster-map.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: airshipit.org/v1alpha1 -kind: ClusterMap -metadata: - labels: - airshipit.org/deploy-k8s: "false" - name: main-map -map: - target-cluster: - parent: ephemeral - kubeconfigSources: - - type: bundle - ephemeral-cluster: - kubeconfigSources: - - type: bundle diff --git a/pkg/phase/testdata/phases/kustomization.yaml b/pkg/phase/testdata/phases/kustomization.yaml index fa8e1c108..eee1ef9be 100755 --- a/pkg/phase/testdata/phases/kustomization.yaml +++ b/pkg/phase/testdata/phases/kustomization.yaml @@ -1,5 +1,4 @@ resources: - phases.yaml - executors.yaml - - cluster-map.yaml - - phaseplan.yaml \ No newline at end of file + - phaseplan.yaml diff --git a/pkg/phase/testdata/reponame/valid_site/phases/cluster_map.yaml b/pkg/phase/testdata/reponame/valid_site/phases/cluster_map.yaml deleted file mode 100755 index 47e720022..000000000 --- a/pkg/phase/testdata/reponame/valid_site/phases/cluster_map.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: airshipit.org/v1alpha1 -kind: ClusterMap -metadata: - name: clusterctl-v1 -map: - target: - parent: ephemeral - kubeconfigSources: - - type: bundle - ephemeral: - kubeconfigSources: - - type: bundle diff --git a/pkg/phase/testdata/reponame/valid_site/phases/kustomization.yaml b/pkg/phase/testdata/reponame/valid_site/phases/kustomization.yaml index d9edc4063..f0160a7cf 100755 --- a/pkg/phase/testdata/reponame/valid_site/phases/kustomization.yaml +++ b/pkg/phase/testdata/reponame/valid_site/phases/kustomization.yaml @@ -5,7 +5,6 @@ resources: - capi_init.yaml - clusterctl.yaml - kubernetes_apply.yaml - - cluster_map.yaml - phase_no_docentrypoint.yaml - no_executor_phase.yaml - kubeapply_phase.yaml diff --git a/pkg/phase/testdata/valid_site/phases/cluster_map.yaml b/pkg/phase/testdata/valid_site/phases/cluster_map.yaml deleted file mode 100644 index 47e720022..000000000 --- a/pkg/phase/testdata/valid_site/phases/cluster_map.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: airshipit.org/v1alpha1 -kind: ClusterMap -metadata: - name: clusterctl-v1 -map: - target: - parent: ephemeral - kubeconfigSources: - - type: bundle - ephemeral: - kubeconfigSources: - - type: bundle diff --git a/pkg/phase/testdata/valid_site/phases/kustomization.yaml b/pkg/phase/testdata/valid_site/phases/kustomization.yaml index cde52b834..7745b365c 100644 --- a/pkg/phase/testdata/valid_site/phases/kustomization.yaml +++ b/pkg/phase/testdata/valid_site/phases/kustomization.yaml @@ -6,7 +6,6 @@ resources: - capi_init.yaml - clusterctl.yaml - kubernetes_apply.yaml - - cluster_map.yaml - phase_no_docentrypoint.yaml - no_executor_phase.yaml - kubeapply_phase.yaml diff --git a/pkg/phase/testdata/valid_validation_site/phases/cluster_map.yaml b/pkg/phase/testdata/valid_validation_site/phases/cluster_map.yaml deleted file mode 100755 index 47e720022..000000000 --- a/pkg/phase/testdata/valid_validation_site/phases/cluster_map.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: airshipit.org/v1alpha1 -kind: ClusterMap -metadata: - name: clusterctl-v1 -map: - target: - parent: ephemeral - kubeconfigSources: - - type: bundle - ephemeral: - kubeconfigSources: - - type: bundle diff --git a/pkg/phase/testdata/valid_validation_site/phases/kustomization.yaml b/pkg/phase/testdata/valid_validation_site/phases/kustomization.yaml index 1de6a49eb..d49013ea5 100755 --- a/pkg/phase/testdata/valid_validation_site/phases/kustomization.yaml +++ b/pkg/phase/testdata/valid_validation_site/phases/kustomization.yaml @@ -1,5 +1,4 @@ resources: - kube_apply.yaml - kubernetes_apply.yaml - - cluster_map.yaml - validation_exec.yaml diff --git a/pkg/util/yaml/writer_test.go b/pkg/util/yaml/writer_test.go index 6953e4f23..17e7c658c 100644 --- a/pkg/util/yaml/writer_test.go +++ b/pkg/util/yaml/writer_test.go @@ -68,7 +68,6 @@ func TestWriteOut(t *testing.T) { } // Verify result contents - // TODO (kkalynovskyi) make more reliable tests assert.Contains(t, b.String(), ob.Name) assert.Contains(t, b.String(), "airshiplabel: airshipit.org") assert.Regexp(t, regexp.MustCompile(`^---.*`), b.String()) diff --git a/testutil/phase/helper.go b/testutil/phase/helper.go index 148fee2cb..a707cd96a 100644 --- a/testutil/phase/helper.go +++ b/testutil/phase/helper.go @@ -18,7 +18,6 @@ import ( "github.com/stretchr/testify/mock" "opendev.org/airship/airshipctl/pkg/api/v1alpha1" - "opendev.org/airship/airshipctl/pkg/cluster/clustermap" "opendev.org/airship/airshipctl/pkg/document" inventoryifc "opendev.org/airship/airshipctl/pkg/inventory/ifc" "opendev.org/airship/airshipctl/pkg/phase/ifc" @@ -95,26 +94,6 @@ func (mh *MockHelper) ListPlans() ([]*v1alpha1.PhasePlan, error) { return val, args.Error(1) } -// ClusterMapAPIobj mock -func (mh *MockHelper) ClusterMapAPIobj() (*v1alpha1.ClusterMap, error) { - args := mh.Called() - val, ok := args.Get(0).(*v1alpha1.ClusterMap) - if !ok { - return nil, args.Error(1) - } - return val, args.Error(1) -} - -// ClusterMap mock -func (mh *MockHelper) ClusterMap() (clustermap.ClusterMap, error) { - args := mh.Called() - val, ok := args.Get(0).(clustermap.ClusterMap) - if !ok { - return nil, args.Error(1) - } - return val, args.Error(1) -} - // ExecutorDoc mock func (mh *MockHelper) ExecutorDoc(phaseID ifc.ID) (document.Document, error) { args := mh.Called() diff --git a/tools/deployment/23_generate_secrets.sh b/tools/deployment/23_generate_secrets.sh index f73ed40e3..7ef773853 100755 --- a/tools/deployment/23_generate_secrets.sh +++ b/tools/deployment/23_generate_secrets.sh @@ -30,11 +30,10 @@ export WORKDIR="${AIRSHIP_CONFIG_MANIFEST_DIRECTORY}/${REPO_NAME}" if [[ -z "$EXTERNAL_KUBECONFIG" ]]; then # we want to take config from bundle - remove kubeconfig file so # airshipctl could regenerated it from kustomize - [ -f "~/.airship/kubeconfig" ] && rm ~/.airship/kubeconfig + [ -f "~/.airship/kubeconfig" ] && rm ~/.airship/kubeconfig && touch ~/.airship/kubeconfig # we need to use tmp file, because airshipctl uses it and fails # if we write directly - airshipctl cluster get-kubeconfig > ~/.airship/tmp-kubeconfig - mv ~/.airship/tmp-kubeconfig ~/.airship/kubeconfig + airshipctl phase run secret-get fi # Validate that we generated everything correctly diff --git a/tools/deployment/25_deploy_gating.sh b/tools/deployment/25_deploy_gating.sh index ccb454acc..ada5d3c22 100755 --- a/tools/deployment/25_deploy_gating.sh +++ b/tools/deployment/25_deploy_gating.sh @@ -15,4 +15,4 @@ set -xe echo "Deploy gating" -airshipctl plan run deploy-gating --debug +KUBECONFIG=~/.airship/kubeconfig airshipctl plan run deploy-gating --debug diff --git a/tools/deployment/provider_common/01_install_kind.sh b/tools/deployment/provider_common/01_install_kind.sh index 0d9532220..1a7ed4099 100755 --- a/tools/deployment/provider_common/01_install_kind.sh +++ b/tools/deployment/provider_common/01_install_kind.sh @@ -11,7 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -: ${KIND_VERSION:="v0.9.0"} +: ${KIND_VERSION:="v0.11.1"} export KIND_URL="https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-$(uname)-amd64" TMP=$(KIND_URL=${KIND_URL} tools/deployment/kind/get_kind.sh) export KIND=${TMP}/kind