Add flags to airshipctl get-kubeconfig cmd
* This commits add --file and --merge flags to airshipctl get-kubeconfig cmd Change-Id: I919d4f068d3ef9bcda6b3a7c9aeb0826a4e5c0d4 Signed-off-by: bijayasharma <vetbijaya@gmail.com> Relates-To: #495 Closes: #495
This commit is contained in:
parent
8d882fcc85
commit
622d45f3bd
@ -37,6 +37,14 @@ Retrieve target-cluster kubeconfig
|
||||
|
||||
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
|
||||
`
|
||||
)
|
||||
|
||||
@ -53,6 +61,21 @@ func NewGetKubeconfigCommand(cfgFactory config.Factory) *cobra.Command {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,16 @@ Retrieve target-cluster kubeconfig
|
||||
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:
|
||||
-h, --help help for get-kubeconfig
|
||||
-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
|
||||
|
@ -27,12 +27,22 @@ Retrieve target-cluster kubeconfig
|
||||
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
|
||||
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for get-kubeconfig
|
||||
-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
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
@ -57,9 +57,12 @@ func StatusRunner(o StatusOptions, w io.Writer) error {
|
||||
// GetKubeconfigCommand holds options for get kubeconfig command
|
||||
type GetKubeconfigCommand struct {
|
||||
ClusterName string
|
||||
File string
|
||||
Merge bool
|
||||
}
|
||||
|
||||
// RunE creates new kubeconfig interface object from secret and prints its content to writer
|
||||
// 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 {
|
||||
@ -89,5 +92,8 @@ func (cmd *GetKubeconfigCommand) RunE(cfgFactory config.Factory, writer io.Write
|
||||
SiteWide(siteWide).
|
||||
Build()
|
||||
|
||||
if cmd.File != "" {
|
||||
return kubeconf.WriteFile(cmd.File, kubeconfig.WriteOptions{Merge: cmd.Merge})
|
||||
}
|
||||
return kubeconf.Write(writer)
|
||||
}
|
||||
|
@ -215,7 +215,7 @@ func TestBuilderClusterctl(t *testing.T) {
|
||||
MockTempFile: func(s1, s2 string) (fs.File, error) {
|
||||
return testfs.TestFile{
|
||||
MockName: func() string { return kubeconfigPath },
|
||||
MockWrite: func() (int, error) { return 0, nil },
|
||||
MockWrite: func([]byte) (int, error) { return 0, nil },
|
||||
MockClose: func() error { return nil },
|
||||
}, nil
|
||||
},
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
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"
|
||||
@ -48,7 +49,8 @@ type Interface interface {
|
||||
// Write will write kubeconfig to the provided writer
|
||||
Write(w io.Writer) error
|
||||
// WriteFile will write kubeconfig data to specified path
|
||||
WriteFile(path string) error
|
||||
// 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)
|
||||
@ -65,6 +67,11 @@ type kubeConfig struct {
|
||||
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
|
||||
@ -206,8 +213,14 @@ func InjectFilePath(path string, fSys fs.FileSystem) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *kubeConfig) WriteFile(path string) (err error) {
|
||||
data, err := k.bytes()
|
||||
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
|
||||
}
|
||||
@ -253,6 +266,24 @@ func (k *kubeConfig) bytes() ([]byte, error) {
|
||||
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
|
||||
|
@ -62,6 +62,52 @@ users:
|
||||
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=
|
||||
`
|
||||
)
|
||||
|
||||
@ -297,7 +343,7 @@ func TestNewKubeConfig(t *testing.T) {
|
||||
MockTempFile: func(root, pattern string) (fs.File, error) {
|
||||
return testfs.TestFile{
|
||||
MockName: func() string { return "kubeconfig-142398" },
|
||||
MockWrite: func() (int, error) { return 0, nil },
|
||||
MockWrite: func([]byte) (int, error) { return 0, nil },
|
||||
MockClose: func() error { return nil },
|
||||
}, nil
|
||||
},
|
||||
@ -322,7 +368,7 @@ func TestNewKubeConfig(t *testing.T) {
|
||||
}
|
||||
return testfs.TestFile{
|
||||
MockName: func() string { return "kubeconfig-142398" },
|
||||
MockWrite: func() (int, error) { return 0, nil },
|
||||
MockWrite: func([]byte) (int, error) { return 0, nil },
|
||||
MockClose: func() error { return nil },
|
||||
}, nil
|
||||
},
|
||||
@ -440,6 +486,7 @@ func TestKubeConfigWriteFile(t *testing.T) {
|
||||
expectedContent string
|
||||
path string
|
||||
expectedErrorContains string
|
||||
merge bool
|
||||
|
||||
fs fs.FileSystem
|
||||
src kubeconfig.KubeSourceFunc
|
||||
@ -448,26 +495,52 @@ func TestKubeConfigWriteFile(t *testing.T) {
|
||||
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.InjectFileSystem(tt.fs))
|
||||
err := kubeconf.WriteFile(tt.path)
|
||||
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.Equal(t, tt.expectedContent, readFile(t, tt.path, tt.fs))
|
||||
assert.YAMLEq(t, tt.expectedContent, readFile(t, tt.path, tt.fs))
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -486,11 +559,19 @@ func read(t *testing.T, r io.Reader) string {
|
||||
}
|
||||
|
||||
func fsWithFile(t *testing.T, path string) fs.FileSystem {
|
||||
memFs := kustfs.MakeFsInMemory()
|
||||
fSys := testfs.MockFileSystem{
|
||||
FileSystem: kustfs.MakeFsInMemory(),
|
||||
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)
|
||||
|
18
pkg/k8s/kubeconfig/testdata/kubeconfig-test
vendored
Normal file
18
pkg/k8s/kubeconfig/testdata/kubeconfig-test
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
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=
|
@ -83,7 +83,7 @@ func TestApply(t *testing.T) {
|
||||
MockTempFile: func(string, string) (fs.File, error) {
|
||||
return testfs.TestFile{
|
||||
MockName: func() string { return filenameRC },
|
||||
MockWrite: func() (int, error) { return 0, nil },
|
||||
MockWrite: func([]byte) (int, error) { return 0, nil },
|
||||
MockClose: func() error { return nil },
|
||||
}, nil
|
||||
},
|
||||
@ -100,7 +100,7 @@ func TestApply(t *testing.T) {
|
||||
MockRemoveAll: func() error { return nil },
|
||||
MockTempFile: func(string, string) (fs.File, error) {
|
||||
return testfs.TestFile{
|
||||
MockWrite: func() (int, error) { return 0, ErrTempFileError },
|
||||
MockWrite: func([]byte) (int, error) { return 0, ErrTempFileError },
|
||||
MockName: func() string { return filenameRC },
|
||||
MockClose: func() error { return nil },
|
||||
}, nil
|
||||
|
@ -154,7 +154,7 @@ func TestClusterctlExecutorRun(t *testing.T) {
|
||||
MockTempFile: func(string, string) (fs.File, error) {
|
||||
return testfs.TestFile{
|
||||
MockName: func() string { return "filename" },
|
||||
MockWrite: func() (int, error) { return 0, nil },
|
||||
MockWrite: func([]byte) (int, error) { return 0, nil },
|
||||
MockClose: func() error { return nil },
|
||||
}, nil
|
||||
},
|
||||
|
@ -344,9 +344,9 @@ 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) error { return nil }
|
||||
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()
|
||||
}
|
||||
|
@ -290,7 +290,7 @@ func testKubeconfig(stringData string) kubeconfig.Interface {
|
||||
MockTempFile: func(root, pattern string) (fs.File, error) {
|
||||
return testfs.TestFile{
|
||||
MockName: func() string { return "kubeconfig-142398" },
|
||||
MockWrite: func() (int, error) { return 0, nil },
|
||||
MockWrite: func([]byte) (int, error) { return 0, nil },
|
||||
MockClose: func() error { return nil },
|
||||
}, nil
|
||||
},
|
||||
|
@ -62,7 +62,7 @@ func (fsys MockFileSystem) Dir(path string) string {
|
||||
type TestFile struct {
|
||||
fs.File
|
||||
MockName func() string
|
||||
MockWrite func() (int, error)
|
||||
MockWrite func([]byte) (int, error)
|
||||
MockClose func() error
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ type TestFile struct {
|
||||
func (f TestFile) Name() string { return f.MockName() }
|
||||
|
||||
// Write File interface implementation
|
||||
func (f TestFile) Write([]byte) (int, error) { return f.MockWrite() }
|
||||
func (f TestFile) Write(b []byte) (int, error) { return f.MockWrite(b) }
|
||||
|
||||
// Close File interface implementation
|
||||
func (f TestFile) Close() error { return f.MockClose() }
|
||||
|
Loading…
Reference in New Issue
Block a user