Introduces config cmd's for set-credentials and get-credentials

to manage authentication information for clusters.

Includes username/password, certificate
and token options.

Change-Id: If95e5bbf5c3ddc4732465e81de407d5ad416e8f2
This commit is contained in:
Rodolfo Pacheco 2020-02-04 13:00:21 -05:00
parent 8c6ebb66d9
commit 36a302fce1
23 changed files with 882 additions and 29 deletions

View File

@ -13,13 +13,15 @@ func NewConfigCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Comma
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
Short: ("Modify airshipctl config files"), Short: ("Modify airshipctl config files"),
Long: (`Modify airshipctl config files using subcommands Long: (`Modify airshipctl config files using subcommands
like "airshipctl config set-current-context my-context" `), like "airshipctl config set-context --current-context my-context" `),
} }
configRootCmd.AddCommand(NewCmdConfigSetCluster(rootSettings)) configRootCmd.AddCommand(NewCmdConfigSetCluster(rootSettings))
configRootCmd.AddCommand(NewCmdConfigGetCluster(rootSettings)) configRootCmd.AddCommand(NewCmdConfigGetCluster(rootSettings))
configRootCmd.AddCommand(NewCmdConfigSetContext(rootSettings)) configRootCmd.AddCommand(NewCmdConfigSetContext(rootSettings))
configRootCmd.AddCommand(NewCmdConfigGetContext(rootSettings)) configRootCmd.AddCommand(NewCmdConfigGetContext(rootSettings))
configRootCmd.AddCommand(NewCmdConfigInit(rootSettings)) configRootCmd.AddCommand(NewCmdConfigInit(rootSettings))
configRootCmd.AddCommand(NewCmdConfigSetAuthInfo(rootSettings))
configRootCmd.AddCommand(NewCmdConfigGetAuthInfo(rootSettings))
return configRootCmd return configRootCmd
} }

View File

@ -0,0 +1,89 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"fmt"
"io"
"github.com/spf13/cobra"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/environment"
)
var (
getAuthInfoLong = (`Display a specific user information, or all defined users if no name is provided`)
getAuthInfoExample = (`# List all the users airshipctl knows about
airshipctl config get-credential
# Display a specific user information
airshipctl config get-credential e2e`)
)
// An AuthInfo refers to a particular user for a cluster
// NewCmdConfigGetAuthInfo returns a Command instance for 'config -AuthInfo' sub command
func NewCmdConfigGetAuthInfo(rootSettings *environment.AirshipCTLSettings) *cobra.Command {
theAuthInfo := &config.AuthInfoOptions{}
getauthinfocmd := &cobra.Command{
Use: "get-credentials NAME",
Short: "Gets a user entry from the airshipctl config",
Long: getAuthInfoLong,
Example: getAuthInfoExample,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 1 {
theAuthInfo.Name = args[0]
}
return runGetAuthInfo(theAuthInfo, cmd.OutOrStdout(), rootSettings.Config())
},
}
return getauthinfocmd
}
// runGetAuthInfo performs the execution of 'config get-credentials' sub command
func runGetAuthInfo(o *config.AuthInfoOptions, out io.Writer, airconfig *config.Config) error {
if o.Name == "" {
return getAuthInfos(out, airconfig)
}
return getAuthInfo(o, out, airconfig)
}
func getAuthInfo(o *config.AuthInfoOptions, out io.Writer, airconfig *config.Config) error {
cName := o.Name
authinfo, err := airconfig.GetAuthInfo(cName)
if err != nil {
return err
}
fmt.Fprintln(out, authinfo)
return nil
}
func getAuthInfos(out io.Writer, airconfig *config.Config) error {
authinfos, err := airconfig.GetAuthInfos()
if err != nil {
return err
}
if len(authinfos) == 0 {
fmt.Fprintln(out, "No User credentials found in the configuration.")
}
for _, authinfo := range authinfos {
fmt.Fprintln(out, authinfo)
}
return nil
}

View File

@ -0,0 +1,106 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config_test
import (
"fmt"
"testing"
kubeconfig "k8s.io/client-go/tools/clientcmd/api"
cmd "opendev.org/airship/airshipctl/cmd/config"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/environment"
"opendev.org/airship/airshipctl/testutil"
)
const (
fooAuthInfo = "AuthInfoFoo"
barAuthInfo = "AuthInfoBar"
bazAuthInfo = "AuthInfoBaz"
missingAuthInfo = "authinfoMissing"
)
func TestGetAuthInfoCmd(t *testing.T) {
conf := &config.Config{
AuthInfos: map[string]*config.AuthInfo{
fooAuthInfo: getTestAuthInfo(),
barAuthInfo: getTestAuthInfo(),
bazAuthInfo: getTestAuthInfo(),
},
}
settings := &environment.AirshipCTLSettings{}
settings.SetConfig(conf)
cmdTests := []*testutil.CmdTest{
{
Name: "get-credentials",
CmdLine: fmt.Sprintf("%s", fooAuthInfo),
Cmd: cmd.NewCmdConfigGetAuthInfo(settings),
},
{
Name: "get-all-credentials",
CmdLine: fmt.Sprintf("%s %s", fooAuthInfo, barAuthInfo),
Cmd: cmd.NewCmdConfigGetAuthInfo(settings),
},
// This is not implemented yet
{
Name: "get-multiple-credentials",
CmdLine: fmt.Sprintf("%s %s", fooAuthInfo, barAuthInfo),
Cmd: cmd.NewCmdConfigGetAuthInfo(settings),
},
{
Name: "missing",
CmdLine: fmt.Sprintf("%s", missingAuthInfo),
Cmd: cmd.NewCmdConfigGetAuthInfo(settings),
Error: fmt.Errorf("User %s information was not "+
"found in the configuration.", missingAuthInfo),
},
}
for _, tt := range cmdTests {
testutil.RunTest(t, tt)
}
}
func TestNoAuthInfosGetAuthInfoCmd(t *testing.T) {
settings := &environment.AirshipCTLSettings{}
settings.SetConfig(&config.Config{})
cmdTest := &testutil.CmdTest{
Name: "no-credentials",
CmdLine: "",
Cmd: cmd.NewCmdConfigGetAuthInfo(settings),
}
testutil.RunTest(t, cmdTest)
}
func getTestAuthInfo() *config.AuthInfo {
kAuthInfo := &kubeconfig.AuthInfo{
Username: "dummy_user",
Password: "dummy_password",
ClientCertificate: "dummy_certificate",
ClientKey: "dummy_key",
Token: "dummy_token",
}
newAuthInfo := &config.AuthInfo{}
newAuthInfo.SetKubeAuthInfo(kAuthInfo)
return newAuthInfo
}

162
cmd/config/set_authinfo.go Normal file
View File

@ -0,0 +1,162 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"errors"
"fmt"
"github.com/spf13/cobra"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/environment"
"opendev.org/airship/airshipctl/pkg/log"
)
var (
setAuthInfoLong = fmt.Sprintf(`Sets a user entry in airshipctl config
Specifying a name that already exists will merge new fields on top of existing values.
Client-certificate flags:
--%v=certfile --%v=keyfile
Bearer token flags:
--%v=bearer_token
Basic auth flags:
--%v=basic_user --%v=basic_password
Bearer token and basic auth are mutually exclusive.`,
config.FlagCertFile,
config.FlagKeyFile,
config.FlagBearerToken,
config.FlagUsername,
config.FlagPassword)
setAuthInfoExample = fmt.Sprintf(`
# Set only the "client-key" field on the "cluster-admin"
# entry, without touching other values:
airshipctl config set-credentials cluster-admin --%v=~/.kube/admin.key
# Set basic auth for the "cluster-admin" entry
airshipctl config set-credentials cluster-admin --%v=admin --%v=uXFGweU9l35qcif
# Embed client certificate data in the "cluster-admin" entry
airshipctl config set-credentials cluster-admin --%v=~/.kube/admin.crt --%v=true`,
config.FlagUsername,
config.FlagUsername,
config.FlagPassword,
config.FlagCertFile,
config.FlagEmbedCerts,
)
)
// NewCmdConfigSetAuthInfo creates a command object for the "set-credentials" action, which
// defines a new AuthInfo airship config.
func NewCmdConfigSetAuthInfo(rootSettings *environment.AirshipCTLSettings) *cobra.Command {
theAuthInfo := &config.AuthInfoOptions{}
setauthinfo := &cobra.Command{
Use: "set-credentials NAME",
Short: "Sets a user entry in the airshipctl config",
Long: setAuthInfoLong,
Example: setAuthInfoExample,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
theAuthInfo.Name = args[0]
modified, err := runSetAuthInfo(theAuthInfo, rootSettings.Config())
if err != nil {
return err
}
if modified {
fmt.Fprintf(cmd.OutOrStdout(), "User information %q modified.\n", theAuthInfo.Name)
} else {
fmt.Fprintf(cmd.OutOrStdout(), "User information %q created.\n", theAuthInfo.Name)
}
return nil
},
}
err := suInitFlags(theAuthInfo, setauthinfo)
if err != nil {
log.Fatal(err)
}
return setauthinfo
}
func suInitFlags(o *config.AuthInfoOptions, setauthinfo *cobra.Command) error {
setauthinfo.Flags().StringVar(&o.ClientCertificate, config.FlagCertFile, o.ClientCertificate,
"Path to "+config.FlagCertFile+" file for the user entry in airshipctl")
err := setauthinfo.MarkFlagFilename(config.FlagCertFile)
if err != nil {
return err
}
setauthinfo.Flags().StringVar(&o.ClientKey, config.FlagKeyFile, o.ClientKey,
"Path to "+config.FlagKeyFile+" file for the user entry in airshipctl")
err = setauthinfo.MarkFlagFilename(config.FlagKeyFile)
if err != nil {
return err
}
setauthinfo.Flags().StringVar(&o.Token, config.FlagBearerToken, o.Token,
config.FlagBearerToken+" for the user entry in airshipctl")
setauthinfo.Flags().StringVar(&o.Username, config.FlagUsername, o.Username,
config.FlagUsername+" for the user entry in airshipctl")
setauthinfo.Flags().StringVar(&o.Password, config.FlagPassword, o.Password,
config.FlagPassword+" for the user entry in airshipctl")
setauthinfo.Flags().BoolVar(&o.EmbedCertData, config.FlagEmbedCerts, false,
"Embed client cert/key for the user entry in airshipctl")
return nil
}
func runSetAuthInfo(o *config.AuthInfoOptions, airconfig *config.Config) (bool, error) {
authinfoWasModified := false
err := o.Validate()
if err != nil {
return authinfoWasModified, err
}
authinfoIWant := o.Name
authinfo, err := airconfig.GetAuthInfo(authinfoIWant)
if err != nil {
var cerr config.ErrMissingConfig
if !errors.As(err, &cerr) {
// An error occurred, but it wasn't a "missing" config error.
return authinfoWasModified, err
}
// authinfo didn't exist, create it
// ignoring the returned added authinfo
airconfig.AddAuthInfo(o)
} else {
// AuthInfo exists, lets update
airconfig.ModifyAuthInfo(authinfo, o)
authinfoWasModified = true
}
// Update configuration file just in time persistence approach
if err := airconfig.PersistConfig(); err != nil {
// Error that it didnt persist the changes
return authinfoWasModified, config.ErrConfigFailed{}
}
return authinfoWasModified, nil
}

View File

@ -0,0 +1,158 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"bytes"
"testing"
kubeconfig "k8s.io/client-go/tools/clientcmd/api"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/environment"
"opendev.org/airship/airshipctl/testutil"
)
const (
testUsername = "admin@kubernetes"
testPassword = "adminPassword"
testNewname = "dummy"
testOldname = "def-user"
pwdDelta = "_changed"
)
type setAuthInfoTest struct {
description string
config *config.Config
args []string
flags []string
expected string
expectedConfig *config.Config
}
func TestConfigSetAuthInfo(t *testing.T) {
cmdTests := []*testutil.CmdTest{
{
Name: "config-cmd-set-authinfo-with-help",
CmdLine: "--help",
Cmd: NewCmdConfigSetAuthInfo(nil),
},
}
for _, tt := range cmdTests {
testutil.RunTest(t, tt)
}
}
func initConfig(t *testing.T, withUser bool, testname string) (*config.Config, *config.Config) {
conf := config.InitConfig(t)
if withUser {
kAuthInfo := kubeconfig.NewAuthInfo()
kAuthInfo.Username = testUsername
kAuthInfo.Password = testPassword
conf.KubeConfig().AuthInfos[testname] = kAuthInfo
conf.AuthInfos[testname].SetKubeAuthInfo(kAuthInfo)
}
expconf := config.InitConfig(t)
expconf.AuthInfos[testname] = config.NewAuthInfo()
expkAuthInfo := kubeconfig.NewAuthInfo()
expkAuthInfo.Username = testUsername
expkAuthInfo.Password = testPassword
expconf.KubeConfig().AuthInfos[testname] = expkAuthInfo
expconf.AuthInfos[testname].SetKubeAuthInfo(expkAuthInfo)
return conf, expconf
}
func TestSetAuthInfo(t *testing.T) {
conf, expconf := initConfig(t, false, testNewname)
test := setAuthInfoTest{
description: "Testing 'airshipctl config set-credential' with a new user",
config: conf,
args: []string{testNewname},
flags: []string{
"--" + config.FlagUsername + "=" + testUsername,
"--" + config.FlagPassword + "=" + testPassword,
},
expected: `User information "` + testNewname + `" created.` + "\n",
expectedConfig: expconf,
}
test.run(t)
}
func TestModifyAuthInfo(t *testing.T) {
conf, expconf := initConfig(t, true, testOldname)
expconf.AuthInfos[testOldname].KubeAuthInfo().Password = testPassword + pwdDelta
test := setAuthInfoTest{
description: "Testing 'airshipctl config set-credential' with an existing user",
config: conf,
args: []string{testOldname},
flags: []string{
"--" + config.FlagPassword + "=" + testPassword + pwdDelta,
},
expected: `User information "` + testOldname + `" modified.` + "\n",
expectedConfig: expconf,
}
test.run(t)
}
func (test setAuthInfoTest) run(t *testing.T) {
// Get the Environment
settings := &environment.AirshipCTLSettings{}
settings.SetConfig(test.config)
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdConfigSetAuthInfo(settings)
cmd.SetOutput(buf)
cmd.SetArgs(test.args)
err := cmd.Flags().Parse(test.flags)
require.NoErrorf(t, err, "unexpected error flags args to command: %v, flags: %v", err, test.flags)
// Execute the Command
// Which should Persist the File
err = cmd.Execute()
require.NoErrorf(t, err, "unexpected error executing command: %v, args: %v, flags: %v", err, test.args, test.flags)
afterRunConf := settings.Config()
// Find the AuthInfo Created or Modified
afterRunAuthInfo, err := afterRunConf.GetAuthInfo(test.args[0])
require.NoError(t, err)
require.NotNil(t, afterRunAuthInfo)
afterKauthinfo := afterRunAuthInfo.KubeAuthInfo()
require.NotNil(t, afterKauthinfo)
testKauthinfo := test.expectedConfig.KubeConfig().AuthInfos[test.args[0]]
require.NotNil(t, testKauthinfo)
assert.EqualValues(t, testKauthinfo.Username, afterKauthinfo.Username)
assert.EqualValues(t, testKauthinfo.Password, afterKauthinfo.Password)
// Test that the Return Message looks correct
if len(test.expected) != 0 {
assert.EqualValues(t, test.expected, buf.String())
}
}

View File

@ -56,7 +56,7 @@ airshipctl config set-cluster e2e --%v-type=target --%v=true --%v=".airship/cert
) )
// NewCmdConfigSetCluster creates a command object for the "set-cluster" action, which // NewCmdConfigSetCluster creates a command object for the "set-cluster" action, which
// defines a new cluster airship config. // defines a new cluster airshipctl config.
func NewCmdConfigSetCluster(rootSettings *environment.AirshipCTLSettings) *cobra.Command { func NewCmdConfigSetCluster(rootSettings *environment.AirshipCTLSettings) *cobra.Command {
theCluster := &config.ClusterOptions{} theCluster := &config.ClusterOptions{}

View File

@ -46,7 +46,7 @@ airshipctl config set-context e2e --%v=true`,
) )
// NewCmdConfigSetContext creates a command object for the "set-context" action, which // NewCmdConfigSetContext creates a command object for the "set-context" action, which
// defines a new Context airship config. // defines a new Context airshipctl config.
func NewCmdConfigSetContext(rootSettings *environment.AirshipCTLSettings) *cobra.Command { func NewCmdConfigSetContext(rootSettings *environment.AirshipCTLSettings) *cobra.Command {
theContext := &config.ContextOptions{} theContext := &config.ContextOptions{}

View File

@ -1,16 +1,18 @@
Modify airshipctl config files using subcommands Modify airshipctl config files using subcommands
like "airshipctl config set-current-context my-context" like "airshipctl config set-context --current-context my-context"
Usage: Usage:
config [command] config [command]
Available Commands: Available Commands:
get-cluster Display a specific cluster or all defined clusters if no name is provided get-cluster Display a specific cluster or all defined clusters if no name is provided
get-context Display a specific context, the current-context or all defined contexts if no name is provided get-context Display a specific context, the current-context or all defined contexts if no name is provided
help Help about any command get-credentials Gets a user entry from the airshipctl config
init Generate initial configuration files for airshipctl help Help about any command
set-cluster Sets a cluster entry in the airshipctl config init Generate initial configuration files for airshipctl
set-context Sets a context entry or updates current-context in the airshipctl config set-cluster Sets a cluster entry in the airshipctl config
set-context Sets a context entry or updates current-context in the airshipctl config
set-credentials Sets a user entry in the airshipctl config
Flags: Flags:
-h, --help help for config -h, --help help for config

View File

@ -1,16 +1,18 @@
Modify airshipctl config files using subcommands Modify airshipctl config files using subcommands
like "airshipctl config set-current-context my-context" like "airshipctl config set-context --current-context my-context"
Usage: Usage:
config [command] config [command]
Available Commands: Available Commands:
get-cluster Display a specific cluster or all defined clusters if no name is provided get-cluster Display a specific cluster or all defined clusters if no name is provided
get-context Display a specific context, the current-context or all defined contexts if no name is provided get-context Display a specific context, the current-context or all defined contexts if no name is provided
help Help about any command get-credentials Gets a user entry from the airshipctl config
init Generate initial configuration files for airshipctl help Help about any command
set-cluster Sets a cluster entry in the airshipctl config init Generate initial configuration files for airshipctl
set-context Sets a context entry or updates current-context in the airshipctl config set-cluster Sets a cluster entry in the airshipctl config
set-context Sets a context entry or updates current-context in the airshipctl config
set-credentials Sets a user entry in the airshipctl config
Flags: Flags:
-h, --help help for config -h, --help help for config

View File

@ -0,0 +1,37 @@
Sets a user entry in airshipctl config
Specifying a name that already exists will merge new fields on top of existing values.
Client-certificate flags:
--client-certificate=certfile --client-key=keyfile
Bearer token flags:
--token=bearer_token
Basic auth flags:
--username=basic_user --password=basic_password
Bearer token and basic auth are mutually exclusive.
Usage:
set-credentials NAME [flags]
Examples:
# Set only the "client-key" field on the "cluster-admin"
# entry, without touching other values:
airshipctl config set-credentials cluster-admin --username=~/.kube/admin.key
# Set basic auth for the "cluster-admin" entry
airshipctl config set-credentials cluster-admin --username=admin --password=uXFGweU9l35qcif
# Embed client certificate data in the "cluster-admin" entry
airshipctl config set-credentials cluster-admin --client-certificate=~/.kube/admin.crt --embed-certs=true
Flags:
--client-certificate string Path to client-certificate file for the user entry in airshipctl
--client-key string Path to client-key file for the user entry in airshipctl
--embed-certs Embed client cert/key for the user entry in airshipctl
-h, --help help for set-credentials
--password string password for the user entry in airshipctl
--token string token for the user entry in airshipctl
--username string username for the user entry in airshipctl

View File

@ -0,0 +1,21 @@
LocationOfOrigin: ""
client-certificate: dummy_certificate
client-key: dummy_key
password: dummy_password
token: dummy_token
username: dummy_user
LocationOfOrigin: ""
client-certificate: dummy_certificate
client-key: dummy_key
password: dummy_password
token: dummy_token
username: dummy_user
LocationOfOrigin: ""
client-certificate: dummy_certificate
client-key: dummy_key
password: dummy_password
token: dummy_token
username: dummy_user

View File

@ -0,0 +1,7 @@
LocationOfOrigin: ""
client-certificate: dummy_certificate
client-key: dummy_key
password: dummy_password
token: dummy_token
username: dummy_user

View File

@ -0,0 +1,21 @@
LocationOfOrigin: ""
client-certificate: dummy_certificate
client-key: dummy_key
password: dummy_password
token: dummy_token
username: dummy_user
LocationOfOrigin: ""
client-certificate: dummy_certificate
client-key: dummy_key
password: dummy_password
token: dummy_token
username: dummy_user
LocationOfOrigin: ""
client-certificate: dummy_certificate
client-key: dummy_key
password: dummy_password
token: dummy_token
username: dummy_user

View File

@ -0,0 +1,14 @@
Error: Missing configuration: User credentials with name 'authinfoMissing'
Usage:
get-credentials NAME [flags]
Examples:
# List all the users airshipctl knows about
airshipctl config get-credential
# Display a specific user information
airshipctl config get-credential e2e
Flags:
-h, --help help for get-credentials

View File

@ -0,0 +1 @@
No User credentials found in the configuration.

View File

@ -62,3 +62,28 @@ func (o *ContextOptions) Validate() error {
// TODO Manifest, Cluster could be validated against the existing config maps // TODO Manifest, Cluster could be validated against the existing config maps
return nil return nil
} }
func (o *AuthInfoOptions) Validate() error {
if len(o.Token) > 0 && (len(o.Username) > 0 || len(o.Password) > 0) {
return fmt.Errorf("you cannot specify more than one authentication method at the same time: --%v or --%v/--%v",
FlagBearerToken, FlagUsername, FlagPassword)
}
if !o.EmbedCertData {
return nil
}
certPath := o.ClientCertificate
if certPath == "" {
return fmt.Errorf("you must specify a --%s to embed", FlagCertFile)
}
if _, err := ioutil.ReadFile(certPath); err != nil {
return fmt.Errorf("error reading %s data from %s: %v", FlagCertFile, certPath, err)
}
keyPath := o.ClientKey
if keyPath == "" {
return fmt.Errorf("you must specify a --%s to embed", FlagKeyFile)
}
if _, err := ioutil.ReadFile(keyPath); err != nil {
return fmt.Errorf("error reading %s data from %s: %v", FlagKeyFile, keyPath, err)
}
return nil
}

View File

@ -68,3 +68,15 @@ func TestValidateContext(t *testing.T) {
err := co.Validate() err := co.Validate()
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestValidateAuthInfo(t *testing.T) {
co := DummyAuthInfoOptions()
// Token and cert error case
err := co.Validate()
assert.Error(t, err)
// Valid Data case
co.Token = ""
err = co.Validate()
assert.NoError(t, err)
}

View File

@ -18,3 +18,13 @@ type ContextOptions struct {
Manifest string Manifest string
Namespace string Namespace string
} }
type AuthInfoOptions struct {
Name string
ClientCertificate string
ClientKey string
Token string
Username string
Password string
EmbedCertData bool
}

View File

@ -240,6 +240,7 @@ func (c *Config) reconcileAuthInfos() {
// Add the reference // Add the reference
c.AuthInfos[key] = NewAuthInfo() c.AuthInfos[key] = NewAuthInfo()
} }
c.AuthInfos[key].SetKubeAuthInfo(authinfo)
} }
// Checking if there is any AuthInfo reference in airship config that does not match // Checking if there is any AuthInfo reference in airship config that does not match
// an actual Auth Info struct in kubeconfig // an actual Auth Info struct in kubeconfig
@ -619,6 +620,61 @@ func (c *Config) CurrentContextManifest() (*Manifest, error) {
return c.Manifests[currentContext.Manifest], nil return c.Manifests[currentContext.Manifest], nil
} }
// Credential or AuthInfo related methods
func (c *Config) GetAuthInfo(aiName string) (*AuthInfo, error) {
authinfo, exists := c.AuthInfos[aiName]
if !exists {
return nil, ErrMissingConfig{What: fmt.Sprintf("User credentials with name '%s'", aiName)}
}
return authinfo, nil
}
func (c *Config) GetAuthInfos() ([]*AuthInfo, error) {
authinfos := []*AuthInfo{}
for cName := range c.AuthInfos {
authinfo, err := c.GetAuthInfo(cName)
if err == nil {
authinfos = append(authinfos, authinfo)
}
}
return authinfos, nil
}
func (c *Config) AddAuthInfo(theAuthInfo *AuthInfoOptions) *AuthInfo {
// Create the new Airship config context
nAuthInfo := NewAuthInfo()
c.AuthInfos[theAuthInfo.Name] = nAuthInfo
// Create a new Kubeconfig AuthInfo object as well
kAuthInfo := kubeconfig.NewAuthInfo()
nAuthInfo.SetKubeAuthInfo(kAuthInfo)
c.KubeConfig().AuthInfos[theAuthInfo.Name] = kAuthInfo
c.ModifyAuthInfo(nAuthInfo, theAuthInfo)
return nAuthInfo
}
func (c *Config) ModifyAuthInfo(authinfo *AuthInfo, theAuthInfo *AuthInfoOptions) {
kAuthInfo := authinfo.KubeAuthInfo()
if kAuthInfo == nil {
return
}
if theAuthInfo.ClientCertificate != "" {
kAuthInfo.ClientCertificate = theAuthInfo.ClientCertificate
}
if theAuthInfo.Token != "" {
kAuthInfo.Token = theAuthInfo.Token
}
if theAuthInfo.Username != "" {
kAuthInfo.Username = theAuthInfo.Username
}
if theAuthInfo.Password != "" {
kAuthInfo.Password = theAuthInfo.Password
}
if theAuthInfo.ClientKey != "" {
kAuthInfo.ClientKey = theAuthInfo.ClientKey
}
}
// CurrentContextBootstrapInfo returns bootstrap info for current context // CurrentContextBootstrapInfo returns bootstrap info for current context
func (c *Config) CurrentContextBootstrapInfo() (*Bootstrap, error) { func (c *Config) CurrentContextBootstrapInfo() (*Bootstrap, error) {
currentCluster, err := c.CurrentContextCluster() currentCluster, err := c.CurrentContextCluster()
@ -738,17 +794,25 @@ func (c *Context) ClusterType() string {
// AuthInfo functions // AuthInfo functions
func (c *AuthInfo) Equal(d *AuthInfo) bool { func (c *AuthInfo) Equal(d *AuthInfo) bool {
if d == nil { if d == nil {
return d == c return c == d
} }
return c == d return c.kAuthInfo == d.kAuthInfo
} }
func (c *AuthInfo) String() string { func (c *AuthInfo) String() string {
yaml, err := yaml.Marshal(&c) kauthinfo := c.KubeAuthInfo()
kyaml, err := yaml.Marshal(&kauthinfo)
if err != nil { if err != nil {
return "" return ""
} }
return string(yaml) return string(kyaml)
}
func (c *AuthInfo) KubeAuthInfo() *kubeconfig.AuthInfo {
return c.kAuthInfo
}
func (c *AuthInfo) SetKubeAuthInfo(kc *kubeconfig.AuthInfo) {
c.kAuthInfo = kc
} }
// Manifest functions // Manifest functions
@ -931,3 +995,11 @@ func KContextString(kContext *kubeconfig.Context) string {
return string(yaml) return string(yaml)
} }
func KAuthInfoString(kAuthInfo *kubeconfig.AuthInfo) string {
yaml, err := yaml.Marshal(&kAuthInfo)
if err != nil {
return ""
}
return string(yaml)
}

View File

@ -32,6 +32,8 @@ import (
"opendev.org/airship/airshipctl/testutil" "opendev.org/airship/airshipctl/testutil"
) )
const stringDelta = "_changed"
func TestString(t *testing.T) { func TestString(t *testing.T) {
fSys := testutil.SetupTestFs(t, "testdata") fSys := testutil.SetupTestFs(t, "testdata")
@ -215,7 +217,7 @@ func TestLoadConfig(t *testing.T) {
require.Contains(t, conf.Clusters, "def") require.Contains(t, conf.Clusters, "def")
assert.Len(t, conf.Clusters["def"].ClusterTypes, 2) assert.Len(t, conf.Clusters["def"].ClusterTypes, 2)
assert.Len(t, conf.Contexts, 3) assert.Len(t, conf.Contexts, 3)
assert.Len(t, conf.AuthInfos, 2) assert.Len(t, conf.AuthInfos, 3)
} }
func TestPersistConfig(t *testing.T) { func TestPersistConfig(t *testing.T) {
@ -385,6 +387,15 @@ func TestKContextString(t *testing.T) {
} }
assert.EqualValues(t, KClusterString(nil), "null\n") assert.EqualValues(t, KClusterString(nil), "null\n")
} }
func TestKAuthInfoString(t *testing.T) {
conf := InitConfig(t)
kAuthInfos := conf.KubeConfig().AuthInfos
for kAi := range kAuthInfos {
assert.NotEmpty(t, KAuthInfoString(kAuthInfos[kAi]))
}
assert.EqualValues(t, KAuthInfoString(nil), "null\n")
}
func TestComplexName(t *testing.T) { func TestComplexName(t *testing.T) {
cName := "aCluster" cName := "aCluster"
ctName := Ephemeral ctName := Ephemeral
@ -434,13 +445,13 @@ func TestAddCluster(t *testing.T) {
assert.EqualValues(t, conf.Clusters[co.Name].ClusterTypes[co.ClusterType], cluster) assert.EqualValues(t, conf.Clusters[co.Name].ClusterTypes[co.ClusterType], cluster)
} }
func TestModifyluster(t *testing.T) { func TestModifyCluster(t *testing.T) {
co := DummyClusterOptions() co := DummyClusterOptions()
conf := InitConfig(t) conf := InitConfig(t)
cluster, err := conf.AddCluster(co) cluster, err := conf.AddCluster(co)
require.NoError(t, err) require.NoError(t, err)
co.Server += "/changes" co.Server += stringDelta
co.InsecureSkipTLSVerify = true co.InsecureSkipTLSVerify = true
co.EmbedCAData = true co.EmbedCAData = true
mcluster, err := conf.ModifyCluster(cluster, co) mcluster, err := conf.ModifyCluster(cluster, co)
@ -533,3 +544,75 @@ func TestGetContext(t *testing.T) {
_, err = conf.GetContext("unknown") _, err = conf.GetContext("unknown")
assert.Error(t, err) assert.Error(t, err)
} }
func TestAddContext(t *testing.T) {
co := DummyContextOptions()
conf := InitConfig(t)
context := conf.AddContext(co)
assert.EqualValues(t, conf.Contexts[co.Name], context)
}
func TestModifyContext(t *testing.T) {
co := DummyContextOptions()
conf := InitConfig(t)
context := conf.AddContext(co)
co.Namespace += stringDelta
co.Cluster += stringDelta
co.AuthInfo += stringDelta
co.Manifest += stringDelta
conf.ModifyContext(context, co)
assert.EqualValues(t, conf.Contexts[co.Name].KubeContext().Namespace, co.Namespace)
assert.EqualValues(t, conf.Contexts[co.Name].KubeContext().Cluster, co.Cluster)
assert.EqualValues(t, conf.Contexts[co.Name].KubeContext().AuthInfo, co.AuthInfo)
assert.EqualValues(t, conf.Contexts[co.Name].Manifest, co.Manifest)
assert.EqualValues(t, conf.Contexts[co.Name], context)
}
// AuthInfo Related
func TestGetAuthInfos(t *testing.T) {
conf := InitConfig(t)
authinfos, err := conf.GetAuthInfos()
require.NoError(t, err)
assert.Len(t, authinfos, 3)
}
func TestGetAuthInfo(t *testing.T) {
conf := InitConfig(t)
authinfo, err := conf.GetAuthInfo("def-user")
require.NoError(t, err)
// Test Positives
assert.EqualValues(t, authinfo.KubeAuthInfo().Username, "dummy_username")
// Test Wrong Cluster
_, err = conf.GetAuthInfo("unknown")
assert.Error(t, err)
}
func TestAddAuthInfo(t *testing.T) {
co := DummyAuthInfoOptions()
conf := InitConfig(t)
authinfo := conf.AddAuthInfo(co)
assert.EqualValues(t, conf.AuthInfos[co.Name], authinfo)
}
func TestModifyAuthInfo(t *testing.T) {
co := DummyAuthInfoOptions()
conf := InitConfig(t)
authinfo := conf.AddAuthInfo(co)
co.Username += stringDelta
co.Password += stringDelta
co.ClientCertificate += stringDelta
co.ClientKey += stringDelta
co.Token += stringDelta
conf.ModifyAuthInfo(authinfo, co)
assert.EqualValues(t, conf.AuthInfos[co.Name].KubeAuthInfo().Username, co.Username)
assert.EqualValues(t, conf.AuthInfos[co.Name].KubeAuthInfo().Password, co.Password)
assert.EqualValues(t, conf.AuthInfos[co.Name].KubeAuthInfo().ClientCertificate, co.ClientCertificate)
assert.EqualValues(t, conf.AuthInfos[co.Name].KubeAuthInfo().ClientKey, co.ClientKey)
assert.EqualValues(t, conf.AuthInfos[co.Name].KubeAuthInfo().Token, co.Token)
assert.EqualValues(t, conf.AuthInfos[co.Name], authinfo)
}

View File

@ -104,7 +104,15 @@ func DummyRepository() *Repository {
} }
func DummyAuthInfo() *AuthInfo { func DummyAuthInfo() *AuthInfo {
return NewAuthInfo() a := NewAuthInfo()
authinfo := kubeconfig.NewAuthInfo()
authinfo.Username = "dummy_username"
authinfo.Password = "dummy_password"
authinfo.ClientCertificate = "dummy_certificate"
authinfo.ClientKey = "dummy_key"
authinfo.Token = "dummy_token"
a.SetKubeAuthInfo(authinfo)
return a
} }
func DummyModules() *Modules { func DummyModules() *Modules {
@ -168,6 +176,16 @@ func DummyContextOptions() *ContextOptions {
return co return co
} }
func DummyAuthInfoOptions() *AuthInfoOptions {
authinfo := &AuthInfoOptions{}
authinfo.Username = "dummy_username"
authinfo.Password = "dummy_password"
authinfo.ClientCertificate = "dummy_certificate"
authinfo.ClientKey = "dummy_key"
authinfo.Token = "dummy_token"
return authinfo
}
func DummyBootstrap() *Bootstrap { func DummyBootstrap() *Bootstrap {
bs := &Bootstrap{} bs := &Bootstrap{}
cont := Container{ cont := Container{
@ -224,7 +242,8 @@ modules-config:
dummy-for-tests: "" dummy-for-tests: ""
users: users:
k-admin: {} k-admin: {}
k-other: {}` k-other: {}
def-user: {}`
//nolint:lll //nolint:lll
testKubeConfigYAML = `apiVersion: v1 testKubeConfigYAML = `apiVersion: v1
@ -262,6 +281,11 @@ current-context: ""
kind: Config kind: Config
preferences: {} preferences: {}
users: users:
users:
- name: def-user
user:
username: dummy_username
password: dummy_password
- name: k-admin - name: k-admin
user: user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJQXhEdzk2RUY4SXN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T1RBNU1qa3hOekF6TURsYUZ3MHlNREE1TWpneE56QXpNVEphTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXV6R0pZdlBaNkRvaTQyMUQKSzhXSmFaQ25OQWQycXo1cC8wNDJvRnpRUGJyQWd6RTJxWVZrek9MOHhBVmVSN1NONXdXb1RXRXlGOEVWN3JyLwo0K0hoSEdpcTVQbXF1SUZ5enpuNi9JWmM4alU5eEVmenZpa2NpckxmVTR2UlhKUXdWd2dBU05sMkFXQUloMmRECmRUcmpCQ2ZpS1dNSHlqMFJiSGFsc0J6T3BnVC9IVHYzR1F6blVRekZLdjJkajVWMU5rUy9ESGp5UlJKK0VMNlEKQlltR3NlZzVQNE5iQzllYnVpcG1NVEFxL0p1bU9vb2QrRmpMMm5acUw2Zkk2ZkJ0RjVPR2xwQ0IxWUo4ZnpDdApHUVFaN0hUSWJkYjJ0cDQzRlZPaHlRYlZjSHFUQTA0UEoxNSswV0F5bVVKVXo4WEE1NDRyL2J2NzRKY0pVUkZoCmFyWmlRd0lEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFMMmhIUmVibEl2VHJTMFNmUVg1RG9ueVVhNy84aTg1endVWApSd3dqdzFuS0U0NDJKbWZWRGZ5b0hRYUM4Ti9MQkxyUXM0U0lqU1JYdmFHU1dSQnRnT1RRV21Db1laMXdSbjdwCndDTXZQTERJdHNWWm90SEZpUFl2b1lHWFFUSXA3YlROMmg1OEJaaEZ3d25nWUovT04zeG1rd29IN1IxYmVxWEYKWHF1TTluekhESk41VlZub1lQR09yRHMwWlg1RnNxNGtWVU0wVExNQm9qN1ZIRDhmU0E5RjRYNU4yMldsZnNPMAo4aksrRFJDWTAyaHBrYTZQQ0pQS0lNOEJaMUFSMG9ZakZxT0plcXpPTjBqcnpYWHh4S2pHVFVUb1BldVA5dCtCCjJOMVA1TnI4a2oxM0lrend5Q1NZclFVN09ZM3ltZmJobHkrcXZxaFVFa014MlQ1SkpmQT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJQXhEdzk2RUY4SXN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T1RBNU1qa3hOekF6TURsYUZ3MHlNREE1TWpneE56QXpNVEphTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXV6R0pZdlBaNkRvaTQyMUQKSzhXSmFaQ25OQWQycXo1cC8wNDJvRnpRUGJyQWd6RTJxWVZrek9MOHhBVmVSN1NONXdXb1RXRXlGOEVWN3JyLwo0K0hoSEdpcTVQbXF1SUZ5enpuNi9JWmM4alU5eEVmenZpa2NpckxmVTR2UlhKUXdWd2dBU05sMkFXQUloMmRECmRUcmpCQ2ZpS1dNSHlqMFJiSGFsc0J6T3BnVC9IVHYzR1F6blVRekZLdjJkajVWMU5rUy9ESGp5UlJKK0VMNlEKQlltR3NlZzVQNE5iQzllYnVpcG1NVEFxL0p1bU9vb2QrRmpMMm5acUw2Zkk2ZkJ0RjVPR2xwQ0IxWUo4ZnpDdApHUVFaN0hUSWJkYjJ0cDQzRlZPaHlRYlZjSHFUQTA0UEoxNSswV0F5bVVKVXo4WEE1NDRyL2J2NzRKY0pVUkZoCmFyWmlRd0lEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFMMmhIUmVibEl2VHJTMFNmUVg1RG9ueVVhNy84aTg1endVWApSd3dqdzFuS0U0NDJKbWZWRGZ5b0hRYUM4Ti9MQkxyUXM0U0lqU1JYdmFHU1dSQnRnT1RRV21Db1laMXdSbjdwCndDTXZQTERJdHNWWm90SEZpUFl2b1lHWFFUSXA3YlROMmg1OEJaaEZ3d25nWUovT04zeG1rd29IN1IxYmVxWEYKWHF1TTluekhESk41VlZub1lQR09yRHMwWlg1RnNxNGtWVU0wVExNQm9qN1ZIRDhmU0E5RjRYNU4yMldsZnNPMAo4aksrRFJDWTAyaHBrYTZQQ0pQS0lNOEJaMUFSMG9ZakZxT0plcXpPTjBqcnpYWHh4S2pHVFVUb1BldVA5dCtCCjJOMVA1TnI4a2oxM0lrend5Q1NZclFVN09ZM3ltZmJobHkrcXZxaFVFa014MlQ1SkpmQT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=

View File

@ -1 +1,6 @@
{} LocationOfOrigin: ""
client-certificate: dummy_certificate
client-key: dummy_key
password: dummy_password
token: dummy_token
username: dummy_username

View File

@ -112,8 +112,8 @@ type Context struct {
} }
type AuthInfo struct { type AuthInfo struct {
// Empty in purpose // Kubeconfig AuthInfo Object
// Will implement Interface to Set/Get fields from kubeconfig as needed kAuthInfo *kubeconfig.AuthInfo
} }
// Manifests is a tuple of references to a Manifest (how do Identify, collect , // Manifests is a tuple of references to a Manifest (how do Identify, collect ,