Introduces set_context and get_context operations

Each of these include an option for --current-context that set or retrieves
the curret context

This patchset mainly creates the cmd/config and pkg/config require additions

Also includes a fync getCurrentContext(<CLUSTERTYPE>)  in the config pkg
that other modules should rely on.

Introduces new  ErrMissingConfig and  ErrConfigFailed types been used by
set-context, will decimate through get/ and set/get cluster after this is
reviewed.

Change-Id: I501483a9db99f33f860eaf329a65bb0209b2aaff
This commit is contained in:
Rodolfo Pacheco 2019-12-04 17:19:06 -05:00
parent 8dd721830b
commit a480527808
25 changed files with 968 additions and 35 deletions

View File

@ -17,6 +17,8 @@ like "airshipctl config set-current-context my-context" `),
}
configRootCmd.AddCommand(NewCmdConfigSetCluster(rootSettings))
configRootCmd.AddCommand(NewCmdConfigGetCluster(rootSettings))
configRootCmd.AddCommand(NewCmdConfigSetContext(rootSettings))
configRootCmd.AddCommand(NewCmdConfigGetContext(rootSettings))
return configRootCmd
}

View File

@ -27,8 +27,7 @@ import (
)
var (
getClusterLong = (`
Gets a specific cluster or all defined clusters if no name is provided`)
getClusterLong = (`Display a specific cluster or all defined clusters if no name is provided`)
getClusterExample = fmt.Sprintf(`
# List all the clusters airshipctl knows about
@ -44,8 +43,7 @@ func NewCmdConfigGetCluster(rootSettings *environment.AirshipCTLSettings) *cobra
theCluster := &config.ClusterOptions{}
getclustercmd := &cobra.Command{
Use: "get-cluster NAME",
Short: "Display a specific cluster",
Long: getClusterLong,
Short: getClusterLong,
Example: getClusterExample,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 1 {

107
cmd/config/get_context.go Normal file
View File

@ -0,0 +1,107 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"fmt"
"io"
"github.com/spf13/cobra"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/environment"
)
var (
getContextLong = (`Display a specific context, the current-context or all defined contexts if no name is provided`)
getContextExample = fmt.Sprintf(`# List all the contexts airshipctl knows about
airshipctl config get-context
# Display the current context
airshipctl config get-context --%v
# Display a specific Context
airshipctl config get-context e2e`,
config.FlagCurrentContext)
)
// A Context refers to a particular cluster, however it does not specify which of the cluster types
// it relates to. Getting explicit information about a particular context will depend
// on the ClusterType flag.
// NewCmdConfigGetContext returns a Command instance for 'config -Context' sub command
func NewCmdConfigGetContext(rootSettings *environment.AirshipCTLSettings) *cobra.Command {
theContext := &config.ContextOptions{}
getcontextcmd := &cobra.Command{
Use: "get-context NAME",
Short: getContextLong,
Example: getContextExample,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 1 {
theContext.Name = args[0]
}
return runGetContext(theContext, cmd.OutOrStdout(), rootSettings.Config())
},
}
gctxInitFlags(theContext, getcontextcmd)
return getcontextcmd
}
func gctxInitFlags(o *config.ContextOptions, getcontextcmd *cobra.Command) {
getcontextcmd.Flags().BoolVar(&o.CurrentContext, config.FlagCurrentContext, false,
config.FlagCurrentContext+" to retrieve the current context entry in airshipctl config")
}
// runGetContext performs the execution of 'config get-Context' sub command
func runGetContext(o *config.ContextOptions, out io.Writer, airconfig *config.Config) error {
if o.Name == "" && !o.CurrentContext {
return getContexts(out, airconfig)
}
return getContext(o, out, airconfig)
}
func getContext(o *config.ContextOptions, out io.Writer, airconfig *config.Config) error {
cName := o.Name
if o.CurrentContext {
cName = airconfig.CurrentContext
}
context, err := airconfig.GetContext(cName)
if err != nil {
return err
}
fmt.Fprintf(out, "%s", context.PrettyString())
return nil
}
func getContexts(out io.Writer, airconfig *config.Config) error {
contexts, err := airconfig.GetContexts()
if err != nil {
return err
}
if contexts == nil {
fmt.Fprint(out, "No Contexts found in the configuration.\n")
}
for _, context := range contexts {
fmt.Fprintf(out, "%s", context.PrettyString())
}
return nil
}

View File

@ -0,0 +1,116 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config_test
import (
"fmt"
"testing"
kubeconfig "k8s.io/client-go/tools/clientcmd/api"
cmd "opendev.org/airship/airshipctl/cmd/config"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/environment"
"opendev.org/airship/airshipctl/testutil"
)
const (
currentContextFlag = "--" + config.FlagCurrentContext
fooContext = "ContextFoo"
barContext = "ContextBar"
bazContext = "ContextBaz"
missingContext = "contextMissing"
)
func TestGetContextCmd(t *testing.T) {
conf := &config.Config{
Contexts: map[string]*config.Context{
fooContext: getNamedTestContext(fooContext),
barContext: getNamedTestContext(barContext),
bazContext: getNamedTestContext(bazContext),
},
CurrentContext: bazContext,
}
settings := &environment.AirshipCTLSettings{}
settings.SetConfig(conf)
cmdTests := []*testutil.CmdTest{
{
Name: "get-context",
CmdLine: fmt.Sprintf("%s", fooContext),
Cmd: cmd.NewCmdConfigGetContext(settings),
},
{
Name: "get-all-contexts",
CmdLine: fmt.Sprintf("%s %s", fooContext, barContext),
Cmd: cmd.NewCmdConfigGetContext(settings),
},
// This is not implemented yet
{
Name: "get-multiple-contexts",
CmdLine: fmt.Sprintf("%s %s", fooContext, barContext),
Cmd: cmd.NewCmdConfigGetContext(settings),
},
{
Name: "missing",
CmdLine: fmt.Sprintf("%s", missingContext),
Cmd: cmd.NewCmdConfigGetContext(settings),
Error: fmt.Errorf("Context %s information was not "+
"found in the configuration.", missingContext),
},
{
Name: "get-current-context",
CmdLine: fmt.Sprintf("%s", currentContextFlag),
Cmd: cmd.NewCmdConfigGetContext(settings),
},
}
for _, tt := range cmdTests {
testutil.RunTest(t, tt)
}
}
func TestNoContextsGetContextCmd(t *testing.T) {
settings := &environment.AirshipCTLSettings{}
settings.SetConfig(&config.Config{})
cmdTest := &testutil.CmdTest{
Name: "no-contexts",
CmdLine: "",
Cmd: cmd.NewCmdConfigGetContext(settings),
}
testutil.RunTest(t, cmdTest)
}
func getNamedTestContext(contextName string) *config.Context {
kContext := &kubeconfig.Context{
Namespace: "dummy_namespace",
AuthInfo: "dummy_user",
Cluster: fmt.Sprintf("dummycluster_%s", config.Ephemeral),
}
newContext := &config.Context{
NameInKubeconf: fmt.Sprintf("%s_%s", contextName, config.Ephemeral),
Manifest: fmt.Sprintf("Manifest_%s", contextName),
}
newContext.SetKubeContext(kContext)
return newContext
}

141
cmd/config/set_context.go Normal file
View File

@ -0,0 +1,141 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"errors"
"fmt"
"github.com/spf13/cobra"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/environment"
conferrors "opendev.org/airship/airshipctl/pkg/errors"
)
var (
setContextLong = (`
Sets a context entry in arshipctl config.
Specifying a name that already exists will merge new fields on top of existing values for those fields.`)
setContextExample = fmt.Sprintf(`
# Create a completely new e2e context entry
airshipctl config set-context e2e --%v=kube-system --%v=manifest --%v=auth-info --%v=%v
# Update the current-context to e2e
airshipctl config set-context e2e --%v=true`,
config.FlagNamespace,
config.FlagManifest,
config.FlagAuthInfoName,
config.FlagClusterType,
config.Target,
config.FlagCurrentContext)
)
// NewCmdConfigSetContext creates a command object for the "set-context" action, which
// defines a new Context airship config.
func NewCmdConfigSetContext(rootSettings *environment.AirshipCTLSettings) *cobra.Command {
theContext := &config.ContextOptions{}
setcontextcmd := &cobra.Command{
Use: "set-context NAME",
Short: "Sets a context entry or updates current-context in the airshipctl config",
Long: setContextLong,
Example: setContextExample,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
theContext.Name = cmd.Flags().Args()[0]
modified, err := runSetContext(theContext, rootSettings.Config())
if err != nil {
return err
}
if modified {
fmt.Fprintf(cmd.OutOrStdout(), "Context %q modified.\n", theContext.Name)
} else {
fmt.Fprintf(cmd.OutOrStdout(), "Context %q created.\n", theContext.Name)
}
return nil
},
}
sctxInitFlags(theContext, setcontextcmd)
return setcontextcmd
}
func sctxInitFlags(o *config.ContextOptions, setcontextcmd *cobra.Command) {
setcontextcmd.Flags().BoolVar(&o.CurrentContext, config.FlagCurrentContext, false,
config.FlagCurrentContext+" for the context entry in airshipctl config")
setcontextcmd.Flags().StringVar(&o.Cluster, config.FlagClusterName, o.Cluster,
config.FlagClusterName+" for the context entry in airshipctl config")
setcontextcmd.Flags().StringVar(&o.AuthInfo, config.FlagAuthInfoName, o.AuthInfo,
config.FlagAuthInfoName+" for the context entry in airshipctl config")
setcontextcmd.Flags().StringVar(&o.Manifest, config.FlagManifest, o.Manifest,
config.FlagManifest+" for the context entry in airshipctl config")
setcontextcmd.Flags().StringVar(&o.Namespace, config.FlagNamespace, o.Namespace,
config.FlagNamespace+" for the context entry in airshipctl config")
setcontextcmd.Flags().StringVar(&o.ClusterType, config.FlagClusterType, "",
config.FlagClusterType+" for the context entry in airshipctl config")
}
func runSetContext(o *config.ContextOptions, airconfig *config.Config) (bool, error) {
contextWasModified := false
err := o.Validate()
if err != nil {
return contextWasModified, err
}
contextIWant := o.Name
context, err := airconfig.GetContext(contextIWant)
if err != nil {
var cerr conferrors.ErrMissingConfig
if !errors.As(err, &cerr) {
// An error occurred, but it wasn't a "missing" config error.
return contextWasModified, err
}
if o.CurrentContext {
return contextWasModified, conferrors.ErrMissingConfig{}
}
// context didn't exist, create it
// ignoring the returned added context
airconfig.AddContext(o)
} else {
// Found the desired Current Context
// Lets update it and be done.
if o.CurrentContext {
airconfig.CurrentContext = o.Name
} else {
// Context exists, lets update
airconfig.ModifyContext(context, o)
}
contextWasModified = true
}
// Update configuration file just in time persistence approach
if err := airconfig.PersistConfig(); err != nil {
// Error that it didnt persist the changes
return contextWasModified, conferrors.ErrConfigFailed{}
}
return contextWasModified, nil
}

View File

@ -0,0 +1,190 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"bytes"
"testing"
kubeconfig "k8s.io/client-go/tools/clientcmd/api"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/environment"
"opendev.org/airship/airshipctl/testutil"
)
const (
testUser = "admin@kubernetes"
)
type setContextTest struct {
description string
config *config.Config
args []string
flags []string
expected string
expectedConfig *config.Config
}
func TestConfigSetContext(t *testing.T) {
cmdTests := []*testutil.CmdTest{
{
Name: "config-cmd-set-context-with-help",
CmdLine: "--help",
Cmd: NewCmdConfigSetContext(nil),
},
}
for _, tt := range cmdTests {
testutil.RunTest(t, tt)
}
}
func TestSetContext(t *testing.T) {
conf := config.InitConfig(t)
tname := "dummycontext"
tctype := config.Ephemeral
expconf := config.InitConfig(t)
expconf.Contexts[tname] = config.NewContext()
clusterName := config.NewClusterComplexName()
clusterName.WithType(tname, tctype)
expconf.Contexts[tname].NameInKubeconf = clusterName.Name()
expconf.Contexts[tname].Manifest = "edge_cloud"
expkContext := kubeconfig.NewContext()
expkContext.AuthInfo = testUser
expkContext.Namespace = "kube-system"
expconf.KubeConfig().Contexts[expconf.Contexts[tname].NameInKubeconf] = expkContext
test := setContextTest{
description: "Testing 'airshipctl config set-context' with a new context",
config: conf,
args: []string{tname},
flags: []string{
"--" + config.FlagClusterType + "=" + config.Target,
"--" + config.FlagAuthInfoName + "=" + testUser,
"--" + config.FlagManifest + "=edge_cloud",
"--" + config.FlagNamespace + "=kube-system",
},
expected: `Context "` + tname + `" created.` + "\n",
expectedConfig: expconf,
}
test.run(t)
}
func TestSetCurrentContext(t *testing.T) {
tname := "def_target"
conf := config.InitConfig(t)
expconf := config.InitConfig(t)
expconf.CurrentContext = "def_target"
test := setContextTest{
description: "Testing 'airshipctl config set-context' with a new current context",
config: conf,
args: []string{tname},
flags: []string{
"--" + config.FlagCurrentContext + "=true",
},
expected: `Context "` + tname + `" modified.` + "\n",
expectedConfig: expconf,
}
test.run(t)
}
func TestModifyContext(t *testing.T) {
tname := testCluster
tctype := config.Ephemeral
conf := config.InitConfig(t)
conf.Contexts[tname] = config.NewContext()
clusterName := config.NewClusterComplexName()
clusterName.WithType(tname, tctype)
conf.Contexts[tname].NameInKubeconf = clusterName.Name()
kContext := kubeconfig.NewContext()
kContext.AuthInfo = testUser
conf.KubeConfig().Contexts[clusterName.Name()] = kContext
conf.Contexts[tname].SetKubeContext(kContext)
expconf := config.InitConfig(t)
expconf.Contexts[tname] = config.NewContext()
expconf.Contexts[tname].NameInKubeconf = clusterName.Name()
expkContext := kubeconfig.NewContext()
expkContext.AuthInfo = testUser
expconf.KubeConfig().Contexts[clusterName.Name()] = expkContext
expconf.Contexts[tname].SetKubeContext(expkContext)
test := setContextTest{
description: "Testing 'airshipctl config set-context' with an existing context",
config: conf,
args: []string{tname},
flags: []string{
"--" + config.FlagAuthInfoName + "=" + testUser,
},
expected: `Context "` + tname + `" modified.` + "\n",
expectedConfig: expconf,
}
test.run(t)
}
func (test setContextTest) run(t *testing.T) {
// Get the Environment
settings := &environment.AirshipCTLSettings{}
settings.SetConfig(test.config)
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdConfigSetContext(settings)
cmd.SetOutput(buf)
cmd.SetArgs(test.args)
err := cmd.Flags().Parse(test.flags)
require.NoErrorf(t, err, "unexpected error flags args to command: %v, flags: %v", err, test.flags)
// Execute the Command
// Which should Persist the File
err = cmd.Execute()
require.NoErrorf(t, err, "unexpected error executing command: %v, args: %v, flags: %v", err, test.args, test.flags)
afterRunConf := settings.Config()
// Find the Context Created or Modified
afterRunContext, err := afterRunConf.GetContext(test.args[0])
require.NoError(t, err)
require.NotNil(t, afterRunContext)
afterKcontext := afterRunContext.KubeContext()
require.NotNil(t, afterKcontext)
testKcontext := test.expectedConfig.KubeConfig().Contexts[test.expectedConfig.Contexts[test.args[0]].NameInKubeconf]
require.NotNil(t, testKcontext)
assert.EqualValues(t, afterKcontext.AuthInfo, testKcontext.AuthInfo)
// Test that the Return Message looks correct
if len(test.expected) != 0 {
assert.EqualValuesf(t, buf.String(), test.expected, "expected %v, but got %v", test.expected, buf.String())
}
}

View File

@ -5,9 +5,11 @@ Usage:
config [command]
Available Commands:
get-cluster Display a specific cluster
get-cluster Display a specific cluster or all defined clusters if no name is provided
get-context Display a specific context, the current-context or all defined contexts if no name is provided
help Help about any command
set-cluster Sets a cluster entry in the airshipctl config
set-context Sets a context entry or updates current-context in the airshipctl config
Flags:
-h, --help help for config

View File

@ -5,9 +5,11 @@ Usage:
config [command]
Available Commands:
get-cluster Display a specific cluster
get-cluster Display a specific cluster or all defined clusters if no name is provided
get-context Display a specific context, the current-context or all defined contexts if no name is provided
help Help about any command
set-cluster Sets a cluster entry in the airshipctl config
set-context Sets a context entry or updates current-context in the airshipctl config
Flags:
-h, --help help for config

View File

@ -0,0 +1,23 @@
Sets a context entry in arshipctl config.
Specifying a name that already exists will merge new fields on top of existing values for those fields.
Usage:
set-context NAME [flags]
Examples:
# Create a completely new e2e context entry
airshipctl config set-context e2e --namespace=kube-system --manifest=manifest --user=auth-info --cluster-type=target
# Update the current-context to e2e
airshipctl config set-context e2e --current-context=true
Flags:
--cluster string cluster for the context entry in airshipctl config
--cluster-type string cluster-type for the context entry in airshipctl config
--current-context current-context for the context entry in airshipctl config
-h, --help help for set-context
--manifest string manifest for the context entry in airshipctl config
--namespace string namespace for the context entry in airshipctl config
--user string user for the context entry in airshipctl config

View File

@ -0,0 +1,27 @@
Context: ContextBar
context-kubeconf: ContextBar_ephemeral
manifest: Manifest_ContextBar
LocationOfOrigin: ""
cluster: dummycluster_ephemeral
namespace: dummy_namespace
user: dummy_user
Context: ContextBaz
context-kubeconf: ContextBaz_ephemeral
manifest: Manifest_ContextBaz
LocationOfOrigin: ""
cluster: dummycluster_ephemeral
namespace: dummy_namespace
user: dummy_user
Context: ContextFoo
context-kubeconf: ContextFoo_ephemeral
manifest: Manifest_ContextFoo
LocationOfOrigin: ""
cluster: dummycluster_ephemeral
namespace: dummy_namespace
user: dummy_user

View File

@ -0,0 +1,9 @@
Context: ContextFoo
context-kubeconf: ContextFoo_ephemeral
manifest: Manifest_ContextFoo
LocationOfOrigin: ""
cluster: dummycluster_ephemeral
namespace: dummy_namespace
user: dummy_user

View File

@ -0,0 +1,9 @@
Context: ContextBaz
context-kubeconf: ContextBaz_ephemeral
manifest: Manifest_ContextBaz
LocationOfOrigin: ""
cluster: dummycluster_ephemeral
namespace: dummy_namespace
user: dummy_user

View File

@ -0,0 +1,27 @@
Context: ContextBar
context-kubeconf: ContextBar_ephemeral
manifest: Manifest_ContextBar
LocationOfOrigin: ""
cluster: dummycluster_ephemeral
namespace: dummy_namespace
user: dummy_user
Context: ContextBaz
context-kubeconf: ContextBaz_ephemeral
manifest: Manifest_ContextBaz
LocationOfOrigin: ""
cluster: dummycluster_ephemeral
namespace: dummy_namespace
user: dummy_user
Context: ContextFoo
context-kubeconf: ContextFoo_ephemeral
manifest: Manifest_ContextFoo
LocationOfOrigin: ""
cluster: dummycluster_ephemeral
namespace: dummy_namespace
user: dummy_user

View File

@ -0,0 +1,18 @@
Error: Missing configuration
Usage:
get-context NAME [flags]
Examples:
# List all the contexts airshipctl knows about
airshipctl config get-context
# Display the current context
airshipctl config get-context --current-context
# Display a specific Context
airshipctl config get-context e2e
Flags:
--current-context current-context to retrieve the current context entry in airshipctl config
-h, --help help for get-context

View File

@ -47,3 +47,18 @@ func (o *ClusterOptions) Validate() error {
}
return nil
}
func (o *ContextOptions) Validate() error {
if len(o.Name) == 0 {
return errors.New("you must specify a non-empty context name")
}
// Expect ClusterType only when this is not setting currentContext
if o.ClusterType != "" {
err := ValidClusterType(o.ClusterType)
if err != nil {
return err
}
}
// TODO Manifest, Cluster could be validated against the existing config maps
return nil
}

View File

@ -22,7 +22,7 @@ import (
"github.com/stretchr/testify/assert"
)
func TestValidate(t *testing.T) {
func TestValidateCluster(t *testing.T) {
co := DummyClusterOptions()
// Assert that the initial dummy config is valid
@ -61,3 +61,11 @@ func TestValidate(t *testing.T) {
err = co.Validate()
assert.Error(t, err)
}
func TestValidateContext(t *testing.T) {
co := DummyContextOptions()
// Valid Data case
err := co.Validate()
assert.NoError(t, err)
}

View File

@ -8,3 +8,13 @@ type ClusterOptions struct {
CertificateAuthority string
EmbedCAData bool
}
type ContextOptions struct {
Name string
ClusterType string
CurrentContext bool
Cluster string
AuthInfo string
Manifest string
Namespace string
}

View File

@ -32,6 +32,7 @@ import (
kubeconfig "k8s.io/client-go/tools/clientcmd/api"
conferrors "opendev.org/airship/airshipctl/pkg/errors"
"opendev.org/airship/airshipctl/pkg/util"
)
@ -97,7 +98,7 @@ func (c *Config) reconcileConfig() error {
// I changed things during the reconciliation
// Lets reflect them in the config files
// Specially useful if the cnofig is loaded during a get operation
// Specially useful if the config is loaded during a get operation
// If it was a Set this would have happened eventually any way
if persistIt {
return c.PersistConfig()
@ -211,6 +212,7 @@ func (c *Config) reconcileContexts(updatedClusterNames map[string]string) {
}
// Make sure the name matches
c.Contexts[key].NameInKubeconf = context.Cluster
c.Contexts[key].SetKubeContext(context)
// What about if a Context refers to a properly named cluster
// that does not exist in airship config
@ -267,8 +269,6 @@ func (c *Config) reconcileCurrentContext() {
c.kubeConfig.CurrentContext = c.CurrentContext
}
}
c.kubeConfig.CurrentContext = ""
c.CurrentContext = ""
}
// This is called by users of the config to make sure that they have
@ -370,7 +370,6 @@ func (c *Config) KubeConfig() *kubeconfig.Config {
return c.kubeConfig
}
// This might be changed later to be generalized
func (c *Config) ClusterNames() []string {
names := []string{}
for k := range c.Clusters {
@ -378,7 +377,15 @@ func (c *Config) ClusterNames() []string {
}
sort.Strings(names)
return names
}
func (c *Config) ContextNames() []string {
names := []string{}
for k := range c.Contexts {
names = append(names, k)
}
sort.Strings(names)
return names
}
// Get A Cluster
@ -476,12 +483,117 @@ func (c *Config) GetClusters() ([]*Cluster, error) {
if err == nil {
clusters = append(clusters, cluster)
}
}
}
return clusters, nil
}
// Context Operations from Config point of view
// Get Context
func (c *Config) GetContext(cName string) (*Context, error) {
context, exists := c.Contexts[cName]
if !exists {
return nil, conferrors.ErrMissingConfig{}
}
return context, nil
}
func (c *Config) GetContexts() ([]*Context, error) {
contexts := []*Context{}
// Given that we change the testing metholdogy
// The ordered names are no longer required
for _, cName := range c.ContextNames() {
context, err := c.GetContext(cName)
if err == nil {
contexts = append(contexts, context)
}
}
return contexts, nil
}
func (c *Config) AddContext(theContext *ContextOptions) *Context {
// Create the new Airship config context
nContext := NewContext()
c.Contexts[theContext.Name] = nContext
// Create a new Kubeconfig Context object as well
kContext := kubeconfig.NewContext()
nContext.NameInKubeconf = theContext.Name
contextName := NewClusterComplexName()
contextName.WithType(theContext.Name, theContext.ClusterType)
nContext.SetKubeContext(kContext)
c.KubeConfig().Contexts[theContext.Name] = kContext
// Ok , I have initialized structs for the Context information
// We can use Modify to populate the correct information
c.ModifyContext(nContext, theContext)
return nContext
}
func (c *Config) ModifyContext(context *Context, theContext *ContextOptions) {
kContext := context.KubeContext()
if kContext == nil {
return
}
if theContext.Cluster != "" {
kContext.Cluster = theContext.Cluster
}
if theContext.AuthInfo != "" {
kContext.AuthInfo = theContext.AuthInfo
}
if theContext.Manifest != "" {
context.Manifest = theContext.Manifest
}
if theContext.Namespace != "" {
kContext.Namespace = theContext.Namespace
}
}
// CurrentContext methods Returns the appropriate information for the current context
// Current Context holds labels for the approriate config objects
// Cluster is the name of the cluster for this context
// ClusterType is the name of the clustertype for this context, it should be a flag we pass to it??
// AuthInfo is the name of the authInfo for this context
// Manifest is the default manifest to be use with this context
// Purpose for this method is simplifying the current context information
func (c *Config) GetCurrentContext() (*Context, error) {
if err := c.EnsureComplete(); err != nil {
return nil, err
}
currentContext, err := c.GetContext(c.CurrentContext)
if err != nil {
// this should not happen since Ensure Complete checks for this
return nil, err
}
return currentContext, nil
}
func (c *Config) CurrentContextCluster() (*Cluster, error) {
currentContext, err := c.GetCurrentContext()
if err != nil {
return nil, err
}
return c.Clusters[currentContext.KubeContext().Cluster].ClusterTypes[currentContext.ClusterType()], nil
}
func (c *Config) CurrentContextAuthInfo() (*AuthInfo, error) {
currentContext, err := c.GetCurrentContext()
if err != nil {
return nil, err
}
return c.AuthInfos[currentContext.KubeContext().AuthInfo], nil
}
func (c *Config) CurrentContextManifest() (*Manifest, error) {
currentContext, err := c.GetCurrentContext()
if err != nil {
return nil, err
}
return c.Manifests[currentContext.Manifest], nil
}
// Purge removes the config file
func (c *Config) Purge() error {
//configFile := c.ConfigFile()
@ -550,15 +662,44 @@ func (c *Context) Equal(d *Context) bool {
return d == c
}
return c.NameInKubeconf == d.NameInKubeconf &&
c.Manifest == d.Manifest
c.Manifest == d.Manifest &&
c.kContext == d.kContext
}
func (c *Context) String() string {
yaml, err := yaml.Marshal(&c)
cyaml, err := yaml.Marshal(&c)
if err != nil {
return ""
}
return string(yaml)
kcluster := c.KubeContext()
kyaml, err := yaml.Marshal(&kcluster)
if err != nil {
return string(cyaml)
}
return fmt.Sprintf("%s\n%s", string(cyaml), string(kyaml))
}
func (c *Context) PrettyString() string {
clusterName := NewClusterComplexName()
clusterName.FromName(c.NameInKubeconf)
return fmt.Sprintf("Context: %s\n%s\n",
clusterName.ClusterName(), c.String())
}
func (c *Context) KubeContext() *kubeconfig.Context {
return c.kContext
}
func (c *Context) SetKubeContext(kc *kubeconfig.Context) {
c.kContext = kc
}
func (c *Context) ClusterType() string {
clusterName := NewClusterComplexName()
clusterName.FromName(c.NameInKubeconf)
return clusterName.ClusterType()
}
// AuthInfo functions
@ -695,3 +836,11 @@ func KClusterString(kCluster *kubeconfig.Cluster) string {
return string(yaml)
}
func KContextString(kContext *kubeconfig.Context) string {
yaml, err := yaml.Marshal(&kContext)
if err != nil {
return ""
}
return string(yaml)
}

View File

@ -232,11 +232,6 @@ func TestPurge(t *testing.T) {
assert.Falsef(t, os.IsExist(err), "Purge failed to remove file at %v", config.LoadedConfigPath())
}
func TestClusterNames(t *testing.T) {
conf := InitConfig(t)
expected := []string{"def", "onlyinkubeconf", "wrongonlyinconfig", "wrongonlyinkubeconf"}
assert.EqualValues(t, expected, conf.ClusterNames())
}
func TestKClusterString(t *testing.T) {
conf := InitConfig(t)
kClusters := conf.KubeConfig().Clusters
@ -245,6 +240,14 @@ func TestKClusterString(t *testing.T) {
}
assert.EqualValues(t, KClusterString(nil), "null\n")
}
func TestKContextString(t *testing.T) {
conf := InitConfig(t)
kContexts := conf.KubeConfig().Contexts
for kCtx := range kContexts {
assert.NotEmpty(t, KContextString(kContexts[kCtx]))
}
assert.EqualValues(t, KClusterString(nil), "null\n")
}
func TestComplexName(t *testing.T) {
cName := "aCluster"
ctName := Ephemeral
@ -372,3 +375,25 @@ func TestReconcileClusters(t *testing.T) {
// Check that the "stragglers" were removed from the airshipconfig
assert.NotContains(t, testConfig.Clusters, "straggler")
}
func TestGetContexts(t *testing.T) {
conf := InitConfig(t)
contexts, err := conf.GetContexts()
require.NoError(t, err)
assert.Len(t, contexts, 3)
}
func TestGetContext(t *testing.T) {
conf := InitConfig(t)
context, err := conf.GetContext("def_ephemeral")
require.NoError(t, err)
// Test Positives
assert.EqualValues(t, context.NameInKubeconf, "def_ephemeral")
assert.EqualValues(t, context.KubeContext().Cluster, "def_ephemeral")
// Test Wrong Cluster
_, err = conf.GetContext("unknown")
assert.Error(t, err)
}

View File

@ -28,23 +28,24 @@ const (
// Constants defining CLI flags
const (
FlagAPIServer = "server"
FlagAuthInfoName = "user"
FlagBearerToken = "token"
FlagCAFile = "certificate-authority"
FlagCertFile = "client-certificate"
FlagClusterName = "cluster"
FlagClusterType = "cluster-type"
FlagAuthInfoName = "user"
FlagContext = "context"
FlagCurrentContext = "current-context"
FlagConfigFilePath = AirshipConfigEnv
FlagNamespace = "namespace"
FlagAPIServer = "server"
FlagInsecure = "insecure-skip-tls-verify"
FlagCertFile = "client-certificate"
FlagKeyFile = "client-key"
FlagCAFile = "certificate-authority"
FlagEmbedCerts = "embed-certs"
FlagBearerToken = "token"
FlagImpersonate = "as"
FlagImpersonateGroup = "as-group"
FlagUsername = "username"
FlagInsecure = "insecure-skip-tls-verify"
FlagKeyFile = "client-key"
FlagManifest = "manifest"
FlagNamespace = "namespace"
FlagPassword = "password"
FlagTimeout = "request-timeout"
FlagManifest = "manifest"
FlagUsername = "username"
)

View File

@ -60,6 +60,12 @@ func DummyContext() *Context {
c := NewContext()
c.NameInKubeconf = "dummy_cluster"
c.Manifest = "dummy_manifest"
context := kubeconfig.NewContext()
context.Namespace = "dummy_namespace"
context.AuthInfo = "dummy_user"
context.Cluster = "dummycluster_ephemeral"
c.SetKubeContext(context)
return c
}
@ -144,6 +150,17 @@ func DummyClusterOptions() *ClusterOptions {
return co
}
func DummyContextOptions() *ContextOptions {
co := &ContextOptions{}
co.Name = "dummy_context"
co.Manifest = "dummy_manifest"
co.AuthInfo = "dummy_user"
co.CurrentContext = false
co.Namespace = "dummy_namespace"
return co
}
const (
testConfigYAML = `apiVersion: airshipit.org/v1alpha1
clusters:

View File

@ -1,2 +1,7 @@
context-kubeconf: dummy_cluster
manifest: dummy_manifest
LocationOfOrigin: ""
cluster: dummycluster_ephemeral
namespace: dummy_namespace
user: dummy_user

View File

@ -97,15 +97,18 @@ type Modules struct {
Dummy string `json:"dummy-for-tests"`
}
// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster),
// Context is a tuple of references to a cluster (how do I communicate with a kubernetes context),
// a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
type Context struct {
// Context name in kubeconf. Should include a clustername following the naming conventions for airshipctl
// <clustername>_<clustertype>
// Context name in kubeconf
NameInKubeconf string `json:"context-kubeconf"`
// Manifest is the default manifest to be use with this context
// +optional
Manifest string `json:"manifest,omitempty"`
// Kubeconfig Context Object
kContext *kubeconfig.Context
}
type AuthInfo struct {

View File

@ -1,11 +1,24 @@
package errors
// AirshipError is the base error type
// used to create extended error types
// in other airshipctl packages.
type AirshipError struct {
Message string
}
// Error function implments the golang
// error interface
func (ae *AirshipError) Error() string {
return ae.Message
}
// ErrNotImplemented returned for not implemented features
type ErrNotImplemented struct {
}
func (e ErrNotImplemented) Error() string {
return "Error. Not implemented"
return "Not implemented"
}
// ErrWrongConfig returned in case of incorrect configuration
@ -13,5 +26,21 @@ type ErrWrongConfig struct {
}
func (e ErrWrongConfig) Error() string {
return "Error. Wrong configuration"
return "Wrong configuration"
}
// ErrMissingConfig returned in case of missing configuration
type ErrMissingConfig struct {
}
func (e ErrMissingConfig) Error() string {
return "Missing configuration"
}
// ErrConfigFailed returned in case of failure during configuration
type ErrConfigFailed struct {
}
func (e ErrConfigFailed) Error() string {
return "Configuration failed to complete."
}