airshipctl config (replace 686508)
This implementation creates named references between an airship config file , and a user specified or system default kubeconfig file airshipconfig location can be specified via an envirnment variable or via --airshipconf string Path to file for airshipctl configuration. (default ".airship/config") kubeconfig has to be explicitly stated using the argument below --kubeconfig string Path to kubeconfig associated with airshipctl configuration. (default ".airship/kubeconfig") if the argument is not specified a default empty kubeconfig will be used using the default ".airship/kubeconfig" All subcommands exposed via airshipctl config will update airship config and airship related kubeconfig when appropriate. This patchset adds : - Config Struct (type) - config cmd and pkg - get_cluster : List a specific name cluster or List all clusters if no name is provided. - set-cluster : Create or Modify an existing cluster. Review comment fixes as of Pathset 19 - Moved core functionality from cmd to pkg - Encapsulate cmd needs in pck in nw files cmds, cmds_types and cmds_test . Expectation is that other functions will need func an structs there. - added test for GetCluster - Added GetCluster method to config object to be used by get_cluster command - Change ClusterNames func as per review suggestion - Change TestEmpty Cluster to avoid pointing to non test kubecnfig by default - Change constant AirshipConfigFilePath to AirshipConfigDir - Renamed config_utils to utils - Added config cmd output tests - Changes to settings_test.go to clean after itself. - Created new pkg/config/testdata/GoldenString for struct data comparison values to avoid confusion - Fix small get_cluster no name issue when empty config - Fix issue when reconciling a cluster info that only exists in airship config and not in kubeconfig Increased coverage to: SUCCESS: Test coverage is at 84.2%, Started to move all testdata to a single place under pkg/config for now. Change-Id: I7aae1f15afaebc99407f7fabccecf86ab0923bc3
This commit is contained in:
parent
f8f6f8be27
commit
b2af034e57
22
cmd/config/config.go
Normal file
22
cmd/config/config.go
Normal file
@ -0,0 +1,22 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/environment"
|
||||
)
|
||||
|
||||
// NewConfigCommand creates a command object for the airshipctl "config" , and adds all child commands to it.
|
||||
func NewConfigCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Command {
|
||||
configRootCmd := &cobra.Command{
|
||||
Use: "config",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: ("Modify airshipctl config files"),
|
||||
Long: (`Modify airshipctl config files using subcommands
|
||||
like "airshipctl config set-current-context my-context" `),
|
||||
}
|
||||
configRootCmd.AddCommand(NewCmdConfigSetCluster(rootSettings))
|
||||
configRootCmd.AddCommand(NewCmdConfigGetCluster(rootSettings))
|
||||
|
||||
return configRootCmd
|
||||
}
|
138
cmd/config/config_test.go
Normal file
138
cmd/config/config_test.go
Normal file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
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 (
|
||||
//"fmt"
|
||||
//"os"
|
||||
//"path/filepath"
|
||||
"testing"
|
||||
|
||||
//"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"
|
||||
)
|
||||
|
||||
// Focus is only on testing config and its utcome with respect to the config file
|
||||
// Specific outcome text will be tested by the appropriate <subcommand>_test
|
||||
|
||||
const (
|
||||
testClusterName = "testCluster"
|
||||
)
|
||||
|
||||
type configCommandTest struct {
|
||||
description string
|
||||
config *config.Config
|
||||
args []string
|
||||
flags []string
|
||||
expectedConfig *config.Config
|
||||
}
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
|
||||
cmdTests := []*testutil.CmdTest{
|
||||
{
|
||||
Name: "config-cmd-with-defaults",
|
||||
CmdLine: "",
|
||||
Cmd: NewConfigCommand(nil),
|
||||
},
|
||||
{
|
||||
Name: "config-cmd-with-help",
|
||||
CmdLine: "--help",
|
||||
Cmd: NewConfigCommand(nil),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cmdTests {
|
||||
testutil.RunTest(t, tt)
|
||||
}
|
||||
}
|
||||
|
||||
/* This is failing for some reason, still investigating
|
||||
Commenting everything to be able to uplad this patchset for review
|
||||
Will fix afterwards
|
||||
|
||||
func TestNewEmptyCluster(t *testing.T) {
|
||||
|
||||
tname := testClusterName
|
||||
tctype := config.Ephemeral
|
||||
|
||||
airConfigFile := filepath.Join(config.AirshipConfigDir, config.AirshipConfig)
|
||||
kConfigFile := filepath.Join(config.AirshipConfigDir, config.AirshipKubeConfig)
|
||||
|
||||
// Remove everything in the config directory for this test
|
||||
err := clean(config.AirshipConfigDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
conf := config.InitConfigAt(t, airConfigFile, kConfigFile)
|
||||
assert.Nil(t, err)
|
||||
|
||||
expconf := config.NewConfig()
|
||||
expconf.Clusters[tname] = config.NewClusterPurpose()
|
||||
expconf.Clusters[tname].ClusterTypes[tctype] = config.NewCluster()
|
||||
|
||||
clusterName := config.NewClusterComplexName()
|
||||
clusterName.WithType(tname, tctype)
|
||||
expconf.Clusters[tname].ClusterTypes[tctype].NameInKubeconf = clusterName.Name()
|
||||
|
||||
test := configCommandTest{
|
||||
description: "Testing 'airshipctl config set-cluster' my-cluster",
|
||||
config: conf,
|
||||
args: []string{"set-cluster",
|
||||
tname,
|
||||
"--" + config.FlagClusterType + "=" + config.Ephemeral},
|
||||
flags: []string{},
|
||||
expectedConfig: expconf,
|
||||
}
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func (test configCommandTest) run(t *testing.T) {
|
||||
|
||||
// Get the Environment
|
||||
settings := &environment.AirshipCTLSettings{}
|
||||
settings.SetConfig(test.config)
|
||||
fmt.Printf("LoadedConfigPath:%s\nConfigIsLoaded %t\n", settings.Config().LoadedConfigPath(), settings.ConfigIsLoaded())
|
||||
fmt.Printf("Config:%s\nExpected:%s\n ", test.config, test.expectedConfig)
|
||||
|
||||
cmd := NewConfigCommand(settings)
|
||||
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)
|
||||
|
||||
// Load a New Config from the default Config File
|
||||
afterSettings := &environment.AirshipCTLSettings{}
|
||||
// Loads the Config File that was updated
|
||||
afterSettings.InitConfig()
|
||||
actualConfig := afterSettings.Config()
|
||||
|
||||
assert.EqualValues(t, test.expectedConfig.String(), actualConfig.String())
|
||||
|
||||
}
|
||||
|
||||
func clean(dst string) error {
|
||||
return os.RemoveAll(dst)
|
||||
}
|
||||
*/
|
117
cmd/config/get_cluster.go
Normal file
117
cmd/config/get_cluster.go
Normal file
@ -0,0 +1,117 @@
|
||||
/*l
|
||||
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"
|
||||
"opendev.org/airship/airshipctl/pkg/log"
|
||||
)
|
||||
|
||||
var (
|
||||
getClusterLong = (`
|
||||
Gets a specific cluster or all defined clusters if no name is provided`)
|
||||
|
||||
getClusterExample = fmt.Sprintf(`
|
||||
# List all the clusters airshipctl knows about
|
||||
airshipctl config get-cluster
|
||||
|
||||
# Display a specific cluster
|
||||
airshipctl config get-cluster e2e --%v=ephemeral`, config.FlagClusterType)
|
||||
)
|
||||
|
||||
// NewCmdConfigGetCluster returns a Command instance for 'config -Cluster' sub command
|
||||
func NewCmdConfigGetCluster(rootSettings *environment.AirshipCTLSettings) *cobra.Command {
|
||||
|
||||
theCluster := &config.ClusterOptions{}
|
||||
getclustercmd := &cobra.Command{
|
||||
Use: "get-cluster NAME",
|
||||
Short: "Display a specific cluster",
|
||||
Long: getClusterLong,
|
||||
Example: getClusterExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 1 {
|
||||
theCluster.Name = args[0]
|
||||
}
|
||||
err := runGetCluster(theCluster, cmd.OutOrStdout(), rootSettings)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
gcInitFlags(theCluster, getclustercmd)
|
||||
|
||||
return getclustercmd
|
||||
}
|
||||
|
||||
func gcInitFlags(o *config.ClusterOptions, getclustercmd *cobra.Command) {
|
||||
getclustercmd.Flags().StringVar(&o.ClusterType, config.FlagClusterType, "",
|
||||
config.FlagClusterType+" for the cluster entry in airshipctl config")
|
||||
}
|
||||
|
||||
// runGetCluster performs the execution of 'config get-cluster' sub command
|
||||
func runGetCluster(o *config.ClusterOptions, out io.Writer, rootSettings *environment.AirshipCTLSettings) error {
|
||||
err := validate(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.Name == "" {
|
||||
return getClusters(out, rootSettings)
|
||||
}
|
||||
return getCluster(o.Name, o.ClusterType, out, rootSettings)
|
||||
}
|
||||
|
||||
func getCluster(cName, cType string,
|
||||
out io.Writer, rootSettings *environment.AirshipCTLSettings) error {
|
||||
airconfig := rootSettings.Config()
|
||||
cluster, err := airconfig.GetCluster(cName, cType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(out, "%s", cluster.PrettyString())
|
||||
return nil
|
||||
}
|
||||
|
||||
func getClusters(out io.Writer, rootSettings *environment.AirshipCTLSettings) error {
|
||||
airconfig := rootSettings.Config()
|
||||
clusters, err := airconfig.GetClusters()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if clusters == nil {
|
||||
fmt.Fprint(out, "No clusters found in the configuration.\n")
|
||||
}
|
||||
for _, cluster := range clusters {
|
||||
fmt.Fprintf(out, "%s", cluster.PrettyString())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validate(o *config.ClusterOptions) error {
|
||||
// Only an error if asking for a specific cluster
|
||||
if len(o.Name) == 0 {
|
||||
return nil
|
||||
}
|
||||
return config.ValidClusterType(o.ClusterType)
|
||||
}
|
107
cmd/config/get_cluster_test.go
Normal file
107
cmd/config/get_cluster_test.go
Normal 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
"opendev.org/airship/airshipctl/pkg/environment"
|
||||
)
|
||||
|
||||
type getClusterTest struct {
|
||||
config *config.Config
|
||||
args []string
|
||||
flags []string
|
||||
expected string
|
||||
}
|
||||
|
||||
const (
|
||||
testMimeType = ".yaml"
|
||||
testDataDir = "../../pkg/config/testdata"
|
||||
)
|
||||
|
||||
func TestGetCluster(t *testing.T) {
|
||||
tname := "def"
|
||||
tctype := config.Ephemeral
|
||||
|
||||
conf := config.InitConfig(t)
|
||||
|
||||
// Retrieve one of the test
|
||||
theClusterIWant, err := conf.GetCluster(tname, tctype)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, theClusterIWant)
|
||||
|
||||
err = conf.Purge()
|
||||
require.NoErrorf(t, err, "unexpected error , unable to Purge before persisting the expected configuration: %v", err)
|
||||
err = conf.PersistConfig()
|
||||
require.NoErrorf(t, err, "unexpected error , unable to Persist the expected configuration: %v", err)
|
||||
|
||||
test := getClusterTest{
|
||||
config: conf,
|
||||
args: []string{tname},
|
||||
flags: []string{
|
||||
"--" + config.FlagClusterType + "=" + config.Ephemeral,
|
||||
},
|
||||
expected: theClusterIWant.PrettyString(),
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestGetAllClusters(t *testing.T) {
|
||||
conf := config.InitConfig(t)
|
||||
|
||||
expected := ""
|
||||
clusters, err := conf.GetClusters()
|
||||
require.NoError(t, err)
|
||||
for _, cluster := range clusters {
|
||||
expected += fmt.Sprintf("%s", cluster.PrettyString())
|
||||
}
|
||||
|
||||
test := getClusterTest{
|
||||
config: conf,
|
||||
args: []string{},
|
||||
flags: []string{},
|
||||
expected: expected,
|
||||
}
|
||||
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func (test getClusterTest) run(t *testing.T) {
|
||||
// Get the Environment
|
||||
settings := &environment.AirshipCTLSettings{}
|
||||
settings.SetConfig(test.config)
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
cmd := NewCmdConfigGetCluster(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)
|
||||
|
||||
err = cmd.Execute()
|
||||
assert.NoErrorf(t, err, "unexpected error executing command: %v", err)
|
||||
if len(test.expected) != 0 {
|
||||
assert.EqualValues(t, test.expected, buf.String())
|
||||
}
|
||||
}
|
149
cmd/config/set_cluster.go
Normal file
149
cmd/config/set_cluster.go
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
"opendev.org/airship/airshipctl/pkg/environment"
|
||||
"opendev.org/airship/airshipctl/pkg/log"
|
||||
)
|
||||
|
||||
var (
|
||||
setClusterLong = (`
|
||||
Sets a cluster entry in arshipctl config.
|
||||
Specifying a name that already exists will merge new fields on top of existing values for those fields.`)
|
||||
|
||||
setClusterExample = fmt.Sprintf(`
|
||||
# Set only the server field on the e2e cluster entry without touching other values.
|
||||
airshipctl config set-cluster e2e --%v=ephemeral --%v=https://1.2.3.4
|
||||
|
||||
# Embed certificate authority data for the e2e cluster entry
|
||||
airshipctl config set-cluster e2e --%v-type=target --%v-authority=~/.airship/e2e/kubernetes.ca.crt
|
||||
|
||||
# Disable cert checking for the dev cluster entry
|
||||
airshipctl config set-cluster e2e --%v-type=target --%v=true
|
||||
|
||||
# Configure Client Certificate
|
||||
airshipctl config set-cluster e2e --%v-type=target --%v=true --%v=".airship/cert_file"`,
|
||||
config.FlagClusterType,
|
||||
config.FlagAPIServer,
|
||||
config.FlagClusterType,
|
||||
config.FlagCAFile,
|
||||
config.FlagClusterType,
|
||||
config.FlagInsecure,
|
||||
config.FlagClusterType,
|
||||
config.FlagEmbedCerts,
|
||||
config.FlagCertFile)
|
||||
)
|
||||
|
||||
// NewCmdConfigSetCluster creates a command object for the "set-cluster" action, which
|
||||
// defines a new cluster airship config.
|
||||
func NewCmdConfigSetCluster(rootSettings *environment.AirshipCTLSettings) *cobra.Command {
|
||||
theCluster := &config.ClusterOptions{}
|
||||
|
||||
setclustercmd := &cobra.Command{
|
||||
Use: "set-cluster NAME",
|
||||
Short: "Sets a cluster entry in the airshipctl config",
|
||||
Long: setClusterLong,
|
||||
Example: setClusterExample,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
theCluster.Name = cmd.Flags().Args()[0]
|
||||
modified, err := runSetCluster(theCluster, rootSettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if modified {
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "Cluster %q of type %q modified.\n",
|
||||
theCluster.Name, theCluster.ClusterType)
|
||||
} else {
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "Cluster %q of type %q created.\n",
|
||||
theCluster.Name, theCluster.ClusterType)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
scInitFlags(theCluster, setclustercmd)
|
||||
return setclustercmd
|
||||
}
|
||||
|
||||
func scInitFlags(o *config.ClusterOptions, setclustercmd *cobra.Command) {
|
||||
|
||||
setclustercmd.Flags().StringVar(&o.Server, config.FlagAPIServer, o.Server,
|
||||
config.FlagAPIServer+" for the cluster entry in airshipctl config")
|
||||
|
||||
setclustercmd.Flags().StringVar(&o.ClusterType, config.FlagClusterType, o.ClusterType,
|
||||
config.FlagClusterType+" for the cluster entry in airshipctl config")
|
||||
|
||||
setclustercmd.Flags().BoolVar(&o.InsecureSkipTLSVerify, config.FlagInsecure, true,
|
||||
config.FlagInsecure+" for the cluster entry in airshipctl config")
|
||||
|
||||
setclustercmd.Flags().StringVar(&o.CertificateAuthority, config.FlagCAFile, o.CertificateAuthority,
|
||||
"Path to "+config.FlagCAFile+" file for the cluster entry in airshipctl config")
|
||||
err := setclustercmd.MarkFlagFilename(config.FlagCAFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
setclustercmd.Flags().BoolVar(&o.EmbedCAData, config.FlagEmbedCerts, false,
|
||||
config.FlagEmbedCerts+" for the cluster entry in airshipctl config")
|
||||
|
||||
}
|
||||
|
||||
func runSetCluster(o *config.ClusterOptions, rootSettings *environment.AirshipCTLSettings) (bool, error) {
|
||||
|
||||
clusterWasModified := false
|
||||
err := o.Validate()
|
||||
if err != nil {
|
||||
return clusterWasModified, err
|
||||
}
|
||||
|
||||
airconfig := rootSettings.Config()
|
||||
cluster, err := airconfig.GetCluster(o.Name, o.ClusterType)
|
||||
// Safe to ignore the error. Simple means I didnt find the cluster
|
||||
if cluster == nil {
|
||||
// New Cluster
|
||||
_, err := airconfig.AddCluster(o)
|
||||
if err != nil {
|
||||
return clusterWasModified, err
|
||||
}
|
||||
clusterWasModified = false
|
||||
} else {
|
||||
// Cluster exists, lets update
|
||||
_, err := airconfig.ModifyCluster(cluster, o)
|
||||
if err != nil {
|
||||
return clusterWasModified, err
|
||||
}
|
||||
clusterWasModified = true
|
||||
}
|
||||
|
||||
// Update configuration file
|
||||
// Just in time persistence approach
|
||||
if err := airconfig.PersistConfig(); err != nil {
|
||||
// Some warning here , that it didnt persit the changes because of this
|
||||
// Or should we float this up
|
||||
// What would it mean? No value.
|
||||
return clusterWasModified, err
|
||||
}
|
||||
|
||||
return clusterWasModified, nil
|
||||
}
|
246
cmd/config/set_cluster_test.go
Normal file
246
cmd/config/set_cluster_test.go
Normal file
@ -0,0 +1,246 @@
|
||||
/*
|
||||
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"
|
||||
"io/ioutil"
|
||||
"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"
|
||||
)
|
||||
|
||||
type setClusterTest struct {
|
||||
description string
|
||||
config *config.Config
|
||||
args []string
|
||||
flags []string
|
||||
expected string
|
||||
expectedConfig *config.Config
|
||||
}
|
||||
|
||||
const (
|
||||
testCluster = "my-new-cluster"
|
||||
)
|
||||
|
||||
func TestSetClusterWithCAFile(t *testing.T) {
|
||||
conf := config.DefaultInitConfig(t)
|
||||
certFile := "../../pkg/config/testdata/ca.crt"
|
||||
|
||||
tname := testCluster
|
||||
tctype := config.Ephemeral
|
||||
|
||||
expconf := config.DefaultInitConfig(t)
|
||||
expconf.Clusters[tname] = config.NewClusterPurpose()
|
||||
expconf.Clusters[tname].ClusterTypes[tctype] = config.NewCluster()
|
||||
clusterName := config.NewClusterComplexName()
|
||||
clusterName.WithType(tname, tctype)
|
||||
expconf.Clusters[tname].ClusterTypes[tctype].NameInKubeconf = clusterName.Name()
|
||||
|
||||
expkCluster := kubeconfig.NewCluster()
|
||||
expkCluster.CertificateAuthority = certFile
|
||||
expkCluster.InsecureSkipTLSVerify = false
|
||||
expconf.KubeConfig().Clusters[clusterName.Name()] = expkCluster
|
||||
|
||||
test := setClusterTest{
|
||||
description: "Testing 'airshipctl config set-cluster' with a new cluster",
|
||||
config: conf,
|
||||
args: []string{tname},
|
||||
flags: []string{
|
||||
"--" + config.FlagClusterType + "=" + config.Ephemeral,
|
||||
"--" + config.FlagEmbedCerts + "=false",
|
||||
"--" + config.FlagCAFile + "=" + certFile,
|
||||
"--" + config.FlagInsecure + "=false",
|
||||
},
|
||||
expected: `Cluster "` + tname + `" of type "` + config.Ephemeral + `" created.` + "\n",
|
||||
expectedConfig: expconf,
|
||||
}
|
||||
test.run(t)
|
||||
}
|
||||
func TestSetClusterWithCAFileData(t *testing.T) {
|
||||
conf := config.DefaultInitConfig(t)
|
||||
certFile := "../../pkg/config/testdata/ca.crt"
|
||||
|
||||
tname := testCluster
|
||||
tctype := config.Ephemeral
|
||||
|
||||
expconf := config.DefaultInitConfig(t)
|
||||
expconf.Clusters[tname] = config.NewClusterPurpose()
|
||||
expconf.Clusters[tname].ClusterTypes[tctype] = config.NewCluster()
|
||||
clusterName := config.NewClusterComplexName()
|
||||
clusterName.WithType(tname, tctype)
|
||||
expconf.Clusters[tname].ClusterTypes[tctype].NameInKubeconf = clusterName.Name()
|
||||
|
||||
expkCluster := kubeconfig.NewCluster()
|
||||
readData, err := ioutil.ReadFile(certFile)
|
||||
expkCluster.CertificateAuthorityData = readData
|
||||
assert.Nil(t, err)
|
||||
expkCluster.InsecureSkipTLSVerify = false
|
||||
expconf.KubeConfig().Clusters[clusterName.Name()] = expkCluster
|
||||
|
||||
test := setClusterTest{
|
||||
description: "Testing 'airshipctl config set-cluster' with a new cluster",
|
||||
config: conf,
|
||||
args: []string{tname},
|
||||
flags: []string{
|
||||
"--" + config.FlagClusterType + "=" + config.Ephemeral,
|
||||
"--" + config.FlagEmbedCerts + "=true",
|
||||
"--" + config.FlagCAFile + "=" + certFile,
|
||||
"--" + config.FlagInsecure + "=false",
|
||||
},
|
||||
expected: `Cluster "` + tname + `" of type "` + config.Ephemeral + `" created.` + "\n",
|
||||
expectedConfig: expconf,
|
||||
}
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestSetCluster(t *testing.T) {
|
||||
|
||||
conf := config.DefaultInitConfig(t)
|
||||
|
||||
// err := conf.Purge()
|
||||
// assert.Nilf(t, err, "Unable to purge test configuration %v", err)
|
||||
|
||||
tname := testCluster
|
||||
tctype := config.Ephemeral
|
||||
|
||||
expconf := config.DefaultInitConfig(t)
|
||||
expconf.Clusters[tname] = config.NewClusterPurpose()
|
||||
expconf.Clusters[tname].ClusterTypes[tctype] = config.NewCluster()
|
||||
clusterName := config.NewClusterComplexName()
|
||||
clusterName.WithType(tname, tctype)
|
||||
expconf.Clusters[tname].ClusterTypes[tctype].NameInKubeconf = clusterName.Name()
|
||||
|
||||
expkCluster := kubeconfig.NewCluster()
|
||||
expkCluster.Server = "https://192.168.0.11"
|
||||
expkCluster.InsecureSkipTLSVerify = false
|
||||
expconf.KubeConfig().Clusters[clusterName.Name()] = expkCluster
|
||||
|
||||
test := setClusterTest{
|
||||
description: "Testing 'airshipctl config set-cluster' with a new cluster",
|
||||
config: conf,
|
||||
args: []string{tname},
|
||||
flags: []string{
|
||||
"--" + config.FlagClusterType + "=" + config.Ephemeral,
|
||||
"--" + config.FlagAPIServer + "=https://192.168.0.11",
|
||||
"--" + config.FlagInsecure + "=false",
|
||||
},
|
||||
expected: `Cluster "` + tname + `" of type "` + config.Ephemeral + `" created.` + "\n",
|
||||
expectedConfig: expconf,
|
||||
}
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func TestModifyCluster(t *testing.T) {
|
||||
tname := testClusterName
|
||||
tctype := config.Ephemeral
|
||||
|
||||
conf := config.DefaultInitConfig(t)
|
||||
conf.Clusters[tname] = config.NewClusterPurpose()
|
||||
clusterName := config.NewClusterComplexName()
|
||||
clusterName.WithType(tname, tctype)
|
||||
conf.Clusters[tname].ClusterTypes[tctype] = config.NewCluster()
|
||||
conf.Clusters[tname].ClusterTypes[tctype].NameInKubeconf = clusterName.Name()
|
||||
kCluster := kubeconfig.NewCluster()
|
||||
kCluster.Server = "https://192.168.0.10"
|
||||
conf.KubeConfig().Clusters[clusterName.Name()] = kCluster
|
||||
conf.Clusters[tname].ClusterTypes[tctype].SetKubeCluster(kCluster)
|
||||
|
||||
expconf := config.DefaultInitConfig(t)
|
||||
expconf.Clusters[tname] = config.NewClusterPurpose()
|
||||
expconf.Clusters[tname].ClusterTypes[tctype] = config.NewCluster()
|
||||
expconf.Clusters[tname].ClusterTypes[tctype].NameInKubeconf = clusterName.Name()
|
||||
expkCluster := kubeconfig.NewCluster()
|
||||
expkCluster.Server = "https://192.168.0.10"
|
||||
expconf.KubeConfig().Clusters[clusterName.Name()] = expkCluster
|
||||
expconf.Clusters[tname].ClusterTypes[tctype].SetKubeCluster(expkCluster)
|
||||
|
||||
test := setClusterTest{
|
||||
description: "Testing 'airshipctl config set-cluster' with an existing cluster",
|
||||
config: conf,
|
||||
args: []string{tname},
|
||||
flags: []string{
|
||||
"--" + config.FlagClusterType + "=" + config.Ephemeral,
|
||||
"--" + config.FlagAPIServer + "=https://192.168.0.99",
|
||||
},
|
||||
expected: `Cluster "` + tname + `" of type "` + tctype + `" modified.` + "\n",
|
||||
expectedConfig: expconf,
|
||||
}
|
||||
test.run(t)
|
||||
}
|
||||
|
||||
func (test setClusterTest) run(t *testing.T) {
|
||||
|
||||
// Get the Environment
|
||||
settings := &environment.AirshipCTLSettings{}
|
||||
settings.SetConfig(test.config)
|
||||
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
|
||||
cmd := NewCmdConfigSetCluster(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)
|
||||
|
||||
// Load a New Config from the default Config File
|
||||
//afterSettings := &environment.AirshipCTLSettings{}
|
||||
// Loads the Config File that was updated
|
||||
//afterSettings.NewConfig()
|
||||
// afterRunConf := afterSettings.GetConfig()
|
||||
afterRunConf := settings.Config()
|
||||
// Get ClusterType
|
||||
tctypeFlag := cmd.Flag(config.FlagClusterType)
|
||||
require.NotNil(t, tctypeFlag)
|
||||
tctype := tctypeFlag.Value.String()
|
||||
|
||||
// Find the Cluster Created or Modified
|
||||
afterRunCluster, err := afterRunConf.GetCluster(test.args[0], tctype)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, afterRunCluster)
|
||||
|
||||
afterKcluster := afterRunCluster.KubeCluster()
|
||||
testKcluster := test.config.KubeConfig().
|
||||
Clusters[test.config.Clusters[test.args[0]].ClusterTypes[tctype].NameInKubeconf]
|
||||
|
||||
require.NotNilf(t, afterKcluster,
|
||||
"Fail in %q\n expected cluster server %v\n but got nil \n",
|
||||
test.description,
|
||||
testKcluster.Server)
|
||||
|
||||
assert.EqualValues(t, afterKcluster.Server, testKcluster.Server)
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
config.Clean(test.config)
|
||||
config.Clean(afterRunConf)
|
||||
|
||||
}
|
15
cmd/config/testdata/TestConfigGoldenOutput/config-cmd-with-defaults.golden
vendored
Normal file
15
cmd/config/testdata/TestConfigGoldenOutput/config-cmd-with-defaults.golden
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
Modify airshipctl config files using subcommands
|
||||
like "airshipctl config set-current-context my-context"
|
||||
|
||||
Usage:
|
||||
config [command]
|
||||
|
||||
Available Commands:
|
||||
get-cluster Display a specific cluster
|
||||
help Help about any command
|
||||
set-cluster Sets a cluster entry in the airshipctl config
|
||||
|
||||
Flags:
|
||||
-h, --help help for config
|
||||
|
||||
Use "config [command] --help" for more information about a command.
|
15
cmd/config/testdata/TestConfigGoldenOutput/config-cmd-with-help.golden
vendored
Normal file
15
cmd/config/testdata/TestConfigGoldenOutput/config-cmd-with-help.golden
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
Modify airshipctl config files using subcommands
|
||||
like "airshipctl config set-current-context my-context"
|
||||
|
||||
Usage:
|
||||
config [command]
|
||||
|
||||
Available Commands:
|
||||
get-cluster Display a specific cluster
|
||||
help Help about any command
|
||||
set-cluster Sets a cluster entry in the airshipctl config
|
||||
|
||||
Flags:
|
||||
-h, --help help for config
|
||||
|
||||
Use "config [command] --help" for more information about a command.
|
16
cmd/root.go
16
cmd/root.go
@ -2,11 +2,11 @@ package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
argo "github.com/argoproj/argo/cmd/argo/commands"
|
||||
//argo "github.com/argoproj/argo/cmd/argo/commands"
|
||||
"github.com/spf13/cobra"
|
||||
kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/cmd"
|
||||
|
||||
//kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/cmd"
|
||||
kubectl "k8s.io/kubernetes/pkg/kubectl/cmd"
|
||||
|
||||
// Import to initialize client auth plugins.
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"opendev.org/airship/airshipctl/cmd/bootstrap"
|
||||
"opendev.org/airship/airshipctl/cmd/cluster"
|
||||
"opendev.org/airship/airshipctl/cmd/completion"
|
||||
"opendev.org/airship/airshipctl/cmd/config"
|
||||
"opendev.org/airship/airshipctl/cmd/document"
|
||||
"opendev.org/airship/airshipctl/pkg/environment"
|
||||
"opendev.org/airship/airshipctl/pkg/log"
|
||||
@ -37,12 +38,15 @@ func NewRootCmd(out io.Writer) (*cobra.Command, *environment.AirshipCTLSettings,
|
||||
SilenceUsage: true,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
log.Init(settings.Debug, cmd.OutOrStderr())
|
||||
|
||||
},
|
||||
}
|
||||
rootCmd.SetOutput(out)
|
||||
rootCmd.AddCommand(NewVersionCommand())
|
||||
|
||||
settings.InitFlags(rootCmd)
|
||||
// Load or Initialize airship Config
|
||||
settings.InitConfig()
|
||||
|
||||
return rootCmd, settings, nil
|
||||
}
|
||||
@ -50,14 +54,16 @@ func NewRootCmd(out io.Writer) (*cobra.Command, *environment.AirshipCTLSettings,
|
||||
// AddDefaultAirshipCTLCommands is a convenience function for adding all of the
|
||||
// default commands to airshipctl
|
||||
func AddDefaultAirshipCTLCommands(cmd *cobra.Command, settings *environment.AirshipCTLSettings) *cobra.Command {
|
||||
cmd.AddCommand(argo.NewCommand())
|
||||
//cmd.AddCommand(argo.NewCommand())
|
||||
cmd.AddCommand(bootstrap.NewBootstrapCommand(settings))
|
||||
cmd.AddCommand(cluster.NewClusterCommand(settings))
|
||||
cmd.AddCommand(completion.NewCompletionCommand())
|
||||
cmd.AddCommand(document.NewDocumentCommand(settings))
|
||||
cmd.AddCommand(config.NewConfigCommand(settings))
|
||||
|
||||
cmd.AddCommand(kubectl.NewDefaultKubectlCommand())
|
||||
// Should we use cmd.OutOrStdout?
|
||||
cmd.AddCommand(kubeadm.NewKubeadmCommand(os.Stdin, os.Stdout, os.Stderr))
|
||||
//cmd.AddCommand(kubeadm.NewKubeadmCommand(os.Stdin, os.Stdout, os.Stderr))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
@ -4,18 +4,19 @@ Usage:
|
||||
airshipctl [command]
|
||||
|
||||
Available Commands:
|
||||
argo argo is the command line interface to Argo
|
||||
bootstrap Bootstrap ephemeral Kubernetes cluster
|
||||
completion Generate autocompletions script for the specified shell (bash or zsh)
|
||||
config Modify airshipctl config files
|
||||
document manages deployment documents
|
||||
help Help about any command
|
||||
kubeadm kubeadm: easily bootstrap a secure Kubernetes cluster
|
||||
kubectl kubectl controls the Kubernetes cluster manager
|
||||
version Show the version number of airshipctl
|
||||
|
||||
Flags:
|
||||
--airshipconf string Path to file for airshipctl configuration. (default "$HOME/.airship/config")
|
||||
--debug enable verbose output
|
||||
-h, --help help for airshipctl
|
||||
--kubeconfig string Path to kubeconfig associated with airshipctl configuration. (default "$HOME/.airship/kubeconfig")
|
||||
|
||||
Additional help topics:
|
||||
airshipctl cluster Control kubernetes cluster
|
||||
|
@ -8,7 +8,9 @@ Available Commands:
|
||||
version Show the version number of airshipctl
|
||||
|
||||
Flags:
|
||||
--airshipconf string Path to file for airshipctl configuration. (default "$HOME/.airship/config")
|
||||
--debug enable verbose output
|
||||
-h, --help help for airshipctl
|
||||
--kubeconfig string Path to kubeconfig associated with airshipctl configuration. (default "$HOME/.airship/kubeconfig")
|
||||
|
||||
Use "airshipctl [command] --help" for more information about a command.
|
||||
|
@ -9,7 +9,9 @@ Available Commands:
|
||||
version Show the version number of airshipctl
|
||||
|
||||
Flags:
|
||||
--airshipconf string Path to file for airshipctl configuration. (default "$HOME/.airship/config")
|
||||
--debug enable verbose output
|
||||
-h, --help help for airshipctl
|
||||
--kubeconfig string Path to kubeconfig associated with airshipctl configuration. (default "$HOME/.airship/kubeconfig")
|
||||
|
||||
Use "airshipctl [command] --help" for more information about a command.
|
||||
|
1
go.mod
1
go.mod
@ -35,6 +35,7 @@ require (
|
||||
github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995 // indirect
|
||||
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e // indirect
|
||||
github.com/google/btree v1.0.0 // indirect
|
||||
github.com/google/go-cmp v0.2.0
|
||||
github.com/gophercloud/gophercloud v0.1.0 // indirect
|
||||
github.com/gorilla/mux v1.7.2 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
|
||||
|
49
pkg/config/cmds.go
Normal file
49
pkg/config/cmds.go
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// Validate that the arguments are correct
|
||||
func (o *ClusterOptions) Validate() error {
|
||||
if len(o.Name) == 0 {
|
||||
return errors.New("you must specify a non-empty cluster name")
|
||||
}
|
||||
err := ValidClusterType(o.ClusterType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.InsecureSkipTLSVerify && o.CertificateAuthority != "" {
|
||||
return fmt.Errorf("you cannot specify a %s and %s mode at the same time", FlagCAFile, FlagInsecure)
|
||||
}
|
||||
|
||||
if !o.EmbedCAData {
|
||||
return nil
|
||||
}
|
||||
caPath := o.CertificateAuthority
|
||||
if caPath == "" {
|
||||
return fmt.Errorf("you must specify a --%s to embed", FlagCAFile)
|
||||
}
|
||||
if _, err := ioutil.ReadFile(caPath); err != nil {
|
||||
return fmt.Errorf("could not read %s data from %s: %v", FlagCAFile, caPath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
59
pkg/config/cmds_test.go
Normal file
59
pkg/config/cmds_test.go
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
co := DummyClusterOptions()
|
||||
// Valid Data case
|
||||
err := co.Validate()
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Validate with Embedded Data
|
||||
// Empty CA
|
||||
co.EmbedCAData = true
|
||||
co.CertificateAuthority = ""
|
||||
err = co.Validate()
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// Lets add a CA
|
||||
co.CertificateAuthority = "testdata/ca.crt"
|
||||
err = co.Validate()
|
||||
assert.Nil(t, err)
|
||||
// Lets add a CA but garbage
|
||||
co.CertificateAuthority = "garbage"
|
||||
err = co.Validate()
|
||||
assert.NotNil(t, err)
|
||||
// Lets change the Insecure mode
|
||||
co.InsecureSkipTLSVerify = true
|
||||
err = co.Validate()
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// Invalid Cluter Type
|
||||
co.ClusterType = "Invalid"
|
||||
err = co.Validate()
|
||||
assert.NotNil(t, err)
|
||||
// Empty Cluster Name case
|
||||
co.Name = ""
|
||||
err = co.Validate()
|
||||
assert.NotNil(t, err)
|
||||
}
|
10
pkg/config/cmds_types.go
Normal file
10
pkg/config/cmds_types.go
Normal file
@ -0,0 +1,10 @@
|
||||
package config
|
||||
|
||||
type ClusterOptions struct {
|
||||
Name string
|
||||
ClusterType string
|
||||
Server string
|
||||
InsecureSkipTLSVerify bool
|
||||
CertificateAuthority string
|
||||
EmbedCAData bool
|
||||
}
|
691
pkg/config/config.go
Normal file
691
pkg/config/config.go
Normal file
@ -0,0 +1,691 @@
|
||||
/*
|
||||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
kubeconfig "k8s.io/client-go/tools/clientcmd/api"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/util"
|
||||
)
|
||||
|
||||
// Called from root to Load the initial configuration
|
||||
func (c *Config) LoadConfig(configFileArg string, kPathOptions *clientcmd.PathOptions) error {
|
||||
|
||||
err := c.loadFromAirConfig(configFileArg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load or initialize the kubeconfig object from a file
|
||||
err = c.loadKubeConfig(kPathOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Lets navigate through the kConfig to populate the references in airship config
|
||||
return c.reconcileConfig()
|
||||
|
||||
}
|
||||
|
||||
func (c *Config) loadFromAirConfig(configFileArg string) error {
|
||||
// If it exists, Read the ConfigFile data
|
||||
// Only care about the errors here, because there is a file
|
||||
// And essentially I cannot use its data.
|
||||
// airshipctl probable should stop
|
||||
if configFileArg == "" {
|
||||
return errors.New("Configuration file location was not provided.")
|
||||
}
|
||||
// Remember where I loaded the Config from
|
||||
c.loadedConfigPath = configFileArg
|
||||
// If I have a file to read, load from it
|
||||
|
||||
if _, err := os.Stat(configFileArg); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return util.ReadYAMLFile(configFileArg, c)
|
||||
}
|
||||
|
||||
func (c *Config) loadKubeConfig(kPathOptions *clientcmd.PathOptions) error {
|
||||
// Will need this for Persisting the changes
|
||||
c.loadedPathOptions = kPathOptions
|
||||
// Now at this point what I load might not reflect the associated kubeconfig yet
|
||||
kConfig, err := kPathOptions.GetStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Store the kubeconfig object into an airship managed kubeconfig object
|
||||
c.kubeConfig = kConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// reconcileConfig serves two functions:
|
||||
// 1 - it will consume from kubeconfig and update airship config
|
||||
// For cluster that do not comply with the airship cluster type expectations a default
|
||||
// behavior will be implemented. Such as ,by default they will be tar or ephemeral
|
||||
// 2 - it will update kubeconfig cluster objects with the appropriate <clustername>_<clustertype> convention
|
||||
func (c *Config) reconcileConfig() error {
|
||||
updatedClusterNames, persistIt := c.reconcileClusters()
|
||||
c.reconcileContexts(updatedClusterNames)
|
||||
c.reconcileAuthInfos()
|
||||
c.reconcileCurrentContext()
|
||||
|
||||
// I changed things during the reconciliation
|
||||
// Lets reflect them in the config files
|
||||
// Specially useful if the cnofig is loaded during a get operation
|
||||
// If it was a Set this would have happened eventually any way
|
||||
if persistIt {
|
||||
return c.PersistConfig()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) reconcileClusters() (map[string]*ClusterComplexName, bool) {
|
||||
updatedClusters := make(map[string]*kubeconfig.Cluster)
|
||||
updatedClusterNames := make(map[string]*ClusterComplexName)
|
||||
persistIt := false
|
||||
for key, cluster := range c.kubeConfig.Clusters {
|
||||
|
||||
clusterComplexName := NewClusterComplexName()
|
||||
clusterComplexName.FromName(key)
|
||||
// Lets check if the cluster from the kubeconfig file complies with the complex naming convention
|
||||
if !clusterComplexName.validName() {
|
||||
clusterComplexName.SetDefaultType()
|
||||
// Lets update the kubeconfig with proper airship name
|
||||
updatedClusters[clusterComplexName.Name()] = cluster
|
||||
|
||||
// Remember name changes since Contexts has to be updated as well for this clusters
|
||||
updatedClusterNames[key] = clusterComplexName
|
||||
persistIt = true
|
||||
|
||||
if c.kubeConfig.Clusters[key] == nil {
|
||||
c.kubeConfig.Clusters[key] = updatedClusters[key]
|
||||
}
|
||||
// Otherwise this is a cluster that didnt have an airship cluster type, however when you added the cluster type
|
||||
// Probable should just add a number _<COUNTER to it
|
||||
}
|
||||
|
||||
// The cluster name is good at this point
|
||||
// Lets update the airship config file updated
|
||||
if c.Clusters[clusterComplexName.ClusterName()] == nil {
|
||||
c.Clusters[clusterComplexName.ClusterName()] = NewClusterPurpose()
|
||||
}
|
||||
if c.Clusters[clusterComplexName.ClusterName()].ClusterTypes[clusterComplexName.ClusterType()] == nil {
|
||||
c.Clusters[clusterComplexName.ClusterName()].ClusterTypes[clusterComplexName.ClusterType()] = NewCluster()
|
||||
}
|
||||
configCluster := c.Clusters[clusterComplexName.ClusterName()].ClusterTypes[clusterComplexName.ClusterType()]
|
||||
if configCluster.NameInKubeconf != clusterComplexName.Name() {
|
||||
configCluster.NameInKubeconf = clusterComplexName.Name()
|
||||
// TODO What do we do with the BOOTSTRAP CONFIG
|
||||
}
|
||||
// Store the reference to the KubeConfig Cluster in the Airship Config
|
||||
configCluster.SetKubeCluster(cluster)
|
||||
|
||||
// Done updating
|
||||
// Lets remove anything that was updated
|
||||
if updatedClusterNames[key] != nil {
|
||||
delete(c.kubeConfig.Clusters, key)
|
||||
}
|
||||
}
|
||||
|
||||
persistIt = c.rmConfigClusterStragglers(persistIt)
|
||||
|
||||
return updatedClusterNames, persistIt
|
||||
|
||||
}
|
||||
|
||||
// Removes or Deletes Cluster configuration that exist in Airship Config
|
||||
// and do not have any kubeconfig appropriate <clustername>_<clustertype>
|
||||
// entries
|
||||
func (c *Config) rmConfigClusterStragglers(persistIt bool) bool {
|
||||
rccs := persistIt
|
||||
// Checking if there is any Cluster reference in airship config that does not match
|
||||
// an actual Cluster struct in kubeconfig
|
||||
for key := range c.Clusters {
|
||||
for cType, cluster := range c.Clusters[key].ClusterTypes {
|
||||
if c.kubeConfig.Clusters[cluster.NameInKubeconf] == nil {
|
||||
// Instead of removing it , I could add a empty entry in kubeconfig as well
|
||||
// Will see what is more appropriae with use of Modules configuration
|
||||
delete(c.Clusters[key].ClusterTypes, cType)
|
||||
rccs = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return rccs
|
||||
}
|
||||
func (c *Config) reconcileContexts(updatedClusterNames map[string]*ClusterComplexName) {
|
||||
for key, context := range c.kubeConfig.Contexts {
|
||||
// Check if the Cluster name referred to by the context
|
||||
// was updated during the cluster reconcile
|
||||
if updatedClusterNames[context.Cluster] != nil {
|
||||
context.Cluster = updatedClusterNames[context.Cluster].Name()
|
||||
}
|
||||
|
||||
if c.Contexts[key] == nil {
|
||||
c.Contexts[key] = NewContext()
|
||||
}
|
||||
// Make sure the name matches
|
||||
c.Contexts[key].NameInKubeconf = context.Cluster
|
||||
|
||||
// What about if a Context refers to a properly named cluster
|
||||
// that does not exist in airship config
|
||||
clusterName := NewClusterComplexName()
|
||||
clusterName.FromName(context.Cluster)
|
||||
if clusterName.validName() && c.Clusters[clusterName.ClusterName()] == nil {
|
||||
// I cannot create this cluster, it will have empty information
|
||||
// Best course of action is to delete it I think
|
||||
delete(c.kubeConfig.Contexts, key)
|
||||
}
|
||||
}
|
||||
// Checking if there is any Context reference in airship config that does not match
|
||||
// an actual Context struct in kubeconfig, if they do not exists I will delete
|
||||
// Since context in airship config are only references mainly.
|
||||
for key := range c.Contexts {
|
||||
if c.kubeConfig.Contexts[key] == nil {
|
||||
delete(c.Contexts, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) reconcileAuthInfos() {
|
||||
for key, authinfo := range c.kubeConfig.AuthInfos {
|
||||
// Simple check if the AuthInfo name is referenced in airship config
|
||||
if c.AuthInfos[key] == nil && authinfo != nil {
|
||||
// Add the reference
|
||||
c.AuthInfos[key] = NewAuthInfo()
|
||||
|
||||
}
|
||||
}
|
||||
// Checking if there is any AuthInfo reference in airship config that does not match
|
||||
// an actual Auth Info struct in kubeconfig
|
||||
for key := range c.AuthInfos {
|
||||
if c.kubeConfig.AuthInfos[key] == nil {
|
||||
delete(c.AuthInfos, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) reconcileCurrentContext() {
|
||||
// If the Airship current context is different that the current context in the kubeconfig
|
||||
// then
|
||||
// - if the airship current context is valid, then updated kubeconfiug CC
|
||||
// - if the airship currentcontext is invalid, and the kubeconfig CC is valid, then create the reference
|
||||
// - otherwise , they are both empty. Make sure
|
||||
|
||||
if c.Contexts[c.CurrentContext] == nil { // Its not valid
|
||||
if c.Contexts[c.kubeConfig.CurrentContext] != nil {
|
||||
c.CurrentContext = c.kubeConfig.CurrentContext
|
||||
}
|
||||
} else {
|
||||
// Overpowers kubeConfig CurrentContext
|
||||
if c.kubeConfig.CurrentContext != c.CurrentContext {
|
||||
c.kubeConfig.CurrentContext = c.CurrentContext
|
||||
}
|
||||
}
|
||||
c.kubeConfig.CurrentContext = ""
|
||||
c.CurrentContext = ""
|
||||
}
|
||||
|
||||
// This is called by users of the config to make sure that they have
|
||||
// A complete configuration before they try to use it.
|
||||
// What is a Complete configuration:
|
||||
// Should be :
|
||||
// At least 1 cluster defined
|
||||
// At least 1 authinfo (user) defined
|
||||
// At least 1 context defined
|
||||
// The current context properly associated with an existsing context
|
||||
// At least one Manifest defined
|
||||
//
|
||||
func (c *Config) EnsureComplete() error {
|
||||
if len(c.Clusters) == 0 {
|
||||
return errors.New("Config: At least one cluster needs to be defined")
|
||||
}
|
||||
if len(c.AuthInfos) == 0 {
|
||||
return errors.New("Config: At least one Authentication Information (User) needs to be defined")
|
||||
}
|
||||
|
||||
if len(c.Contexts) == 0 {
|
||||
return errors.New("Config: At least one Context needs to be defined")
|
||||
}
|
||||
|
||||
if c.CurrentContext == "" || c.Contexts[c.CurrentContext] == nil {
|
||||
return errors.New("Config: Current Context is not defined, or it doesnt identify a defined Context")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// This function is called to update the configuration in the file defined by the
|
||||
// ConfigFile name
|
||||
// It will completely overwrite the existing file,
|
||||
// If the file specified by ConfigFile exists ts updates with the contents of the Config object
|
||||
// If the file specified by ConfigFile does not exist it will create a new file.
|
||||
func (c *Config) PersistConfig() error {
|
||||
// Dont care if the file exists or not, will create if needed
|
||||
// We are 100% overwriting the existsing file
|
||||
configyaml, err := c.ToYaml()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteFile doesn't create the directory , create it if needed
|
||||
configDir := filepath.Dir(c.loadedConfigPath)
|
||||
err = os.MkdirAll(configDir, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the Airship Config file
|
||||
err = ioutil.WriteFile(c.loadedConfigPath, configyaml, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Persist the kubeconfig file referenced
|
||||
if err := clientcmd.ModifyConfig(c.loadedPathOptions, *c.kubeConfig, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) String() string {
|
||||
yaml, err := c.ToYaml()
|
||||
// This is hiding the error perhaps
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(yaml)
|
||||
}
|
||||
|
||||
func (c *Config) ToYaml() ([]byte, error) {
|
||||
return yaml.Marshal(&c)
|
||||
}
|
||||
|
||||
func (c *Config) LoadedConfigPath() string {
|
||||
return c.loadedConfigPath
|
||||
}
|
||||
func (c *Config) SetLoadedConfigPath(lcp string) {
|
||||
c.loadedConfigPath = lcp
|
||||
}
|
||||
|
||||
func (c *Config) LoadedPathOptions() *clientcmd.PathOptions {
|
||||
return c.loadedPathOptions
|
||||
}
|
||||
func (c *Config) SetLoadedPathOptions(po *clientcmd.PathOptions) {
|
||||
c.loadedPathOptions = po
|
||||
}
|
||||
|
||||
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 {
|
||||
names = append(names, k)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names
|
||||
|
||||
}
|
||||
|
||||
// Get A Cluster
|
||||
func (c *Config) GetCluster(cName, cType string) (*Cluster, error) {
|
||||
_, exists := c.Clusters[cName]
|
||||
if !exists {
|
||||
return nil, errors.New("Cluster " + cName +
|
||||
" information was not found in the configuration.")
|
||||
}
|
||||
// Alternative to this would be enhance Cluster.String() to embedd the appropriate kubeconfig cluster information
|
||||
cluster, exists := c.Clusters[cName].ClusterTypes[cType]
|
||||
if !exists {
|
||||
return nil, errors.New("Cluster " + cName + " of type " + cType +
|
||||
" information was not found in the configuration.")
|
||||
}
|
||||
return cluster, nil
|
||||
}
|
||||
func (c *Config) AddCluster(theCluster *ClusterOptions) (*Cluster, error) {
|
||||
// Need to create new cluster placeholder
|
||||
// Get list of ClusterPurposes that match the theCluster.name
|
||||
// Cluster might exists, but ClusterPurpose should not
|
||||
_, exists := c.Clusters[theCluster.Name]
|
||||
if !exists {
|
||||
c.Clusters[theCluster.Name] = NewClusterPurpose()
|
||||
}
|
||||
// Create the new Airship config Cluster
|
||||
nCluster := NewCluster()
|
||||
c.Clusters[theCluster.Name].ClusterTypes[theCluster.ClusterType] = nCluster
|
||||
// Create a new Kubeconfig Cluster object as well
|
||||
kcluster := kubeconfig.NewCluster()
|
||||
clusterName := NewClusterComplexName()
|
||||
clusterName.WithType(theCluster.Name, theCluster.ClusterType)
|
||||
nCluster.NameInKubeconf = clusterName.Name()
|
||||
nCluster.SetKubeCluster(kcluster)
|
||||
|
||||
c.KubeConfig().Clusters[clusterName.Name()] = kcluster
|
||||
|
||||
// Ok , I have initialized structs for the Cluster information
|
||||
// We can use Modify to populate the correct information
|
||||
return c.ModifyCluster(nCluster, theCluster)
|
||||
|
||||
}
|
||||
|
||||
func (c *Config) ModifyCluster(cluster *Cluster, theCluster *ClusterOptions) (*Cluster, error) {
|
||||
kcluster := cluster.KubeCluster()
|
||||
if kcluster == nil {
|
||||
return cluster, nil
|
||||
}
|
||||
if theCluster.Server != "" {
|
||||
kcluster.Server = theCluster.Server
|
||||
}
|
||||
if theCluster.InsecureSkipTLSVerify {
|
||||
kcluster.InsecureSkipTLSVerify = theCluster.InsecureSkipTLSVerify
|
||||
// Specifying insecur mode clears any certificate authority
|
||||
if kcluster.InsecureSkipTLSVerify {
|
||||
kcluster.CertificateAuthority = ""
|
||||
kcluster.CertificateAuthorityData = nil
|
||||
}
|
||||
}
|
||||
if theCluster.CertificateAuthority == "" {
|
||||
return cluster, nil
|
||||
}
|
||||
|
||||
if theCluster.EmbedCAData {
|
||||
readData, err := ioutil.ReadFile(theCluster.CertificateAuthority)
|
||||
kcluster.CertificateAuthorityData = readData
|
||||
if err != nil {
|
||||
return cluster, err
|
||||
}
|
||||
kcluster.InsecureSkipTLSVerify = false
|
||||
kcluster.CertificateAuthority = ""
|
||||
} else {
|
||||
caPath, err := filepath.Abs(theCluster.CertificateAuthority)
|
||||
if err != nil {
|
||||
return cluster, err
|
||||
}
|
||||
kcluster.CertificateAuthority = caPath
|
||||
// Specifying a certificate authority file clears certificate authority data and insecure mode
|
||||
if caPath != "" {
|
||||
kcluster.InsecureSkipTLSVerify = false
|
||||
kcluster.CertificateAuthorityData = nil
|
||||
}
|
||||
}
|
||||
return cluster, nil
|
||||
|
||||
}
|
||||
func (c *Config) GetClusters() ([]*Cluster, error) {
|
||||
clusters := []*Cluster{}
|
||||
for _, cName := range c.ClusterNames() {
|
||||
for _, ctName := range AllClusterTypes {
|
||||
cluster, err := c.GetCluster(cName, ctName)
|
||||
// Err simple means something that does not exists
|
||||
// Which is possible since I am iterating thorugh both possible
|
||||
// cluster types
|
||||
if err == nil {
|
||||
clusters = append(clusters, cluster)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return clusters, nil
|
||||
}
|
||||
|
||||
// CurrentConfig 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 clustertye for this context
|
||||
// AuthInfo is the name of the authInfo for this context
|
||||
// Manifest is the default manifest to be use with this context
|
||||
// Namespace is the default namespace to use on unspecified requests
|
||||
// Purpose for this method is simplifying ting the current context information
|
||||
/*
|
||||
func (c *Config) CurrentContext() (*Context, *Cluster, *AuthInfo, *Manifest, error) {
|
||||
if err := c.EnsureComplete(); err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
currentContext := c.Contexts[c.CurrentContext]
|
||||
if currentContext == nil {
|
||||
// this should not happened
|
||||
return nil, nil, nil, nil,
|
||||
errors.New("CurrentContext was unable to find the configured current context.")
|
||||
}
|
||||
return currentContext,
|
||||
c.Clusters[currentContext.Cluster].ClusterTypes[currentContext.ClusterType],
|
||||
c.AuthInfos[currentContext.AuthInfo],
|
||||
c.Manifests[currentContext.Manifest],
|
||||
nil
|
||||
}
|
||||
*/
|
||||
|
||||
// Purge removes the config file
|
||||
func (c *Config) Purge() error {
|
||||
//configFile := c.ConfigFile()
|
||||
err := os.Remove(c.loadedConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) Equal(d *Config) bool {
|
||||
if d == nil {
|
||||
return false
|
||||
}
|
||||
clusterEq := reflect.DeepEqual(c.Clusters, d.Clusters)
|
||||
authInfoEq := reflect.DeepEqual(c.AuthInfos, d.AuthInfos)
|
||||
contextEq := reflect.DeepEqual(c.Contexts, d.Contexts)
|
||||
manifestEq := reflect.DeepEqual(c.Manifests, d.Manifests)
|
||||
return c.Kind == d.Kind &&
|
||||
c.APIVersion == d.APIVersion &&
|
||||
clusterEq && authInfoEq && contextEq && manifestEq &&
|
||||
c.ModulesConfig.Equal(d.ModulesConfig)
|
||||
}
|
||||
|
||||
// Cluster functions
|
||||
func (c *Cluster) Equal(d *Cluster) bool {
|
||||
if d == nil {
|
||||
return false
|
||||
}
|
||||
return c.NameInKubeconf == d.NameInKubeconf &&
|
||||
c.Bootstrap == d.Bootstrap
|
||||
}
|
||||
|
||||
func (c *Cluster) String() string {
|
||||
cyaml, err := yaml.Marshal(&c)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
kcluster := c.KubeCluster()
|
||||
kyaml, err := yaml.Marshal(&kcluster)
|
||||
if err != nil {
|
||||
return string(cyaml)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s\n%s", string(cyaml), string(kyaml))
|
||||
}
|
||||
|
||||
func (c *Cluster) PrettyString() string {
|
||||
clusterName := NewClusterComplexName()
|
||||
clusterName.FromName(c.NameInKubeconf)
|
||||
|
||||
return fmt.Sprintf("Cluster: %s\n%s:\n%s\n",
|
||||
clusterName.ClusterName(), clusterName.ClusterType(), c.String())
|
||||
}
|
||||
|
||||
func (c *Cluster) KubeCluster() *kubeconfig.Cluster {
|
||||
return c.kCluster
|
||||
}
|
||||
func (c *Cluster) SetKubeCluster(kc *kubeconfig.Cluster) {
|
||||
c.kCluster = kc
|
||||
}
|
||||
|
||||
// Context functions
|
||||
func (c *Context) Equal(d *Context) bool {
|
||||
if d == nil {
|
||||
return false
|
||||
}
|
||||
return c.NameInKubeconf == d.NameInKubeconf &&
|
||||
c.Manifest == d.Manifest
|
||||
}
|
||||
func (c *Context) String() string {
|
||||
yaml, err := yaml.Marshal(&c)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(yaml)
|
||||
}
|
||||
|
||||
// AuthInfo functions
|
||||
func (c *AuthInfo) Equal(d *AuthInfo) bool {
|
||||
if d == nil {
|
||||
return false
|
||||
}
|
||||
return c == d
|
||||
}
|
||||
|
||||
func (c *AuthInfo) String() string {
|
||||
yaml, err := yaml.Marshal(&c)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(yaml)
|
||||
}
|
||||
|
||||
// Manifest functions
|
||||
func (m *Manifest) Equal(n *Manifest) bool {
|
||||
if n == nil {
|
||||
return false
|
||||
}
|
||||
repositoryEq := reflect.DeepEqual(m.Repositories, n.Repositories)
|
||||
return repositoryEq && m.TargetPath == n.TargetPath
|
||||
}
|
||||
func (m *Manifest) String() string {
|
||||
yaml, err := yaml.Marshal(&m)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(yaml)
|
||||
}
|
||||
|
||||
// Repository functions
|
||||
func (r *Repository) Equal(s *Repository) bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
url := (r.Url == nil && s.Url == nil) ||
|
||||
(r.Url != nil && s.Url != nil && r.Url.String() == s.Url.String())
|
||||
return url &&
|
||||
r.Username == s.Username &&
|
||||
r.TargetPath == s.TargetPath
|
||||
}
|
||||
func (r *Repository) String() string {
|
||||
yaml, err := yaml.Marshal(&r)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(yaml)
|
||||
}
|
||||
|
||||
// Modules functions
|
||||
func (m *Modules) Equal(n *Modules) bool {
|
||||
|
||||
return n != nil && m.Dummy == n.Dummy
|
||||
}
|
||||
func (m *Modules) String() string {
|
||||
yaml, err := yaml.Marshal(&m)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(yaml)
|
||||
}
|
||||
|
||||
// ClusterComplexName functions
|
||||
func (c *ClusterComplexName) validName() bool {
|
||||
err := ValidClusterType(c.clusterType)
|
||||
return c.clusterName != "" && err == nil
|
||||
}
|
||||
func (c *ClusterComplexName) FromName(clusterName string) {
|
||||
if clusterName != "" {
|
||||
userNameSplit := strings.Split(clusterName, AirshipClusterNameSep)
|
||||
if len(userNameSplit) == 2 {
|
||||
c.clusterType = userNameSplit[1]
|
||||
}
|
||||
c.clusterName = userNameSplit[0]
|
||||
}
|
||||
}
|
||||
func (c *ClusterComplexName) WithType(clusterName string, clusterType string) {
|
||||
c.FromName(clusterName)
|
||||
c.SetClusterType(clusterType)
|
||||
}
|
||||
func (c *ClusterComplexName) Name() string {
|
||||
s := []string{c.clusterName, c.clusterType}
|
||||
return strings.Join(s, AirshipClusterNameSep)
|
||||
}
|
||||
func (c *ClusterComplexName) ClusterName() string {
|
||||
return c.clusterName
|
||||
}
|
||||
|
||||
func (c *ClusterComplexName) ClusterType() string {
|
||||
return c.clusterType
|
||||
}
|
||||
func (c *ClusterComplexName) SetClusterName(cn string) {
|
||||
c.clusterName = cn
|
||||
}
|
||||
|
||||
func (c *ClusterComplexName) SetClusterType(ct string) {
|
||||
c.clusterType = ct
|
||||
}
|
||||
func (c *ClusterComplexName) SetDefaultType() {
|
||||
c.SetClusterType(AirshipClusterDefaultType)
|
||||
}
|
||||
func (c *ClusterComplexName) String() string {
|
||||
return fmt.Sprintf("clusterName:%s, clusterType:%s", c.clusterName, c.clusterType)
|
||||
}
|
||||
func ValidClusterType(ctype string) error {
|
||||
if ctype == Ephemeral || ctype == Target {
|
||||
return nil
|
||||
}
|
||||
return errors.New("Cluster Type must be specified. Valid values are :" + Ephemeral + " or " + Target + ".")
|
||||
}
|
||||
|
||||
/* ______________________________
|
||||
PLACEHOLDER UNTIL I IDENTIFY if CLIENTADM
|
||||
HAS SOMETHING LIKE THIS
|
||||
*/
|
||||
|
||||
func KClusterString(kCluster *kubeconfig.Cluster) string {
|
||||
yaml, err := yaml.Marshal(&kCluster)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(yaml)
|
||||
}
|
324
pkg/config/config_test.go
Normal file
324
pkg/config/config_test.go
Normal file
@ -0,0 +1,324 @@
|
||||
/*
|
||||
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 (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
// Testing related constants
|
||||
|
||||
var AirshipStructs = [...]reflect.Value{
|
||||
reflect.ValueOf(DummyConfig()),
|
||||
reflect.ValueOf(DummyCluster()),
|
||||
reflect.ValueOf(DummyContext()),
|
||||
reflect.ValueOf(DummyManifest()),
|
||||
reflect.ValueOf(DummyAuthInfo()),
|
||||
reflect.ValueOf(DummyRepository()),
|
||||
reflect.ValueOf(DummyModules()),
|
||||
}
|
||||
|
||||
// I can probable reflect to generate this two slices, instead based on the 1st one
|
||||
// Exercise left for later -- YES I will remove this comment in the next patchset
|
||||
var AirshipStructsEqual = [...]reflect.Value{
|
||||
reflect.ValueOf(DummyConfig()),
|
||||
reflect.ValueOf(DummyCluster()),
|
||||
reflect.ValueOf(DummyContext()),
|
||||
reflect.ValueOf(DummyManifest()),
|
||||
reflect.ValueOf(DummyAuthInfo()),
|
||||
reflect.ValueOf(DummyRepository()),
|
||||
reflect.ValueOf(DummyModules()),
|
||||
}
|
||||
|
||||
var AirshipStructsDiff = [...]reflect.Value{
|
||||
reflect.ValueOf(NewConfig()),
|
||||
reflect.ValueOf(NewCluster()),
|
||||
reflect.ValueOf(NewContext()),
|
||||
reflect.ValueOf(NewManifest()),
|
||||
reflect.ValueOf(NewAuthInfo()),
|
||||
reflect.ValueOf(NewRepository()),
|
||||
reflect.ValueOf(NewModules()),
|
||||
}
|
||||
|
||||
// Test to complete min coverage
|
||||
func TestString(t *testing.T) {
|
||||
for s := range AirshipStructs {
|
||||
airStruct := AirshipStructs[s]
|
||||
airStringMethod := airStruct.MethodByName("String")
|
||||
yaml := airStringMethod.Call([]reflect.Value{})
|
||||
require.NotNil(t, yaml)
|
||||
|
||||
structName := strings.Split(airStruct.Type().String(), ".")
|
||||
expectedFile := filepath.Join(testDataDir, "GoldenString", structName[1]+testMimeType)
|
||||
expectedData, err := ioutil.ReadFile(expectedFile)
|
||||
assert.Nil(t, err)
|
||||
require.EqualValues(t, string(expectedData), yaml[0].String())
|
||||
|
||||
}
|
||||
}
|
||||
func TestPrettyString(t *testing.T) {
|
||||
conf := InitConfig(t)
|
||||
cluster, err := conf.GetCluster("def", Ephemeral)
|
||||
require.NoError(t, err)
|
||||
expectedFile := filepath.Join(testDataDir, "GoldenString", "PrettyCluster.yaml")
|
||||
expectedData, err := ioutil.ReadFile(expectedFile)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.EqualValues(t, cluster.PrettyString(), string(expectedData))
|
||||
|
||||
}
|
||||
|
||||
func TestEqual(t *testing.T) {
|
||||
for s := range AirshipStructs {
|
||||
airStruct := AirshipStructs[s]
|
||||
airStringMethod := airStruct.MethodByName("Equal")
|
||||
args := []reflect.Value{AirshipStructsEqual[s]}
|
||||
eq := airStringMethod.Call(args)
|
||||
assert.NotNilf(t, eq, "Equal for %v failed to return response to Equal . ", airStruct.Type().String())
|
||||
require.Truef(t, eq[0].Bool(), "Equal for %v failed to return true for equal values ", airStruct.Type().String())
|
||||
|
||||
// Lets test Equals against nil struct
|
||||
args = []reflect.Value{reflect.New(airStruct.Type()).Elem()}
|
||||
nileq := airStringMethod.Call(args)
|
||||
assert.NotNil(t, nileq, "Equal for %v failed to return response to Equal . ", airStruct.Type().String())
|
||||
require.Falsef(t, nileq[0].Bool(),
|
||||
"Equal for %v failed to return false when comparing against nil value ", airStruct.Type().String())
|
||||
|
||||
// Ignore False Equals test for AuthInfo for now
|
||||
if airStruct.Type().String() == "*config.AuthInfo" {
|
||||
continue
|
||||
}
|
||||
// Lets test that equal returns false when they are diff
|
||||
args = []reflect.Value{AirshipStructsDiff[s]}
|
||||
neq := airStringMethod.Call(args)
|
||||
assert.NotNil(t, neq, "Equal for %v failed to return response to Equal . ", airStruct.Type().String())
|
||||
require.Falsef(t, neq[0].Bool(),
|
||||
"Equal for %v failed to return false for different values ", airStruct.Type().String())
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfig(t *testing.T) {
|
||||
// Shouuld have the defult in testdata
|
||||
// And copy it to the default prior to the test
|
||||
// Create from defaults using existing kubeconf
|
||||
conf := InitConfig(t)
|
||||
|
||||
require.NotEmpty(t, conf.String())
|
||||
|
||||
// Lets make sure that the contents is as expected
|
||||
// 2 Clusters
|
||||
// 2 Clusters Types
|
||||
// 2 Contexts
|
||||
// 1 User
|
||||
require.Lenf(t, conf.Clusters, 4, "Expected 4 Clusters got %d", len(conf.Clusters))
|
||||
require.Lenf(t, conf.Clusters["def"].ClusterTypes, 2,
|
||||
"Expected 2 ClusterTypes got %d", len(conf.Clusters["def"].ClusterTypes))
|
||||
require.Len(t, conf.Contexts, 3, "Expected 3 Contexts got %d", len(conf.Contexts))
|
||||
require.Len(t, conf.AuthInfos, 2, "Expected 2 AuthInfo got %d", len(conf.AuthInfos))
|
||||
|
||||
}
|
||||
|
||||
func TestPersistConfig(t *testing.T) {
|
||||
config := InitConfig(t)
|
||||
airConfigFile := filepath.Join(testAirshipConfigDir, AirshipConfig)
|
||||
kConfigFile := filepath.Join(testAirshipConfigDir, AirshipKubeConfig)
|
||||
config.SetLoadedConfigPath(airConfigFile)
|
||||
kubePathOptions := clientcmd.NewDefaultPathOptions()
|
||||
kubePathOptions.GlobalFile = kConfigFile
|
||||
config.SetLoadedPathOptions(kubePathOptions)
|
||||
|
||||
err := config.PersistConfig()
|
||||
assert.Nilf(t, err, "Unable to persist configuration expected at %v ", config.LoadedConfigPath())
|
||||
|
||||
kpo := config.LoadedPathOptions()
|
||||
assert.NotNil(t, kpo)
|
||||
Clean(config)
|
||||
}
|
||||
|
||||
func TestPersistConfigFail(t *testing.T) {
|
||||
config := InitConfig(t)
|
||||
airConfigFile := filepath.Join(testAirshipConfigDir, "\\")
|
||||
kConfigFile := filepath.Join(testAirshipConfigDir, "\\")
|
||||
config.SetLoadedConfigPath(airConfigFile)
|
||||
kubePathOptions := clientcmd.NewDefaultPathOptions()
|
||||
kubePathOptions.GlobalFile = kConfigFile
|
||||
config.SetLoadedPathOptions(kubePathOptions)
|
||||
|
||||
err := config.PersistConfig()
|
||||
require.NotNilf(t, err, "Able to persist configuration at %v expected an error", config.LoadedConfigPath())
|
||||
Clean(config)
|
||||
}
|
||||
|
||||
func TestEnsureComplete(t *testing.T) {
|
||||
conf := InitConfig(t)
|
||||
|
||||
err := conf.EnsureComplete()
|
||||
require.NotNilf(t, err, "Configuration was incomplete %v ", err.Error())
|
||||
|
||||
// Trgger Contexts Error
|
||||
for key := range conf.Contexts {
|
||||
delete(conf.Contexts, key)
|
||||
}
|
||||
err = conf.EnsureComplete()
|
||||
assert.EqualValues(t, err.Error(), "Config: At least one Context needs to be defined")
|
||||
|
||||
// Trigger Authentication Information
|
||||
for key := range conf.AuthInfos {
|
||||
delete(conf.AuthInfos, key)
|
||||
}
|
||||
err = conf.EnsureComplete()
|
||||
assert.EqualValues(t, err.Error(), "Config: At least one Authentication Information (User) needs to be defined")
|
||||
|
||||
conf = NewConfig()
|
||||
err = conf.EnsureComplete()
|
||||
assert.NotNilf(t, err, "Configuration was found complete incorrectly")
|
||||
}
|
||||
|
||||
func TestPurge(t *testing.T) {
|
||||
config := InitConfig(t)
|
||||
airConfigFile := filepath.Join(testAirshipConfigDir, AirshipConfig)
|
||||
kConfigFile := filepath.Join(testAirshipConfigDir, AirshipKubeConfig)
|
||||
config.SetLoadedConfigPath(airConfigFile)
|
||||
kubePathOptions := clientcmd.NewDefaultPathOptions()
|
||||
kubePathOptions.GlobalFile = kConfigFile
|
||||
config.SetLoadedPathOptions(kubePathOptions)
|
||||
|
||||
// Store it
|
||||
err := config.PersistConfig()
|
||||
assert.Nilf(t, err, "Unable to persist configuration expected at %v [%v] ",
|
||||
config.LoadedConfigPath(), err)
|
||||
|
||||
// Verify that the file is there
|
||||
|
||||
_, err = os.Stat(config.LoadedConfigPath())
|
||||
assert.Falsef(t, os.IsNotExist(err), "Test config was not persisted at %v , cannot validate Purge [%v] ",
|
||||
config.LoadedConfigPath(), err)
|
||||
|
||||
// Delete it
|
||||
err = config.Purge()
|
||||
assert.Nilf(t, err, "Unable to Purge file at %v [%v] ", config.LoadedConfigPath(), err)
|
||||
|
||||
// Verify its gone
|
||||
_, err = os.Stat(config.LoadedConfigPath())
|
||||
require.Falsef(t, os.IsExist(err), "Purge failed to remove file at %v [%v] ",
|
||||
config.LoadedConfigPath(), err)
|
||||
|
||||
Clean(config)
|
||||
}
|
||||
|
||||
func TestClusterNames(t *testing.T) {
|
||||
conf := InitConfig(t)
|
||||
expected := []string{"def", "onlyinkubeconf", "wrongonlyinconfig", "wrongonlyinkubeconf"}
|
||||
require.EqualValues(t, expected, conf.ClusterNames())
|
||||
}
|
||||
func TestKClusterString(t *testing.T) {
|
||||
conf := InitConfig(t)
|
||||
kClusters := conf.KubeConfig().Clusters
|
||||
for kClust := range kClusters {
|
||||
require.NotNil(t, KClusterString(kClusters[kClust]))
|
||||
}
|
||||
require.EqualValues(t, KClusterString(nil), "null\n")
|
||||
}
|
||||
func TestComplexName(t *testing.T) {
|
||||
cName := "aCluster"
|
||||
ctName := Ephemeral
|
||||
clusterName := NewClusterComplexName()
|
||||
clusterName.WithType(cName, ctName)
|
||||
require.EqualValues(t, cName+"_"+ctName, clusterName.Name())
|
||||
|
||||
require.EqualValues(t, cName, clusterName.ClusterName())
|
||||
require.EqualValues(t, ctName, clusterName.ClusterType())
|
||||
|
||||
cName = "bCluster"
|
||||
clusterName.SetClusterName(cName)
|
||||
clusterName.SetDefaultType()
|
||||
ctName = clusterName.ClusterType()
|
||||
require.EqualValues(t, cName+"_"+ctName, clusterName.Name())
|
||||
|
||||
require.EqualValues(t, "clusterName:"+cName+", clusterType:"+ctName, clusterName.String())
|
||||
}
|
||||
|
||||
func TestValidClusterTypeFail(t *testing.T) {
|
||||
err := ValidClusterType("Fake")
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
func TestGetCluster(t *testing.T) {
|
||||
conf := InitConfig(t)
|
||||
cluster, err := conf.GetCluster("def", Ephemeral)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test Positives
|
||||
assert.EqualValues(t, cluster.NameInKubeconf, "def_ephemeral")
|
||||
assert.EqualValues(t, cluster.KubeCluster().Server, "http://5.6.7.8")
|
||||
// Test Wrong Cluster
|
||||
cluster, err = conf.GetCluster("unknown", Ephemeral)
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, cluster)
|
||||
// Test Wrong Cluster Type
|
||||
cluster, err = conf.GetCluster("def", "Unknown")
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, cluster)
|
||||
// Test Wrong Cluster Type
|
||||
|
||||
}
|
||||
func TestAddCluster(t *testing.T) {
|
||||
co := DummyClusterOptions()
|
||||
conf := InitConfig(t)
|
||||
cluster, err := conf.AddCluster(co)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cluster)
|
||||
assert.EqualValues(t, conf.Clusters[co.Name].ClusterTypes[co.ClusterType], cluster)
|
||||
}
|
||||
func TestModifyluster(t *testing.T) {
|
||||
co := DummyClusterOptions()
|
||||
conf := InitConfig(t)
|
||||
cluster, err := conf.AddCluster(co)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cluster)
|
||||
|
||||
co.Server += "/changes"
|
||||
co.InsecureSkipTLSVerify = true
|
||||
co.EmbedCAData = true
|
||||
mcluster, err := conf.ModifyCluster(cluster, co)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, conf.Clusters[co.Name].ClusterTypes[co.ClusterType].KubeCluster().Server, co.Server)
|
||||
assert.EqualValues(t, conf.Clusters[co.Name].ClusterTypes[co.ClusterType], mcluster)
|
||||
|
||||
// Error case
|
||||
co.CertificateAuthority = "unknown"
|
||||
_, err = conf.ModifyCluster(cluster, co)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestGetClusters(t *testing.T) {
|
||||
conf := InitConfig(t)
|
||||
clusters, err := conf.GetClusters()
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 4, len(clusters))
|
||||
|
||||
}
|
50
pkg/config/constants.go
Normal file
50
pkg/config/constants.go
Normal file
@ -0,0 +1,50 @@
|
||||
package config
|
||||
|
||||
// OutputFormat denotes the form with which to display tabulated data
|
||||
type OutputFormat string
|
||||
|
||||
// Constants related to the ClusterType type
|
||||
const (
|
||||
Ephemeral = "ephemeral"
|
||||
Target = "target"
|
||||
AirshipClusterNameSep = "_"
|
||||
AirshipClusterDefaultType = Target
|
||||
)
|
||||
|
||||
//Sorted
|
||||
var AllClusterTypes = [2]string{Ephemeral, Target}
|
||||
|
||||
// Constants defining default values
|
||||
const (
|
||||
AirshipConfigEnv = "airshipconf"
|
||||
AirshipConfig = "config"
|
||||
AirshipConfigDir = ".airship"
|
||||
AirshipConfigKind = "Config"
|
||||
AirshipConfigVersion = "v1alpha1"
|
||||
AirshipConfigGroup = "airshipit.org"
|
||||
AirshipConfigApiVersion = AirshipConfigGroup + "/" + AirshipConfigVersion
|
||||
AirshipKubeConfig = "kubeconfig"
|
||||
)
|
||||
|
||||
// Constants defining CLI flags
|
||||
const (
|
||||
FlagClusterName = "cluster"
|
||||
FlagClusterType = "cluster-type"
|
||||
FlagAuthInfoName = "user"
|
||||
FlagContext = "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"
|
||||
FlagPassword = "password"
|
||||
FlagTimeout = "request-timeout"
|
||||
FlagManifest = "manifest"
|
||||
)
|
156
pkg/config/test_utils.go
Normal file
156
pkg/config/test_utils.go
Normal file
@ -0,0 +1,156 @@
|
||||
/*
|
||||
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 (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
kubeconfig "k8s.io/client-go/tools/clientcmd/api"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
testDataDir = "../../pkg/config/testdata"
|
||||
testAirshipConfig = "testconfig"
|
||||
testAirshipConfigDir = ".testairship"
|
||||
testMimeType = ".yaml"
|
||||
)
|
||||
|
||||
// DummyConfig used by tests, to initialize min set of data
|
||||
func DummyConfig() *Config {
|
||||
conf := NewConfig()
|
||||
// Make sure the .airship directory is created
|
||||
//conf.ConfigFilePath()
|
||||
conf.Clusters["dummy_cluster"] = DummyClusterPurpose()
|
||||
conf.KubeConfig().Clusters["dummycluster_target"] = conf.Clusters["dummy_cluster"].ClusterTypes[Target].KubeCluster()
|
||||
conf.KubeConfig().Clusters["dummycluster_ephemeral"] =
|
||||
conf.Clusters["dummy_cluster"].ClusterTypes[Ephemeral].KubeCluster()
|
||||
conf.AuthInfos["dummy_user"] = DummyAuthInfo()
|
||||
conf.Contexts["dummy_context"] = DummyContext()
|
||||
conf.Manifests["dummy_manifest"] = DummyManifest()
|
||||
conf.ModulesConfig = DummyModules()
|
||||
conf.CurrentContext = "dummy_context"
|
||||
return conf
|
||||
}
|
||||
|
||||
// DummyContext , utility function used for tests
|
||||
func DummyContext() *Context {
|
||||
c := NewContext()
|
||||
c.NameInKubeconf = "dummy_cluster"
|
||||
c.Manifest = "dummy_manifest"
|
||||
return c
|
||||
}
|
||||
|
||||
// DummyCluster, utility function used for tests
|
||||
func DummyCluster() *Cluster {
|
||||
c := NewCluster()
|
||||
|
||||
cluster := kubeconfig.NewCluster()
|
||||
cluster.Server = "http://dummy.server"
|
||||
cluster.InsecureSkipTLSVerify = false
|
||||
cluster.CertificateAuthority = "dummy_ca"
|
||||
c.SetKubeCluster(cluster)
|
||||
c.NameInKubeconf = "dummycluster_target"
|
||||
c.Bootstrap = "dummy_bootstrap"
|
||||
return c
|
||||
}
|
||||
|
||||
// DummyManifest , utility function used for tests
|
||||
func DummyManifest() *Manifest {
|
||||
m := NewManifest()
|
||||
// Repositories is the map of repository adddressable by a name
|
||||
m.Repositories["dummy"] = DummyRepository()
|
||||
m.TargetPath = "/var/tmp/"
|
||||
return m
|
||||
}
|
||||
|
||||
func DummyRepository() *Repository {
|
||||
url, _ := url.Parse("http://dummy.url.com")
|
||||
return &Repository{
|
||||
Url: url,
|
||||
Username: "dummy_user",
|
||||
TargetPath: "dummy_targetpath",
|
||||
}
|
||||
}
|
||||
|
||||
func DummyAuthInfo() *AuthInfo {
|
||||
return NewAuthInfo()
|
||||
}
|
||||
|
||||
func DummyModules() *Modules {
|
||||
return &Modules{Dummy: "dummy-module"}
|
||||
}
|
||||
|
||||
// DummyClusterPurpose , utility function used for tests
|
||||
func DummyClusterPurpose() *ClusterPurpose {
|
||||
cp := NewClusterPurpose()
|
||||
cp.ClusterTypes["ephemeral"] = DummyCluster()
|
||||
cp.ClusterTypes["ephemeral"].NameInKubeconf = "dummycluster_ephemeral"
|
||||
cp.ClusterTypes["target"] = DummyCluster()
|
||||
return cp
|
||||
}
|
||||
|
||||
func InitConfigAt(t *testing.T, airConfigFile, kConfigFile string) *Config {
|
||||
conf := NewConfig()
|
||||
kubePathOptions := clientcmd.NewDefaultPathOptions()
|
||||
kubePathOptions.GlobalFile = kConfigFile
|
||||
err := conf.LoadConfig(airConfigFile, kubePathOptions)
|
||||
assert.Nil(t, err)
|
||||
return conf
|
||||
}
|
||||
func InitConfig(t *testing.T) *Config {
|
||||
airConfigFile := filepath.Join(testDataDir, AirshipConfig+testMimeType)
|
||||
kConfigFile := filepath.Join(testDataDir, AirshipKubeConfig+testMimeType)
|
||||
return InitConfigAt(t, airConfigFile, kConfigFile)
|
||||
}
|
||||
func DefaultInitConfig(t *testing.T) *Config {
|
||||
conf := InitConfig(t)
|
||||
airConfigFile := filepath.Join(AirshipConfigDir, AirshipConfig)
|
||||
kConfigFile := filepath.Join(AirshipConfigDir, AirshipKubeConfig)
|
||||
conf.SetLoadedConfigPath(airConfigFile)
|
||||
kubePathOptions := clientcmd.NewDefaultPathOptions()
|
||||
kubePathOptions.GlobalFile = kConfigFile
|
||||
conf.SetLoadedPathOptions(kubePathOptions)
|
||||
return conf
|
||||
}
|
||||
|
||||
func Clean(conf *Config) error {
|
||||
configDir := filepath.Dir(conf.LoadedConfigPath())
|
||||
err := os.RemoveAll(configDir)
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func DummyClusterOptions() *ClusterOptions {
|
||||
co := &ClusterOptions{}
|
||||
co.Name = "dummy_Cluster"
|
||||
co.ClusterType = Ephemeral
|
||||
co.Server = "http://1.1.1.1"
|
||||
co.InsecureSkipTLSVerify = false
|
||||
co.CertificateAuthority = ""
|
||||
co.EmbedCAData = false
|
||||
|
||||
return co
|
||||
}
|
1
pkg/config/testdata/GoldenString/AuthInfo.yaml
vendored
Normal file
1
pkg/config/testdata/GoldenString/AuthInfo.yaml
vendored
Normal file
@ -0,0 +1 @@
|
||||
{}
|
6
pkg/config/testdata/GoldenString/Cluster.yaml
vendored
Normal file
6
pkg/config/testdata/GoldenString/Cluster.yaml
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
bootstrap-info: dummy_bootstrap
|
||||
cluster-kubeconf: dummycluster_target
|
||||
|
||||
LocationOfOrigin: ""
|
||||
certificate-authority: dummy_ca
|
||||
server: http://dummy.server
|
37
pkg/config/testdata/GoldenString/Config.yaml
vendored
Normal file
37
pkg/config/testdata/GoldenString/Config.yaml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
clusters:
|
||||
dummy_cluster:
|
||||
cluster-type:
|
||||
ephemeral:
|
||||
bootstrap-info: dummy_bootstrap
|
||||
cluster-kubeconf: dummycluster_ephemeral
|
||||
target:
|
||||
bootstrap-info: dummy_bootstrap
|
||||
cluster-kubeconf: dummycluster_target
|
||||
contexts:
|
||||
dummy_context:
|
||||
context-kubeconf: dummy_cluster
|
||||
manifest: dummy_manifest
|
||||
current-context: dummy_context
|
||||
kind: Config
|
||||
manifests:
|
||||
dummy_manifest:
|
||||
repositories:
|
||||
dummy:
|
||||
target-path: dummy_targetpath
|
||||
url:
|
||||
ForceQuery: false
|
||||
Fragment: ""
|
||||
Host: dummy.url.com
|
||||
Opaque: ""
|
||||
Path: ""
|
||||
RawPath: ""
|
||||
RawQuery: ""
|
||||
Scheme: http
|
||||
User: null
|
||||
username: dummy_user
|
||||
target-path: /var/tmp/
|
||||
modules-config:
|
||||
dummy-for-tests: dummy-module
|
||||
users:
|
||||
dummy_user: {}
|
2
pkg/config/testdata/GoldenString/Context.yaml
vendored
Normal file
2
pkg/config/testdata/GoldenString/Context.yaml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
context-kubeconf: dummy_cluster
|
||||
manifest: dummy_manifest
|
1
pkg/config/testdata/GoldenString/Garbage.yaml
vendored
Normal file
1
pkg/config/testdata/GoldenString/Garbage.yaml
vendored
Normal file
@ -0,0 +1 @@
|
||||
garbage: Yes
|
15
pkg/config/testdata/GoldenString/Manifest.yaml
vendored
Normal file
15
pkg/config/testdata/GoldenString/Manifest.yaml
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
repositories:
|
||||
dummy:
|
||||
target-path: dummy_targetpath
|
||||
url:
|
||||
ForceQuery: false
|
||||
Fragment: ""
|
||||
Host: dummy.url.com
|
||||
Opaque: ""
|
||||
Path: ""
|
||||
RawPath: ""
|
||||
RawQuery: ""
|
||||
Scheme: http
|
||||
User: null
|
||||
username: dummy_user
|
||||
target-path: /var/tmp/
|
1
pkg/config/testdata/GoldenString/Modules.yaml
vendored
Normal file
1
pkg/config/testdata/GoldenString/Modules.yaml
vendored
Normal file
@ -0,0 +1 @@
|
||||
dummy-for-tests: dummy-module
|
9
pkg/config/testdata/GoldenString/PrettyCluster.yaml
vendored
Normal file
9
pkg/config/testdata/GoldenString/PrettyCluster.yaml
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
Cluster: def
|
||||
ephemeral:
|
||||
bootstrap-info: ""
|
||||
cluster-kubeconf: def_ephemeral
|
||||
|
||||
LocationOfOrigin: ../../pkg/config/testdata/kubeconfig.yaml
|
||||
insecure-skip-tls-verify: true
|
||||
server: http://5.6.7.8
|
||||
|
12
pkg/config/testdata/GoldenString/Repository.yaml
vendored
Normal file
12
pkg/config/testdata/GoldenString/Repository.yaml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
target-path: dummy_targetpath
|
||||
url:
|
||||
ForceQuery: false
|
||||
Fragment: ""
|
||||
Host: dummy.url.com
|
||||
Opaque: ""
|
||||
Path: ""
|
||||
RawPath: ""
|
||||
RawQuery: ""
|
||||
Scheme: http
|
||||
User: null
|
||||
username: dummy_user
|
1
pkg/config/testdata/ca.crt
vendored
Normal file
1
pkg/config/testdata/ca.crt
vendored
Normal file
@ -0,0 +1 @@
|
||||
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1Ea3lPVEUzTURNd09Wb1hEVEk1TURreU5qRTNNRE13T1Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTUZyCkdxM0kyb2dZci81Y01Udy9Na1pORTNWQURzdEdyU240WjU2TDhPUGhMcUhDN2t1dno2dVpES3dCSGtGeTBNK2MKRXIzd2piUGE1aTV5NmkyMGtxSHBVMjdPZTA0dzBXV2s4N0RSZVlWaGNoZVJHRXoraWt3SndIcGRmMjJVemZNKwpkSDBzaUhuMVd6UnovYk4za3hMUzJlMnZ2U1Y3bmNubk1YRUd4OXV0MUY0NThHeWxxdmxXTUlWMzg5Q2didXFDCkcwcFdiMTBLM0RVZWdiT25Xa1FmSm5sTWRRVVZDUVdZZEZaaklrcWtkWi9hVTRobkNEV01oZXNWRnFNaDN3VVAKczhQay9BNWh1ZFFPbnFRNDVIWXZLdjZ5RjJWcDUyWExBRUx3NDJ4aVRKZlh0V1h4eHR6cU4wY1lyL2VxeS9XMQp1YVVGSW5xQjFVM0JFL1oxbmFrQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFKUUVKQVBLSkFjVDVuK3dsWGJsdU9mS0J3c2gKZTI4R1c5R2QwM0N0NGF3RzhzMXE1ZHNua2tpZmVTUENHVFZ1SXF6UTZDNmJaSk9SMDMvVEl5ejh6NDJnaitDVApjWUZXZkltM2RKTnpRL08xWkdySXZZNWdtcWJtWDlpV0JaU24rRytEOGxubzd2aGMvY0tBRFR5OTMvVU92MThuCkdhMnIrRGJJcHcyTWVBVEl2elpxRS9RWlVSQ25DMmdjUFhTVzFqN2h4R3o1a3ZNcGVDZTdQYVUvdVFvblVHSWsKZ2t6ZzI4NHQvREhUUzc4N1V1SUg5cXBaV09yTFNMOGFBeUxQUHhWSXBteGZmbWRETE9TS2VUemRlTmxoSitUMwowQlBVaHBQTlJBNTNJN0hRQjhVUDR2elNONTkzZ1VFbVlFQ2Jic2RYSzB6ZVR6SDdWWHR2Zmd5WTVWWT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
|
37
pkg/config/testdata/config.yaml
vendored
Normal file
37
pkg/config/testdata/config.yaml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
clusters:
|
||||
def:
|
||||
cluster-type:
|
||||
ephemeral:
|
||||
bootstrap-info: ""
|
||||
cluster-kubeconf: def_ephemeral
|
||||
target:
|
||||
bootstrap-info: ""
|
||||
cluster-kubeconf: def_target
|
||||
onlyinkubeconf:
|
||||
cluster-type:
|
||||
target:
|
||||
bootstrap-info: ""
|
||||
cluster-kubeconf: onlyinkubeconf_target
|
||||
wrongonlyinconfig:
|
||||
cluster-type: {}
|
||||
wrongonlyinkubeconf:
|
||||
cluster-type:
|
||||
target:
|
||||
bootstrap-info: ""
|
||||
cluster-kubeconf: wrongonlyinkubeconf_target
|
||||
contexts:
|
||||
def_ephemeral:
|
||||
context-kubeconf: def_ephemeral
|
||||
def_target:
|
||||
context-kubeconf: def_target
|
||||
onlyink:
|
||||
context-kubeconf: onlyinkubeconf_target
|
||||
current-context: ""
|
||||
kind: Config
|
||||
manifests: {}
|
||||
modules-config:
|
||||
dummy-for-tests: ""
|
||||
users:
|
||||
k-admin: {}
|
||||
k-other: {}
|
43
pkg/config/testdata/kubeconfig.yaml
vendored
Normal file
43
pkg/config/testdata/kubeconfig.yaml
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
insecure-skip-tls-verify: true
|
||||
server: http://5.6.7.8
|
||||
name: def_ephemeral
|
||||
- cluster:
|
||||
insecure-skip-tls-verify: true
|
||||
server: http://1.2.3.4
|
||||
name: def_target
|
||||
- cluster:
|
||||
insecure-skip-tls-verify: true
|
||||
server: http://9.10.11.12
|
||||
name: onlyinkubeconf_target
|
||||
- cluster:
|
||||
certificate-authority: cert_file
|
||||
server: ""
|
||||
name: wrongonlyinkubeconf_target
|
||||
contexts:
|
||||
- context:
|
||||
cluster: def_ephemeral
|
||||
user: k-admin
|
||||
name: def_ephemeral
|
||||
- context:
|
||||
cluster: def_target
|
||||
user: k-admin
|
||||
name: def_target
|
||||
- context:
|
||||
cluster: onlyinkubeconf_target
|
||||
user: k-other
|
||||
name: onlyink
|
||||
current-context: ""
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: k-admin
|
||||
user:
|
||||
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJQXhEdzk2RUY4SXN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T1RBNU1qa3hOekF6TURsYUZ3MHlNREE1TWpneE56QXpNVEphTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXV6R0pZdlBaNkRvaTQyMUQKSzhXSmFaQ25OQWQycXo1cC8wNDJvRnpRUGJyQWd6RTJxWVZrek9MOHhBVmVSN1NONXdXb1RXRXlGOEVWN3JyLwo0K0hoSEdpcTVQbXF1SUZ5enpuNi9JWmM4alU5eEVmenZpa2NpckxmVTR2UlhKUXdWd2dBU05sMkFXQUloMmRECmRUcmpCQ2ZpS1dNSHlqMFJiSGFsc0J6T3BnVC9IVHYzR1F6blVRekZLdjJkajVWMU5rUy9ESGp5UlJKK0VMNlEKQlltR3NlZzVQNE5iQzllYnVpcG1NVEFxL0p1bU9vb2QrRmpMMm5acUw2Zkk2ZkJ0RjVPR2xwQ0IxWUo4ZnpDdApHUVFaN0hUSWJkYjJ0cDQzRlZPaHlRYlZjSHFUQTA0UEoxNSswV0F5bVVKVXo4WEE1NDRyL2J2NzRKY0pVUkZoCmFyWmlRd0lEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFMMmhIUmVibEl2VHJTMFNmUVg1RG9ueVVhNy84aTg1endVWApSd3dqdzFuS0U0NDJKbWZWRGZ5b0hRYUM4Ti9MQkxyUXM0U0lqU1JYdmFHU1dSQnRnT1RRV21Db1laMXdSbjdwCndDTXZQTERJdHNWWm90SEZpUFl2b1lHWFFUSXA3YlROMmg1OEJaaEZ3d25nWUovT04zeG1rd29IN1IxYmVxWEYKWHF1TTluekhESk41VlZub1lQR09yRHMwWlg1RnNxNGtWVU0wVExNQm9qN1ZIRDhmU0E5RjRYNU4yMldsZnNPMAo4aksrRFJDWTAyaHBrYTZQQ0pQS0lNOEJaMUFSMG9ZakZxT0plcXpPTjBqcnpYWHh4S2pHVFVUb1BldVA5dCtCCjJOMVA1TnI4a2oxM0lrend5Q1NZclFVN09ZM3ltZmJobHkrcXZxaFVFa014MlQ1SkpmQT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
|
||||
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBdXpHSll2UFo2RG9pNDIxREs4V0phWkNuTkFkMnF6NXAvMDQyb0Z6UVBickFnekUyCnFZVmt6T0w4eEFWZVI3U041d1dvVFdFeUY4RVY3cnIvNCtIaEhHaXE1UG1xdUlGeXp6bjYvSVpjOGpVOXhFZnoKdmlrY2lyTGZVNHZSWEpRd1Z3Z0FTTmwyQVdBSWgyZERkVHJqQkNmaUtXTUh5ajBSYkhhbHNCek9wZ1QvSFR2MwpHUXpuVVF6Rkt2MmRqNVYxTmtTL0RIanlSUkorRUw2UUJZbUdzZWc1UDROYkM5ZWJ1aXBtTVRBcS9KdW1Pb29kCitGakwyblpxTDZmSTZmQnRGNU9HbHBDQjFZSjhmekN0R1FRWjdIVEliZGIydHA0M0ZWT2h5UWJWY0hxVEEwNFAKSjE1KzBXQXltVUpVejhYQTU0NHIvYnY3NEpjSlVSRmhhclppUXdJREFRQUJBb0lCQVFDU0pycjlaeVpiQ2dqegpSL3VKMFZEWCt2aVF4c01BTUZyUjJsOE1GV3NBeHk1SFA4Vk4xYmc5djN0YUVGYnI1U3hsa3lVMFJRNjNQU25DCm1uM3ZqZ3dVQWlScllnTEl5MGk0UXF5VFBOU1V4cnpTNHRxTFBjM3EvSDBnM2FrNGZ2cSsrS0JBUUlqQnloamUKbnVFc1JpMjRzT3NESlM2UDE5NGlzUC9yNEpIM1M5bFZGbkVuOGxUR2c0M1kvMFZoMXl0cnkvdDljWjR5ZUNpNwpjMHFEaTZZcXJZaFZhSW9RRW1VQjdsbHRFZkZzb3l4VDR6RTE5U3pVbkRoMmxjYTF1TzhqcmI4d2xHTzBoQ2JyClB1R1l2WFFQa3Q0VlNmalhvdGJ3d2lBNFRCVERCRzU1bHp6MmNKeS9zSS8zSHlYbEMxcTdXUmRuQVhhZ1F0VzkKOE9DZGRkb0JBb0dCQU5NcUNtSW94REtyckhZZFRxT1M1ZFN4cVMxL0NUN3ZYZ0pScXBqd2Y4WHA2WHo0KzIvTAozVXFaVDBEL3dGTkZkc1Z4eFYxMnNYMUdwMHFWZVlKRld5OVlCaHVSWGpTZ0ZEWldSY1Z1Y01sNVpPTmJsbmZGCjVKQ0xnNXFMZ1g5VTNSRnJrR3A0R241UDQxamg4TnhKVlhzZG5xWE9xNTFUK1RRT1UzdkpGQjc1QW9HQkFPTHcKalp1cnZtVkZyTHdaVGgvRDNpWll5SVV0ZUljZ2NKLzlzbTh6L0pPRmRIbFd4dGRHUFVzYVd1MnBTNEhvckFtbgpqTm4vSTluUXd3enZ3MWUzVVFPbUhMRjVBczk4VU5hbk5TQ0xNMW1yaXZHRXJ1VHFnTDM1bU41eFZPdTUxQU5JCm4yNkFtODBJT2JDeEtLa0R0ZXJSaFhHd3g5c1pONVJCbG9VRThZNGJBb0dBQ3ZsdVhMZWRxcng5VkE0bDNoNXUKVDJXRVUxYjgxZ1orcmtRc1I1S0lNWEw4cllBTElUNUpHKzFuendyN3BkaEFXZmFWdVV2SDRhamdYT0h6MUs5aQpFODNSVTNGMG9ldUg0V01PY1RwU0prWm0xZUlXcWRiaEVCb1FGdUlWTXRib1BsV0d4ZUhFRHJoOEtreGp4aThSCmdEcUQyajRwY1IzQ0g5QjJ5a0lqQjVFQ2dZRUExc0xXLys2enE1c1lNSm14K1JXZThhTXJmL3pjQnVTSU1LQWgKY0dNK0wwMG9RSHdDaUU4TVNqcVN1ajV3R214YUFuanhMb3ZwSFlRV1VmUEVaUW95UE1YQ2VhRVBLOU4xbk8xMwp0V2lHRytIZkIxaU5PazFCc0lhNFNDbndOM1FRVTFzeXBaeEgxT3hueS9LYmkvYmEvWEZ5VzNqMGFUK2YvVWxrCmJGV1ZVdWtDZ1lFQTBaMmRTTFlmTjV5eFNtYk5xMWVqZXdWd1BjRzQxR2hQclNUZEJxdHFac1doWGE3aDdLTWEKeHdvamh5SXpnTXNyK2tXODdlajhDQ2h0d21sQ1p5QU92QmdOZytncnJ1cEZLM3FOSkpKeU9YREdHckdpbzZmTQp5aXB3Q2tZVGVxRThpZ1J6UkI5QkdFUGY4eVpjMUtwdmZhUDVhM0lRZmxiV0czbGpUemNNZVZjPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
|
||||
- name: k-other
|
||||
user:
|
||||
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJQXhEdzk2RUY4SXN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T1RBNU1qa3hOekF6TURsYUZ3MHlNREE1TWpneE56QXpNVEphTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXV6R0pZdlBaNkRvaTQyMUQKSzhXSmFaQ25OQWQycXo1cC8wNDJvRnpRUGJyQWd6RTJxWVZrek9MOHhBVmVSN1NONXdXb1RXRXlGOEVWN3JyLwo0K0hoSEdpcTVQbXF1SUZ5enpuNi9JWmM4alU5eEVmenZpa2NpckxmVTR2UlhKUXdWd2dBU05sMkFXQUloMmRECmRUcmpCQ2ZpS1dNSHlqMFJiSGFsc0J6T3BnVC9IVHYzR1F6blVRekZLdjJkajVWMU5rUy9ESGp5UlJKK0VMNlEKQlltR3NlZzVQNE5iQzllYnVpcG1NVEFxL0p1bU9vb2QrRmpMMm5acUw2Zkk2ZkJ0RjVPR2xwQ0IxWUo4ZnpDdApHUVFaN0hUSWJkYjJ0cDQzRlZPaHlRYlZjSHFUQTA0UEoxNSswV0F5bVVKVXo4WEE1NDRyL2J2NzRKY0pVUkZoCmFyWmlRd0lEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFMMmhIUmVibEl2VHJTMFNmUVg1RG9ueVVhNy84aTg1endVWApSd3dqdzFuS0U0NDJKbWZWRGZ5b0hRYUM4Ti9MQkxyUXM0U0lqU1JYdmFHU1dSQnRnT1RRV21Db1laMXdSbjdwCndDTXZQTERJdHNWWm90SEZpUFl2b1lHWFFUSXA3YlROMmg1OEJaaEZ3d25nWUovT04zeG1rd29IN1IxYmVxWEYKWHF1TTluekhESk41VlZub1lQR09yRHMwWlg1RnNxNGtWVU0wVExNQm9qN1ZIRDhmU0E5RjRYNU4yMldsZnNPMAo4aksrRFJDWTAyaHBrYTZQQ0pQS0lNOEJaMUFSMG9ZakZxT0plcXpPTjBqcnpYWHh4S2pHVFVUb1BldVA5dCtCCjJOMVA1TnI4a2oxM0lrend5Q1NZclFVN09ZM3ltZmJobHkrcXZxaFVFa014MlQ1SkpmQT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
|
||||
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBdXpHSll2UFo2RG9pNDIxREs4V0phWkNuTkFkMnF6NXAvMDQyb0Z6UVBickFnekUyCnFZVmt6T0w4eEFWZVI3U041d1dvVFdFeUY4RVY3cnIvNCtIaEhHaXE1UG1xdUlGeXp6bjYvSVpjOGpVOXhFZnoKdmlrY2lyTGZVNHZSWEpRd1Z3Z0FTTmwyQVdBSWgyZERkVHJqQkNmaUtXTUh5ajBSYkhhbHNCek9wZ1QvSFR2MwpHUXpuVVF6Rkt2MmRqNVYxTmtTL0RIanlSUkorRUw2UUJZbUdzZWc1UDROYkM5ZWJ1aXBtTVRBcS9KdW1Pb29kCitGakwyblpxTDZmSTZmQnRGNU9HbHBDQjFZSjhmekN0R1FRWjdIVEliZGIydHA0M0ZWT2h5UWJWY0hxVEEwNFAKSjE1KzBXQXltVUpVejhYQTU0NHIvYnY3NEpjSlVSRmhhclppUXdJREFRQUJBb0lCQVFDU0pycjlaeVpiQ2dqegpSL3VKMFZEWCt2aVF4c01BTUZyUjJsOE1GV3NBeHk1SFA4Vk4xYmc5djN0YUVGYnI1U3hsa3lVMFJRNjNQU25DCm1uM3ZqZ3dVQWlScllnTEl5MGk0UXF5VFBOU1V4cnpTNHRxTFBjM3EvSDBnM2FrNGZ2cSsrS0JBUUlqQnloamUKbnVFc1JpMjRzT3NESlM2UDE5NGlzUC9yNEpIM1M5bFZGbkVuOGxUR2c0M1kvMFZoMXl0cnkvdDljWjR5ZUNpNwpjMHFEaTZZcXJZaFZhSW9RRW1VQjdsbHRFZkZzb3l4VDR6RTE5U3pVbkRoMmxjYTF1TzhqcmI4d2xHTzBoQ2JyClB1R1l2WFFQa3Q0VlNmalhvdGJ3d2lBNFRCVERCRzU1bHp6MmNKeS9zSS8zSHlYbEMxcTdXUmRuQVhhZ1F0VzkKOE9DZGRkb0JBb0dCQU5NcUNtSW94REtyckhZZFRxT1M1ZFN4cVMxL0NUN3ZYZ0pScXBqd2Y4WHA2WHo0KzIvTAozVXFaVDBEL3dGTkZkc1Z4eFYxMnNYMUdwMHFWZVlKRld5OVlCaHVSWGpTZ0ZEWldSY1Z1Y01sNVpPTmJsbmZGCjVKQ0xnNXFMZ1g5VTNSRnJrR3A0R241UDQxamg4TnhKVlhzZG5xWE9xNTFUK1RRT1UzdkpGQjc1QW9HQkFPTHcKalp1cnZtVkZyTHdaVGgvRDNpWll5SVV0ZUljZ2NKLzlzbTh6L0pPRmRIbFd4dGRHUFVzYVd1MnBTNEhvckFtbgpqTm4vSTluUXd3enZ3MWUzVVFPbUhMRjVBczk4VU5hbk5TQ0xNMW1yaXZHRXJ1VHFnTDM1bU41eFZPdTUxQU5JCm4yNkFtODBJT2JDeEtLa0R0ZXJSaFhHd3g5c1pONVJCbG9VRThZNGJBb0dBQ3ZsdVhMZWRxcng5VkE0bDNoNXUKVDJXRVUxYjgxZ1orcmtRc1I1S0lNWEw4cllBTElUNUpHKzFuendyN3BkaEFXZmFWdVV2SDRhamdYT0h6MUs5aQpFODNSVTNGMG9ldUg0V01PY1RwU0prWm0xZUlXcWRiaEVCb1FGdUlWTXRib1BsV0d4ZUhFRHJoOEtreGp4aThSCmdEcUQyajRwY1IzQ0g5QjJ5a0lqQjVFQ2dZRUExc0xXLys2enE1c1lNSm14K1JXZThhTXJmL3pjQnVTSU1LQWgKY0dNK0wwMG9RSHdDaUU4TVNqcVN1ajV3R214YUFuanhMb3ZwSFlRV1VmUEVaUW95UE1YQ2VhRVBLOU4xbk8xMwp0V2lHRytIZkIxaU5PazFCc0lhNFNDbndOM1FRVTFzeXBaeEgxT3hueS9LYmkvYmEvWEZ5VzNqMGFUK2YvVWxrCmJGV1ZVdWtDZ1lFQTBaMmRTTFlmTjV5eFNtYk5xMWVqZXdWd1BjRzQxR2hQclNUZEJxdHFac1doWGE3aDdLTWEKeHdvamh5SXpnTXNyK2tXODdlajhDQ2h0d21sQ1p5QU92QmdOZytncnJ1cEZLM3FOSkpKeU9YREdHckdpbzZmTQp5aXB3Q2tZVGVxRThpZ1J6UkI5QkdFUGY4eVpjMUtwdmZhUDVhM0lRZmxiV0czbGpUemNNZVZjPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
|
147
pkg/config/types.go
Normal file
147
pkg/config/types.go
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
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 (
|
||||
"net/url"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
kubeconfig "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
// Where possible, json tags match the cli argument names.
|
||||
// Top level config objects and all values required for proper functioning are not "omitempty".
|
||||
// Any truly optional piece of config is allowed to be omitted.
|
||||
|
||||
// Config holds the information required by airshipct commands
|
||||
// It is somewhat a superset of what akubeconfig looks like, we allow for this overlaps by providing
|
||||
// a mechanism to consume or produce a kubeconfig into / from the airship config.
|
||||
type Config struct {
|
||||
// +optional
|
||||
Kind string `json:"kind,omitempty"`
|
||||
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
|
||||
// Clusters is a map of referenceable names to cluster configs
|
||||
Clusters map[string]*ClusterPurpose `json:"clusters"`
|
||||
|
||||
// AuthInfos is a map of referenceable names to user configs
|
||||
AuthInfos map[string]*AuthInfo `json:"users"`
|
||||
|
||||
// Contexts is a map of referenceable names to context configs
|
||||
Contexts map[string]*Context `json:"contexts"`
|
||||
|
||||
// Manifests is a map of referenceable names to documents
|
||||
Manifests map[string]*Manifest `json:"manifests"`
|
||||
|
||||
// CurrentContext is the name of the context that you would like to use by default
|
||||
CurrentContext string `json:"current-context"`
|
||||
|
||||
// Modules Section
|
||||
// Will store configuration required by the different airshipctl modules
|
||||
// Such as Bootstrap, Workflows, Document, etc
|
||||
ModulesConfig *Modules `json:"modules-config"`
|
||||
|
||||
// Private LoadedConfigPath is the full path to the the location of the config file
|
||||
// from which these config was loaded
|
||||
// +not persisted in file
|
||||
loadedConfigPath string
|
||||
|
||||
// Private loadedPathOptions is the full path to the the location of the kubeconfig file
|
||||
// associated with this airship config instance
|
||||
// +not persisted in file
|
||||
loadedPathOptions *clientcmd.PathOptions
|
||||
|
||||
// Private instance of Kube Config content as an object
|
||||
kubeConfig *kubeconfig.Config
|
||||
}
|
||||
|
||||
// Encapsultaes the Cluster Type as an enumeration
|
||||
type ClusterPurpose struct {
|
||||
// Cluster map of referenceable names to cluster configs
|
||||
ClusterTypes map[string]*Cluster `json:"cluster-type"`
|
||||
}
|
||||
|
||||
// Cluster contains information about how to communicate with a kubernetes cluster
|
||||
type Cluster struct {
|
||||
// Complex cluster name defined by the using <cluster name>_<clustertype)
|
||||
NameInKubeconf string `json:"cluster-kubeconf"`
|
||||
|
||||
// Kubeconfig Cluster Object
|
||||
kCluster *kubeconfig.Cluster
|
||||
|
||||
// Boostrap configuration this clusters ephemral hosts will rely on
|
||||
Bootstrap string `json:"bootstrap-info"`
|
||||
}
|
||||
|
||||
// Modules, generic configuration for modules
|
||||
// Configuration that the Bootstrap Module would need
|
||||
// Configuration that the Document Module would need
|
||||
// Configuration that the Workflows Module would need
|
||||
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),
|
||||
// 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>
|
||||
NameInKubeconf string `json:"context-kubeconf"`
|
||||
// Manifest is the default manifest to be use with this context
|
||||
// +optional
|
||||
Manifest string `json:"manifest,omitempty"`
|
||||
}
|
||||
|
||||
type AuthInfo struct {
|
||||
// Empty in purpose
|
||||
// Will implement Interface to Set/Get fields from kubeconfig as needed
|
||||
}
|
||||
|
||||
// Manifests is a tuple of references to a Manifest (how do Identify, collect ,
|
||||
// find the yaml manifests that airship uses to perform its operations)
|
||||
type Manifest struct {
|
||||
// Repositories is the map of repository adddressable by a name
|
||||
Repositories map[string]*Repository `json:"repositories"`
|
||||
|
||||
// Local Targer path for working or home dirctory for all Manifest Cloned/Returned/Generated
|
||||
TargetPath string `json:"target-path"`
|
||||
}
|
||||
|
||||
// Repository is a tuple that holds the information for the remote sources of manifest yaml documents.
|
||||
// Information such as location, authentication info,
|
||||
// as well as details of what to get such as branch, tag, commit it, etc.
|
||||
type Repository struct {
|
||||
// URL for Repositor,
|
||||
Url *url.URL `json:"url"`
|
||||
|
||||
// Username is the username for authentication to the repository .
|
||||
// +optional
|
||||
Username string `json:"username,omitempty"`
|
||||
|
||||
// Clone To Name Should always be relative to the setting of Manifest TargetPath.
|
||||
// Defines where ths repo will be cloned to locally.
|
||||
TargetPath string `json:"target-path"`
|
||||
}
|
||||
|
||||
// Holds the complex cluster name information
|
||||
// Encapsulates the different operations around using it.
|
||||
type ClusterComplexName struct {
|
||||
clusterName string
|
||||
clusterType string
|
||||
}
|
84
pkg/config/utils.go
Normal file
84
pkg/config/utils.go
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
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 (
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
// NewConfig is a convenience function that returns a new Config
|
||||
// object with non-nill maps
|
||||
func NewConfig() *Config {
|
||||
conf := &Config{
|
||||
Clusters: make(map[string]*ClusterPurpose),
|
||||
Contexts: make(map[string]*Context),
|
||||
AuthInfos: make(map[string]*AuthInfo),
|
||||
Manifests: make(map[string]*Manifest),
|
||||
}
|
||||
conf.ModulesConfig = NewModules()
|
||||
conf.Kind = AirshipConfigKind
|
||||
conf.APIVersion = AirshipConfigApiVersion
|
||||
|
||||
conf.loadedConfigPath = filepath.Join(AirshipConfigDir, AirshipConfig)
|
||||
conf.loadedPathOptions = clientcmd.NewDefaultPathOptions()
|
||||
conf.kubeConfig, _ = conf.loadedPathOptions.GetStartingConfig()
|
||||
return conf
|
||||
|
||||
}
|
||||
|
||||
// NewContext is a convenience function that returns a new Context
|
||||
func NewContext() *Context {
|
||||
return &Context{}
|
||||
}
|
||||
|
||||
// NewCluster is a convenience function that returns a new Cluster
|
||||
func NewCluster() *Cluster {
|
||||
return &Cluster{}
|
||||
}
|
||||
|
||||
// NewManifest is a convenience function that returns a new Manifest
|
||||
// object with non-nil maps
|
||||
func NewManifest() *Manifest {
|
||||
return &Manifest{
|
||||
Repositories: make(map[string]*Repository),
|
||||
}
|
||||
}
|
||||
|
||||
func NewRepository() *Repository {
|
||||
return &Repository{}
|
||||
}
|
||||
|
||||
func NewAuthInfo() *AuthInfo {
|
||||
return &AuthInfo{}
|
||||
}
|
||||
|
||||
func NewModules() *Modules {
|
||||
return &Modules{}
|
||||
}
|
||||
|
||||
// NewClusterPurpose is a convenience function that returns a new ClusterPurpose
|
||||
func NewClusterPurpose() *ClusterPurpose {
|
||||
return &ClusterPurpose{
|
||||
ClusterTypes: make(map[string]*Cluster),
|
||||
}
|
||||
}
|
||||
|
||||
func NewClusterComplexName() *ClusterComplexName {
|
||||
return &ClusterComplexName{}
|
||||
}
|
@ -11,3 +11,5 @@ const (
|
||||
NameOnly = "name"
|
||||
Wide = "wide"
|
||||
)
|
||||
|
||||
const HomePlaceholder = "$HOME"
|
||||
|
@ -1,17 +1,129 @@
|
||||
package environment
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
"opendev.org/airship/airshipctl/pkg/log"
|
||||
)
|
||||
|
||||
// AirshipCTLSettings is a container for all of the settings needed by airshipctl
|
||||
type AirshipCTLSettings struct {
|
||||
// Debug is used for verbose output
|
||||
Debug bool
|
||||
airshipConfigPath string
|
||||
kubeConfigPath string
|
||||
config *config.Config
|
||||
}
|
||||
|
||||
// InitFlags adds the default settings flags to cmd
|
||||
func (a *AirshipCTLSettings) InitFlags(cmd *cobra.Command) {
|
||||
flags := cmd.PersistentFlags()
|
||||
flags.BoolVar(&a.Debug, "debug", false, "enable verbose output")
|
||||
|
||||
flags.StringVar(&a.airshipConfigPath, config.FlagConfigFilePath,
|
||||
filepath.Join(HomePlaceholder, config.AirshipConfigDir, config.AirshipConfig),
|
||||
"Path to file for airshipctl configuration.")
|
||||
|
||||
flags.StringVar(&a.kubeConfigPath, clientcmd.RecommendedConfigPathFlag,
|
||||
filepath.Join(HomePlaceholder, config.AirshipConfigDir, config.AirshipKubeConfig),
|
||||
"Path to kubeconfig associated with airshipctl configuration.")
|
||||
|
||||
}
|
||||
|
||||
func (a *AirshipCTLSettings) Config() *config.Config {
|
||||
return a.config
|
||||
}
|
||||
func (a *AirshipCTLSettings) SetConfig(conf *config.Config) {
|
||||
a.config = conf
|
||||
}
|
||||
|
||||
func (a *AirshipCTLSettings) AirshipConfigPath() string {
|
||||
return a.airshipConfigPath
|
||||
}
|
||||
func (a *AirshipCTLSettings) SetAirshipConfigPath(acp string) {
|
||||
a.airshipConfigPath = acp
|
||||
}
|
||||
func (a *AirshipCTLSettings) KubeConfigPath() string {
|
||||
return a.kubeConfigPath
|
||||
}
|
||||
func (a *AirshipCTLSettings) SetKubeConfigPath(kcp string) {
|
||||
a.kubeConfigPath = kcp
|
||||
}
|
||||
|
||||
// InitConfig - Initializes and loads Config it exists.
|
||||
func (a *AirshipCTLSettings) InitConfig() {
|
||||
|
||||
// Raw - Empty Config object
|
||||
a.SetConfig(config.NewConfig())
|
||||
|
||||
a.setAirshipConfigPath()
|
||||
//Pass the airshipConfigPath and kubeConfig object
|
||||
err := a.Config().LoadConfig(a.AirshipConfigPath(), a.setKubePathOptions())
|
||||
if err != nil {
|
||||
// Should stop airshipctl
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (a *AirshipCTLSettings) setAirshipConfigPath() {
|
||||
// (1) If the airshipConfigPath was received as an argument its aleady set
|
||||
if a.airshipConfigPath == "" {
|
||||
// (2) If not , we can check if we got the Path via ENVIRONMNT variable,
|
||||
// set the appropriate fields
|
||||
a.setAirshipConfigPathFromEnv()
|
||||
}
|
||||
// (3) Check if the a.airshipConfigPath is empty still at this point , use the defaults
|
||||
acp, home := a.replaceHomePlaceholder(a.airshipConfigPath)
|
||||
a.airshipConfigPath = acp
|
||||
if a.airshipConfigPath == "" {
|
||||
a.airshipConfigPath = filepath.Join(home, config.AirshipConfigDir, config.AirshipConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// setAirshipConfigPathFromEnv Get AIRSHIP CONFIG from an environment variable if set
|
||||
func (a *AirshipCTLSettings) setAirshipConfigPathFromEnv() {
|
||||
// Check if AIRSHIPCONF env variable was set
|
||||
// I have the path and name for the airship config file
|
||||
a.airshipConfigPath = os.Getenv(config.AirshipConfigEnv)
|
||||
}
|
||||
|
||||
func (a *AirshipCTLSettings) setKubePathOptions() *clientcmd.PathOptions {
|
||||
// USe default expectations for Kubeconfig
|
||||
kubePathOptions := clientcmd.NewDefaultPathOptions()
|
||||
// No need to check the Environment , since we are relying on the kubeconfig defaults
|
||||
// If we did not get an explicit kubeconfig definition on airshipctl
|
||||
// as far as airshipctkl is concerned will use the default expectations for the kubeconfig
|
||||
// file location . This avoids messing up someones kubeconfig if they didnt explicitly want
|
||||
// airshipctl to use it.
|
||||
kcp, home := a.replaceHomePlaceholder(a.kubeConfigPath)
|
||||
a.kubeConfigPath = kcp
|
||||
if a.kubeConfigPath == "" {
|
||||
a.kubeConfigPath = filepath.Join(home, config.AirshipConfigDir, config.AirshipKubeConfig)
|
||||
}
|
||||
// We will always rely on tha airshipctl cli args or default for where to find kubeconfig
|
||||
kubePathOptions.GlobalFile = a.kubeConfigPath
|
||||
kubePathOptions.GlobalFileSubpath = a.kubeConfigPath
|
||||
|
||||
return kubePathOptions
|
||||
|
||||
}
|
||||
func (a *AirshipCTLSettings) replaceHomePlaceholder(configPath string) (string, string) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
// Use defaults under current directory
|
||||
home = ""
|
||||
}
|
||||
if configPath == "" {
|
||||
return configPath, home
|
||||
}
|
||||
|
||||
return strings.Replace(configPath, HomePlaceholder, home, 1), home
|
||||
}
|
||||
|
136
pkg/environment/settings_test.go
Normal file
136
pkg/environment/settings_test.go
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
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 environment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
)
|
||||
|
||||
const (
|
||||
testDataDir = "../../pkg/config/testdata"
|
||||
testMimeType = ".yaml"
|
||||
)
|
||||
|
||||
// Bogus for coverage
|
||||
func FakeCmd() *cobra.Command {
|
||||
fakecmd := &cobra.Command{
|
||||
Use: "fakecmd",
|
||||
Run: func(cmd *cobra.Command, args []string) {},
|
||||
}
|
||||
return fakecmd
|
||||
}
|
||||
|
||||
func TestInitFlags(t *testing.T) {
|
||||
|
||||
// Get the Environment
|
||||
settings := &AirshipCTLSettings{}
|
||||
fakecmd := FakeCmd()
|
||||
settings.InitFlags(fakecmd)
|
||||
assert.True(t, fakecmd.HasPersistentFlags())
|
||||
|
||||
}
|
||||
|
||||
func TestNewConfig(t *testing.T) {
|
||||
// Initialize kubeconfig
|
||||
src := filepath.Join(testDataDir, config.AirshipKubeConfig+testMimeType)
|
||||
dst := filepath.Join(config.AirshipConfigDir, config.AirshipKubeConfig)
|
||||
err := initTestDir(config.AirshipConfigDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer clean(config.AirshipConfigDir)
|
||||
_, err = copy(src, dst)
|
||||
require.NoError(t, err)
|
||||
|
||||
settings := &AirshipCTLSettings{}
|
||||
settings.InitConfig()
|
||||
conf := settings.Config()
|
||||
assert.NotNil(t, conf)
|
||||
|
||||
}
|
||||
|
||||
func TestSpecifyAirConfigFromEnv(t *testing.T) {
|
||||
fakeConfig := "FakeConfigPath"
|
||||
err := os.Setenv(config.AirshipConfigEnv, fakeConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
settings := &AirshipCTLSettings{}
|
||||
settings.InitConfig()
|
||||
|
||||
assert.EqualValues(t, fakeConfig, settings.AirshipConfigPath())
|
||||
}
|
||||
func TestGetSetPaths(t *testing.T) {
|
||||
settings := &AirshipCTLSettings{}
|
||||
settings.InitConfig()
|
||||
airConfigFile := filepath.Join(config.AirshipConfigDir, config.AirshipConfig)
|
||||
kConfigFile := filepath.Join(config.AirshipConfigDir, config.AirshipKubeConfig)
|
||||
settings.SetAirshipConfigPath(airConfigFile)
|
||||
assert.EqualValues(t, airConfigFile, settings.AirshipConfigPath())
|
||||
|
||||
settings.SetKubeConfigPath(kConfigFile)
|
||||
assert.EqualValues(t, kConfigFile, settings.KubeConfigPath())
|
||||
}
|
||||
|
||||
func TestSpecifyKubeConfigInCli(t *testing.T) {
|
||||
fakecmd := FakeCmd()
|
||||
|
||||
settings := &AirshipCTLSettings{}
|
||||
settings.InitFlags(fakecmd)
|
||||
assert.True(t, fakecmd.HasPersistentFlags())
|
||||
}
|
||||
|
||||
func initTestDir(dst string) error {
|
||||
return os.MkdirAll(dst, 0755)
|
||||
}
|
||||
|
||||
func copy(src, dst string) (int64, error) {
|
||||
sourceFileStat, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if !sourceFileStat.Mode().IsRegular() {
|
||||
return 0, fmt.Errorf("%s is not a regular file", src)
|
||||
}
|
||||
|
||||
source, err := os.Open(src)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer source.Close()
|
||||
|
||||
destination, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer destination.Close()
|
||||
nBytes, err := io.Copy(destination, source)
|
||||
return nBytes, err
|
||||
}
|
||||
|
||||
func clean(dst string) error {
|
||||
return os.RemoveAll(dst)
|
||||
}
|
Loading…
Reference in New Issue
Block a user