Skip to content

Commit

Permalink
Add oc serviceaccounts create-kubeconfig to simplify bootstrapping
Browse files Browse the repository at this point in the history
  • Loading branch information
smarterclayton committed Dec 2, 2016
1 parent b20ed8d commit 6e11f72
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 0 deletions.
172 changes: 172 additions & 0 deletions pkg/cmd/cli/sa/create_kubeconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package sa

import (
"errors"
"fmt"
"io"
"os"

"github.com/spf13/cobra"

kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/unversioned"
kclientcmd "k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"

"github.com/openshift/origin/pkg/cmd/templates"
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
"github.com/openshift/origin/pkg/serviceaccounts"
)

const (
CreateKubeconfigRecommendedName = "create-kubeconfig"

createKubeconfigShort = `Generate a kubeconfig file for a service account`

createKubeconfigUsage = `%s SA-NAME`
)

var (
createKubeconfigLong = templates.LongDesc(`
Generate a kubeconfig file that will utilize this service account.
The kubeconfig file will reference the service account token and use the current server,
namespace, and cluster contact info. If the service account has multiple tokens, the
first token found will be returned. The generated file will be output to STDOUT.
Service account API tokens are used by service accounts to authenticate to the API.
Client actions using a service account token will be executed as if the service account
itself were making the actions.`)

createKubeconfigExamples = templates.Examples(`
# Create a kubeconfig file for service account 'default'
%[1]s 'default' > default.kubeconfig`)
)

type CreateKubeconfigOptions struct {
SAName string
SAClient unversioned.ServiceAccountsInterface
SecretsClient unversioned.SecretsInterface
RawConfig clientcmdapi.Config
ContextNamespace string

Out io.Writer
Err io.Writer
}

func NewCommandCreateKubeconfig(name, fullname string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
options := &CreateKubeconfigOptions{
Out: out,
Err: os.Stderr,
}

cmd := &cobra.Command{
Use: fmt.Sprintf(createKubeconfigUsage, name),
Short: createKubeconfigShort,
Long: createKubeconfigLong,
Example: fmt.Sprintf(createKubeconfigExamples, fullname),
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(args, f, cmd))
cmdutil.CheckErr(options.Validate())
cmdutil.CheckErr(options.Run())
},
}
cmd.Flags().StringVar(&options.ContextNamespace, "with-namespace", "", "Namespace for this context in .kubeconfig.")
return cmd
}

func (o *CreateKubeconfigOptions) Complete(args []string, f *clientcmd.Factory, cmd *cobra.Command) error {
if len(args) != 1 {
return cmdutil.UsageError(cmd, fmt.Sprintf("expected one service account name as an argument, got %q", args))
}

o.SAName = args[0]

client, err := f.Client()
if err != nil {
return err
}

namespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}

o.RawConfig, err = f.OpenShiftClientConfig.RawConfig()
if err != nil {
return err
}

if len(o.ContextNamespace) == 0 {
o.ContextNamespace = namespace
}

o.SAClient = client.ServiceAccounts(namespace)
o.SecretsClient = client.Secrets(namespace)
return nil
}

func (o *CreateKubeconfigOptions) Validate() error {
if o.SAName == "" {
return errors.New("service account name cannot be empty")
}

if o.SAClient == nil || o.SecretsClient == nil {
return errors.New("API clients must not be nil in order to create a new service account token")
}

if o.Out == nil || o.Err == nil {
return errors.New("cannot proceed if output or error writers are nil")
}

return nil
}

func (o *CreateKubeconfigOptions) Run() error {
serviceAccount, err := o.SAClient.Get(o.SAName)
if err != nil {
return err
}

for _, reference := range serviceAccount.Secrets {
secret, err := o.SecretsClient.Get(reference.Name)
if err != nil {
continue
}

if serviceaccounts.IsValidServiceAccountToken(serviceAccount, secret) {
token, exists := secret.Data[kapi.ServiceAccountTokenKey]
if !exists {
return fmt.Errorf("service account token %q for service account %q did not contain token data", secret.Name, serviceAccount.Name)
}

cfg := &o.RawConfig
if err := clientcmdapi.MinifyConfig(cfg); err != nil {
return fmt.Errorf("invalid configuration, unable to create new config file: %v", err)
}

ctx := cfg.Contexts[cfg.CurrentContext]
ctx.Namespace = o.ContextNamespace
// rename the current context
cfg.CurrentContext = o.SAName
cfg.Contexts = map[string]*clientcmdapi.Context{
cfg.CurrentContext: ctx,
}
// use the server name
ctx.AuthInfo = o.SAName
cfg.AuthInfos = map[string]*clientcmdapi.AuthInfo{
ctx.AuthInfo: {
Token: string(token),
},
}
out, err := kclientcmd.Write(*cfg)
if err != nil {
return err
}
fmt.Fprintf(o.Out, string(out))
return nil
}
}
return fmt.Errorf("could not find a service account token for service account %q", serviceAccount.Name)
}
1 change: 1 addition & 0 deletions pkg/cmd/cli/sa/subcommand.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func NewCmdServiceAccounts(name, fullName string, f *clientcmd.Factory, out, err
Run: cmdutil.DefaultSubCommandRun(errOut),
}

cmds.AddCommand(NewCommandCreateKubeconfig(CreateKubeconfigRecommendedName, fullName+" "+CreateKubeconfigRecommendedName, f, out))
cmds.AddCommand(NewCommandGetServiceAccountToken(GetServiceAccountTokenRecommendedName, fullName+" "+GetServiceAccountTokenRecommendedName, f, out))
cmds.AddCommand(NewCommandNewServiceAccountToken(NewServiceAccountTokenRecommendedName, fullName+" "+NewServiceAccountTokenRecommendedName, f, out))

Expand Down
6 changes: 6 additions & 0 deletions test/cmd/secrets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,9 @@ os::cmd::expect_success 'oc secret unlink --help'

echo "secrets: ok"
os::test::junit::declare_suite_end

os::test::junit::declare_suite_start "cmd/serviceaccounts-create-kubeconfig"
os::cmd::expect_success 'oc serviceaccounts create-kubeconfig default > ${TMPDIR}/generated_default.kubeconfig'

This comment has been minimized.

Copy link
@stevekuznetsov

stevekuznetsov Dec 9, 2016

Contributor

${TMPDIR} is not set on all systems. We explicitly set ${BASETMPDIR} for Origin tasks and things like ${ARTIFACT_DIR} for things that could be useful to keep for debugging. Please use one of the correct temporary locations. This fails in an Ansible-triggered make invocation.

os::cmd::expect_success_and_text 'KUBECONFIG=${TMPDIR}/generated_default.kubeconfig oc whoami' "system:serviceaccount:$(oc project -q):default"
echo "serviceaccounts: ok"
os::test::junit::declare_suite_end

0 comments on commit 6e11f72

Please sign in to comment.