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 <raliev@mirantis.com>
Closes: #666
This commit is contained in:
Ruslan Aliev 2021-11-04 05:06:48 +00:00
parent 0a09456562
commit 6dc39f0735
102 changed files with 1254 additions and 3992 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
})
}
}

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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"}
}

View File

@ -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)
}
}

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -27,7 +27,6 @@ SEE ALSO
~~~~~~~~
* :ref:`airshipctl baremetal <airshipctl_baremetal>` - Airshipctl command to manage bare metal host(s)
* :ref:`airshipctl cluster <airshipctl_cluster>` - Airshipctl command to manage kubernetes clusters
* :ref:`airshipctl completion <airshipctl_completion>` - Airshipctl command to generate completion script for the specified shell (bash or zsh)
* :ref:`airshipctl config <airshipctl_config>` - Airshipctl command to manage airshipctl config file
* :ref:`airshipctl document <airshipctl_document>` - Airshipctl command to manage site manifest documents

View File

@ -33,6 +33,5 @@ SEE ALSO
* :ref:`airshipctl <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_cluster_get-kubeconfig>` - Airshipctl command to retrieve kubeconfig for a desired cluster(s)
* :ref:`airshipctl cluster list <airshipctl_cluster_list>` - Airshipctl command to get and list defined clusters
* :ref:`airshipctl cluster status <airshipctl_cluster_status>` - Retrieve statuses of deployed cluster components

View File

@ -7,5 +7,4 @@ cluster
airshipctl_cluster
airshipctl_cluster_get-kubeconfig
airshipctl_cluster_list
airshipctl_cluster_status

View File

@ -7,7 +7,6 @@ Commands
airshipctl
baremetal/index
cluster/index
completion/index
config/index
document/index

View File

@ -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

View File

@ -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

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

@ -323,52 +323,6 @@ Kubeconfig api object example
client-certificate-data: <client-cert-data>
client-key-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
<https://godoc.org/opendev.org/airship/airshipctl/pkg/api/v1alpha1#ClusterMap>`__
- `Cluster map interface source code
<https://pkg.go.dev/opendev.org/airship/airshipctl/pkg/phase/executors#ClusterctlExecutor>`__
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
-------------

6
go.mod
View File

@ -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

11
go.sum
View File

@ -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=

View File

@ -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

View File

@ -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"]

View File

@ -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 <phase_name>
if a phase has GenericContainer executor defined along with KubeconfigOptions.

View File

@ -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.

View File

@ -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
)

View File

@ -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=

View File

@ -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)
}
}

View File

@ -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

View File

@ -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:

View File

@ -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: []

View File

@ -12,5 +12,4 @@ resources:
- wait_bmh
- wait_label_node
- check_ingress_ctrl
- merge_kubeconfig
- wait_machines_ready

View File

@ -1,6 +0,0 @@
configMapGenerator:
- name: merge-kubeconfig
options:
disableNameSuffixHash: true
files:
- script=kubectl_merge_kubeconfig.sh

View File

@ -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

View File

@ -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

View File

@ -1,7 +1,6 @@
resources:
- phases.yaml
- executors.yaml
- cluster-map.yaml
- ../function/clusterctl
# Scripts for generic containers
- ../function/phase-helpers

View File

@ -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

View File

@ -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

View File

@ -10,7 +10,6 @@ patchesJson6902:
path: infrastructure-providers.json
patchesStrategicMerge:
- plan_patch.yaml
- cluster_map_patch.yaml
- executor_patch.yaml
transformers:
- ../../../function/clusterctl/replacements

View File

@ -1,5 +1,4 @@
resources:
- ../kubeconfig
- ../../../type/gating/phases
- ../target/catalogues
- catalogue.yaml

View File

@ -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),
}
}

View File

@ -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

View File

@ -47,7 +47,6 @@ func init() {
&KubeConfig{},
&KubernetesApply{},
&IsoConfiguration{},
&ClusterMap{},
&ReplacementTransformer{},
&Templater{},
&BootConfiguration{},

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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))
})
}
}

View File

@ -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)
}

View File

@ -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{}

View File

@ -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:

View File

@ -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),
}
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -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
}

View File

@ -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=

View File

@ -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=

View File

@ -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=

View File

@ -1,2 +0,0 @@
resources:
- kubeconfig.yaml

View File

@ -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=

View File

@ -1,2 +0,0 @@
resources:
- kubeconfig.yaml

View File

@ -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))
}

View File

@ -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(),

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -30,6 +30,9 @@ const (
GenericContainer = "generic-container"
Ephemeral = "ephemeral"
BMHManager = "BaremetalManager"
KubeConfigFile = "~/.kube/config"
KubeConfigMount = "/kubeconfig"
)
// RegisterExecutor adds executor to phase executor registry

View File

@ -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)
}

View File

@ -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

View File

@ -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()
}

View File

@ -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())

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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

View File

@ -1,4 +1,3 @@
resources:
- kube_apply.yaml
- kubernetes_apply.yaml
- cluster_map.yaml

View File

@ -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

View File

@ -1,5 +1,4 @@
resources:
- phases.yaml
- executors.yaml
- cluster-map.yaml
- phaseplan.yaml
- phaseplan.yaml

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,5 +1,4 @@
resources:
- kube_apply.yaml
- kubernetes_apply.yaml
- cluster_map.yaml
- validation_exec.yaml

View File

@ -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())

View File

@ -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()

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More