add new command karmadactl create
Signed-off-by: hulizhe <pompeii.hu@gmail.com>
This commit is contained in:
parent
c7571227f5
commit
a6aa08e6b3
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
Copyright 2024 The Karmada 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 create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
kubectlcreate "k8s.io/kubectl/pkg/cmd/create"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
|
||||
"github.com/karmada-io/karmada/pkg/karmadactl/util"
|
||||
)
|
||||
|
||||
var (
|
||||
createLong = templates.LongDesc(`
|
||||
Create a resource from a file or from stdin.
|
||||
|
||||
JSON and YAML formats are accepted.`)
|
||||
|
||||
createExample = templates.Examples(`
|
||||
# Create a pod using the data in pod.json
|
||||
%[1]s create -f ./pod.json
|
||||
|
||||
# Create a pod based on the JSON passed into stdin
|
||||
cat pod.json | %[1]s create -f -
|
||||
|
||||
# Edit the data in registry.yaml in JSON then create the resource using the edited data
|
||||
%[1]s create -f registry.yaml --edit -o json`)
|
||||
)
|
||||
|
||||
// NewCmdCreate returns new initialized instance of create sub command
|
||||
func NewCmdCreate(f util.Factory, parentCommnd string, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
cmd := kubectlcreate.NewCmdCreate(f, ioStreams)
|
||||
cmd.Long = fmt.Sprintf(createLong, parentCommnd)
|
||||
cmd.Example = fmt.Sprintf(createExample, parentCommnd)
|
||||
return cmd
|
||||
}
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/karmada-io/karmada/pkg/karmadactl/apply"
|
||||
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit"
|
||||
"github.com/karmada-io/karmada/pkg/karmadactl/cordon"
|
||||
"github.com/karmada-io/karmada/pkg/karmadactl/create"
|
||||
"github.com/karmada-io/karmada/pkg/karmadactl/deinit"
|
||||
"github.com/karmada-io/karmada/pkg/karmadactl/describe"
|
||||
"github.com/karmada-io/karmada/pkg/karmadactl/exec"
|
||||
|
@ -87,6 +88,7 @@ func NewKarmadaCtlCommand(cmdUse, parentCommand string) *cobra.Command {
|
|||
Message: "Basic Commands:",
|
||||
Commands: []*cobra.Command{
|
||||
get.NewCmdGet(f, parentCommand, ioStreams),
|
||||
create.NewCmdCreate(f, parentCommand, ioStreams),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,472 @@
|
|||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
kruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
"k8s.io/client-go/dynamic"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/cmd/util/editor"
|
||||
"k8s.io/kubectl/pkg/generate"
|
||||
"k8s.io/kubectl/pkg/rawhttp"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
// CreateOptions is the commandline options for 'create' sub command
|
||||
type CreateOptions struct {
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
RecordFlags *genericclioptions.RecordFlags
|
||||
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
|
||||
ValidationDirective string
|
||||
|
||||
fieldManager string
|
||||
|
||||
FilenameOptions resource.FilenameOptions
|
||||
Selector string
|
||||
EditBeforeCreate bool
|
||||
Raw string
|
||||
|
||||
Recorder genericclioptions.Recorder
|
||||
PrintObj func(obj kruntime.Object) error
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
var (
|
||||
createLong = templates.LongDesc(i18n.T(`
|
||||
Create a resource from a file or from stdin.
|
||||
|
||||
JSON and YAML formats are accepted.`))
|
||||
|
||||
createExample = templates.Examples(i18n.T(`
|
||||
# Create a pod using the data in pod.json
|
||||
kubectl create -f ./pod.json
|
||||
|
||||
# Create a pod based on the JSON passed into stdin
|
||||
cat pod.json | kubectl create -f -
|
||||
|
||||
# Edit the data in registry.yaml in JSON then create the resource using the edited data
|
||||
kubectl create -f registry.yaml --edit -o json`))
|
||||
)
|
||||
|
||||
// NewCreateOptions returns an initialized CreateOptions instance
|
||||
func NewCreateOptions(ioStreams genericiooptions.IOStreams) *CreateOptions {
|
||||
return &CreateOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
RecordFlags: genericclioptions.NewRecordFlags(),
|
||||
|
||||
Recorder: genericclioptions.NoopRecorder{},
|
||||
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreate returns new initialized instance of create sub command
|
||||
func NewCmdCreate(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewCreateOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "create -f FILENAME",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a resource from a file or from stdin"),
|
||||
Long: createLong,
|
||||
Example: createExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames, o.FilenameOptions.Kustomize) {
|
||||
ioStreams.ErrOut.Write([]byte("Error: must specify one of -f and -k\n\n"))
|
||||
defaultRunFunc := cmdutil.DefaultSubCommandRun(ioStreams.ErrOut)
|
||||
defaultRunFunc(cmd, args)
|
||||
return
|
||||
}
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.RunCreate(f, cmd))
|
||||
},
|
||||
}
|
||||
|
||||
// bind flag structs
|
||||
o.RecordFlags.AddFlags(cmd)
|
||||
|
||||
usage := "to use to create the resource"
|
||||
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmd.Flags().BoolVar(&o.EditBeforeCreate, "edit", o.EditBeforeCreate, "Edit the API resource before creating")
|
||||
cmd.Flags().Bool("windows-line-endings", runtime.GOOS == "windows",
|
||||
"Only relevant if --edit=true. Defaults to the line ending native to your platform.")
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmdutil.AddLabelSelectorFlagVar(cmd, &o.Selector)
|
||||
cmd.Flags().StringVar(&o.Raw, "raw", o.Raw, "Raw URI to POST to the server. Uses the transport specified by the kubeconfig file.")
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-create")
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
// create subcommands
|
||||
cmd.AddCommand(NewCmdCreateNamespace(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateQuota(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateSecret(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateConfigMap(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateServiceAccount(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateService(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateDeployment(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateClusterRole(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateClusterRoleBinding(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateRole(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateRoleBinding(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreatePodDisruptionBudget(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreatePriorityClass(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateJob(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateCronJob(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateIngress(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateToken(f, ioStreams))
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Validate makes sure there is no discrepency in command options
|
||||
func (o *CreateOptions) Validate() error {
|
||||
if len(o.Raw) > 0 {
|
||||
if o.EditBeforeCreate {
|
||||
return fmt.Errorf("--raw and --edit are mutually exclusive")
|
||||
}
|
||||
if len(o.FilenameOptions.Filenames) != 1 {
|
||||
return fmt.Errorf("--raw can only use a single local file or stdin")
|
||||
}
|
||||
if strings.Index(o.FilenameOptions.Filenames[0], "http://") == 0 || strings.Index(o.FilenameOptions.Filenames[0], "https://") == 0 {
|
||||
return fmt.Errorf("--raw cannot read from a url")
|
||||
}
|
||||
if o.FilenameOptions.Recursive {
|
||||
return fmt.Errorf("--raw and --recursive are mutually exclusive")
|
||||
}
|
||||
if len(o.Selector) > 0 {
|
||||
return fmt.Errorf("--raw and --selector (-l) are mutually exclusive")
|
||||
}
|
||||
if o.PrintFlags.OutputFormat != nil && len(*o.PrintFlags.OutputFormat) > 0 {
|
||||
return fmt.Errorf("--raw and --output are mutually exclusive")
|
||||
}
|
||||
if _, err := url.ParseRequestURI(o.Raw); err != nil {
|
||||
return fmt.Errorf("--raw must be a valid URL path: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Complete completes all the required options
|
||||
func (o *CreateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 0 {
|
||||
return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args)
|
||||
}
|
||||
var err error
|
||||
o.RecordFlags.Complete(cmd)
|
||||
o.Recorder, err = o.RecordFlags.ToRecorder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PrintObj = func(obj kruntime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunCreate performs the creation
|
||||
func (o *CreateOptions) RunCreate(f cmdutil.Factory, cmd *cobra.Command) error {
|
||||
// raw only makes sense for a single file resource multiple objects aren't likely to do what you want.
|
||||
// the validator enforces this, so
|
||||
if len(o.Raw) > 0 {
|
||||
restClient, err := f.RESTClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return rawhttp.RawPost(restClient, o.IOStreams, o.Raw, o.FilenameOptions.Filenames[0])
|
||||
}
|
||||
|
||||
if o.EditBeforeCreate {
|
||||
return RunEditOnCreate(f, o.PrintFlags, o.RecordFlags, o.IOStreams, cmd, &o.FilenameOptions, o.fieldManager)
|
||||
}
|
||||
|
||||
schema, err := f.Validator(o.ValidationDirective)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := f.NewBuilder().
|
||||
Unstructured().
|
||||
Schema(schema).
|
||||
ContinueOnError().
|
||||
NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||
FilenameParam(enforceNamespace, &o.FilenameOptions).
|
||||
LabelSelectorParam(o.Selector).
|
||||
Flatten().
|
||||
Do()
|
||||
err = r.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
count := 0
|
||||
err = r.Visit(func(info *resource.Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := util.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info.Object, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return cmdutil.AddSourceToErr("creating", info.Source, err)
|
||||
}
|
||||
|
||||
if err := o.Recorder.Record(info.Object); err != nil {
|
||||
klog.V(4).Infof("error recording current command: %v", err)
|
||||
}
|
||||
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
obj, err := resource.
|
||||
NewHelper(info.Client, info.Mapping).
|
||||
DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
|
||||
WithFieldManager(o.fieldManager).
|
||||
WithFieldValidation(o.ValidationDirective).
|
||||
Create(info.Namespace, true, info.Object)
|
||||
if err != nil {
|
||||
return cmdutil.AddSourceToErr("creating", info.Source, err)
|
||||
}
|
||||
info.Refresh(obj, true)
|
||||
}
|
||||
|
||||
count++
|
||||
|
||||
return o.PrintObj(info.Object)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
return fmt.Errorf("no objects passed to create")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunEditOnCreate performs edit on creation
|
||||
func RunEditOnCreate(f cmdutil.Factory, printFlags *genericclioptions.PrintFlags, recordFlags *genericclioptions.RecordFlags, ioStreams genericiooptions.IOStreams, cmd *cobra.Command, options *resource.FilenameOptions, fieldManager string) error {
|
||||
editOptions := editor.NewEditOptions(editor.EditBeforeCreateMode, ioStreams)
|
||||
editOptions.FilenameOptions = *options
|
||||
validationDirective, err := cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
editOptions.ValidateOptions = cmdutil.ValidateOptions{
|
||||
ValidationDirective: string(validationDirective),
|
||||
}
|
||||
editOptions.PrintFlags = printFlags
|
||||
editOptions.ApplyAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
editOptions.RecordFlags = recordFlags
|
||||
editOptions.FieldManager = "kubectl-create"
|
||||
|
||||
err = editOptions.Complete(f, []string{}, cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return editOptions.Run()
|
||||
}
|
||||
|
||||
// NameFromCommandArgs is a utility function for commands that assume the first argument is a resource name
|
||||
func NameFromCommandArgs(cmd *cobra.Command, args []string) (string, error) {
|
||||
argsLen := cmd.ArgsLenAtDash()
|
||||
// ArgsLenAtDash returns -1 when -- was not specified
|
||||
if argsLen == -1 {
|
||||
argsLen = len(args)
|
||||
}
|
||||
if argsLen != 1 {
|
||||
return "", cmdutil.UsageErrorf(cmd, "exactly one NAME is required, got %d", argsLen)
|
||||
}
|
||||
return args[0], nil
|
||||
}
|
||||
|
||||
// CreateSubcommandOptions is an options struct to support create subcommands
|
||||
type CreateSubcommandOptions struct {
|
||||
// PrintFlags holds options necessary for obtaining a printer
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
// Name of resource being created
|
||||
Name string
|
||||
// StructuredGenerator is the resource generator for the object being created
|
||||
StructuredGenerator generate.StructuredGenerator
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
CreateAnnotation bool
|
||||
FieldManager string
|
||||
ValidationDirective string
|
||||
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
|
||||
Mapper meta.RESTMapper
|
||||
DynamicClient dynamic.Interface
|
||||
|
||||
PrintObj printers.ResourcePrinterFunc
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewCreateSubcommandOptions returns initialized CreateSubcommandOptions
|
||||
func NewCreateSubcommandOptions(ioStreams genericiooptions.IOStreams) *CreateSubcommandOptions {
|
||||
return &CreateSubcommandOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// Complete completes all the required options
|
||||
func (o *CreateSubcommandOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string, generator generate.StructuredGenerator) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Name = name
|
||||
o.StructuredGenerator = generator
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PrintObj = func(obj kruntime.Object, out io.Writer) error {
|
||||
return printer.PrintObj(obj, out)
|
||||
}
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.DynamicClient, err = f.DynamicClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Mapper, err = f.ToRESTMapper()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run executes a create subcommand using the specified options
|
||||
func (o *CreateSubcommandOptions) Run() error {
|
||||
obj, err := o.StructuredGenerator.StructuredGenerate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, obj, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
// create subcommands have compiled knowledge of things they create, so type them directly
|
||||
gvks, _, err := scheme.Scheme.ObjectKinds(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gvk := gvks[0]
|
||||
mapping, err := o.Mapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
asUnstructured := &unstructured.Unstructured{}
|
||||
if err := scheme.Scheme.Convert(obj, asUnstructured, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if mapping.Scope.Name() == meta.RESTScopeNameRoot {
|
||||
o.Namespace = ""
|
||||
}
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
actualObject, err := o.DynamicClient.Resource(mapping.Resource).Namespace(o.Namespace).Create(context.TODO(), asUnstructured, createOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ensure we pass a versioned object to the printer
|
||||
obj = actualObject
|
||||
} else {
|
||||
if meta, err := meta.Accessor(obj); err == nil && o.EnforceNamespace {
|
||||
meta.SetNamespace(o.Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
return o.PrintObj(obj, o.Out)
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
cliflag "k8s.io/component-base/cli/flag"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
clusterRoleLong = templates.LongDesc(i18n.T(`
|
||||
Create a cluster role.`))
|
||||
|
||||
clusterRoleExample = templates.Examples(i18n.T(`
|
||||
# Create a cluster role named "pod-reader" that allows user to perform "get", "watch" and "list" on pods
|
||||
kubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods
|
||||
|
||||
# Create a cluster role named "pod-reader" with ResourceName specified
|
||||
kubectl create clusterrole pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
|
||||
|
||||
# Create a cluster role named "foo" with API Group specified
|
||||
kubectl create clusterrole foo --verb=get,list,watch --resource=rs.apps
|
||||
|
||||
# Create a cluster role named "foo" with SubResource specified
|
||||
kubectl create clusterrole foo --verb=get,list,watch --resource=pods,pods/status
|
||||
|
||||
# Create a cluster role name "foo" with NonResourceURL specified
|
||||
kubectl create clusterrole "foo" --verb=get --non-resource-url=/logs/*
|
||||
|
||||
# Create a cluster role name "monitoring" with AggregationRule specified
|
||||
kubectl create clusterrole monitoring --aggregation-rule="rbac.example.com/aggregate-to-monitoring=true"`))
|
||||
|
||||
// Valid nonResource verb list for validation.
|
||||
validNonResourceVerbs = []string{"*", "get", "post", "put", "delete", "patch", "head", "options"}
|
||||
)
|
||||
|
||||
// CreateClusterRoleOptions is returned by NewCmdCreateClusterRole
|
||||
type CreateClusterRoleOptions struct {
|
||||
*CreateRoleOptions
|
||||
NonResourceURLs []string
|
||||
AggregationRule map[string]string
|
||||
FieldManager string
|
||||
}
|
||||
|
||||
// NewCmdCreateClusterRole initializes and returns new ClusterRoles command
|
||||
func NewCmdCreateClusterRole(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
c := &CreateClusterRoleOptions{
|
||||
CreateRoleOptions: NewCreateRoleOptions(ioStreams),
|
||||
AggregationRule: map[string]string{},
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "clusterrole NAME --verb=verb --resource=resource.group [--resource-name=resourcename] [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a cluster role"),
|
||||
Long: clusterRoleLong,
|
||||
Example: clusterRoleExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(c.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(c.Validate())
|
||||
cmdutil.CheckErr(c.RunCreateRole())
|
||||
},
|
||||
}
|
||||
|
||||
c.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().StringSliceVar(&c.Verbs, "verb", c.Verbs, "Verb that applies to the resources contained in the rule")
|
||||
cmd.Flags().StringSliceVar(&c.NonResourceURLs, "non-resource-url", c.NonResourceURLs, "A partial url that user should have access to.")
|
||||
cmd.Flags().StringSlice("resource", []string{}, "Resource that the rule applies to")
|
||||
cmd.Flags().StringArrayVar(&c.ResourceNames, "resource-name", c.ResourceNames, "Resource in the white list that the rule applies to, repeat this flag for multiple items")
|
||||
cmd.Flags().Var(cliflag.NewMapStringString(&c.AggregationRule), "aggregation-rule", "An aggregation label selector for combining ClusterRoles.")
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &c.FieldManager, "kubectl-create")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options
|
||||
func (c *CreateClusterRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
// Remove duplicate nonResourceURLs
|
||||
nonResourceURLs := []string{}
|
||||
for _, n := range c.NonResourceURLs {
|
||||
if !arrayContains(nonResourceURLs, n) {
|
||||
nonResourceURLs = append(nonResourceURLs, n)
|
||||
}
|
||||
}
|
||||
c.NonResourceURLs = nonResourceURLs
|
||||
|
||||
return c.CreateRoleOptions.Complete(f, cmd, args)
|
||||
}
|
||||
|
||||
// Validate makes sure there is no discrepency in CreateClusterRoleOptions
|
||||
func (c *CreateClusterRoleOptions) Validate() error {
|
||||
if c.Name == "" {
|
||||
return fmt.Errorf("name must be specified")
|
||||
}
|
||||
|
||||
if len(c.AggregationRule) > 0 {
|
||||
if len(c.NonResourceURLs) > 0 || len(c.Verbs) > 0 || len(c.Resources) > 0 || len(c.ResourceNames) > 0 {
|
||||
return fmt.Errorf("aggregation rule must be specified without nonResourceURLs, verbs, resources or resourceNames")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validate verbs.
|
||||
if len(c.Verbs) == 0 {
|
||||
return fmt.Errorf("at least one verb must be specified")
|
||||
}
|
||||
|
||||
if len(c.Resources) == 0 && len(c.NonResourceURLs) == 0 {
|
||||
return fmt.Errorf("one of resource or nonResourceURL must be specified")
|
||||
}
|
||||
|
||||
// validate resources
|
||||
if len(c.Resources) > 0 {
|
||||
for _, v := range c.Verbs {
|
||||
if !arrayContains(validResourceVerbs, v) {
|
||||
fmt.Fprintf(c.ErrOut, "Warning: '%s' is not a standard resource verb\n", v)
|
||||
}
|
||||
}
|
||||
if err := c.validateResource(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//validate non-resource-url
|
||||
if len(c.NonResourceURLs) > 0 {
|
||||
for _, v := range c.Verbs {
|
||||
if !arrayContains(validNonResourceVerbs, v) {
|
||||
return fmt.Errorf("invalid verb: '%s' for nonResourceURL", v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, nonResourceURL := range c.NonResourceURLs {
|
||||
if nonResourceURL == "*" {
|
||||
continue
|
||||
}
|
||||
|
||||
if nonResourceURL == "" || !strings.HasPrefix(nonResourceURL, "/") {
|
||||
return fmt.Errorf("nonResourceURL should start with /")
|
||||
}
|
||||
|
||||
if strings.ContainsRune(nonResourceURL[:len(nonResourceURL)-1], '*') {
|
||||
return fmt.Errorf("nonResourceURL only supports wildcard matches when '*' is at the end")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// RunCreateRole creates a new clusterRole
|
||||
func (c *CreateClusterRoleOptions) RunCreateRole() error {
|
||||
clusterRole := &rbacv1.ClusterRole{
|
||||
// this is ok because we know exactly how we want to be serialized
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "ClusterRole"},
|
||||
}
|
||||
clusterRole.Name = c.Name
|
||||
|
||||
var err error
|
||||
if len(c.AggregationRule) == 0 {
|
||||
rules, err := generateResourcePolicyRules(c.Mapper, c.Verbs, c.Resources, c.ResourceNames, c.NonResourceURLs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clusterRole.Rules = rules
|
||||
} else {
|
||||
clusterRole.AggregationRule = &rbacv1.AggregationRule{
|
||||
ClusterRoleSelectors: []metav1.LabelSelector{
|
||||
{
|
||||
MatchLabels: c.AggregationRule,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if err := util.CreateOrUpdateAnnotation(c.CreateAnnotation, clusterRole, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create ClusterRole.
|
||||
if c.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if c.FieldManager != "" {
|
||||
createOptions.FieldManager = c.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = c.ValidationDirective
|
||||
if c.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
clusterRole, err = c.Client.ClusterRoles().Create(context.TODO(), clusterRole, createOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.PrintObj(clusterRole)
|
||||
}
|
228
vendor/k8s.io/kubectl/pkg/cmd/create/create_clusterrolebinding.go
generated
vendored
Normal file
228
vendor/k8s.io/kubectl/pkg/cmd/create/create_clusterrolebinding.go
generated
vendored
Normal file
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
rbacclientv1 "k8s.io/client-go/kubernetes/typed/rbac/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/completion"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
clusterRoleBindingLong = templates.LongDesc(i18n.T(`
|
||||
Create a cluster role binding for a particular cluster role.`))
|
||||
|
||||
clusterRoleBindingExample = templates.Examples(i18n.T(`
|
||||
# Create a cluster role binding for user1, user2, and group1 using the cluster-admin cluster role
|
||||
kubectl create clusterrolebinding cluster-admin --clusterrole=cluster-admin --user=user1 --user=user2 --group=group1`))
|
||||
)
|
||||
|
||||
// ClusterRoleBindingOptions is returned by NewCmdCreateClusterRoleBinding
|
||||
type ClusterRoleBindingOptions struct {
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj func(obj runtime.Object) error
|
||||
|
||||
Name string
|
||||
ClusterRole string
|
||||
Users []string
|
||||
Groups []string
|
||||
ServiceAccounts []string
|
||||
FieldManager string
|
||||
CreateAnnotation bool
|
||||
|
||||
Client rbacclientv1.RbacV1Interface
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewClusterRoleBindingOptions creates a new *ClusterRoleBindingOptions with sane defaults
|
||||
func NewClusterRoleBindingOptions(ioStreams genericiooptions.IOStreams) *ClusterRoleBindingOptions {
|
||||
return &ClusterRoleBindingOptions{
|
||||
Users: []string{},
|
||||
Groups: []string{},
|
||||
ServiceAccounts: []string{},
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreateClusterRoleBinding returns an initialized command instance of ClusterRoleBinding
|
||||
func NewCmdCreateClusterRoleBinding(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewClusterRoleBindingOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "clusterrolebinding NAME --clusterrole=NAME [--user=username] [--group=groupname] [--serviceaccount=namespace:serviceaccountname] [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a cluster role binding for a particular cluster role"),
|
||||
Long: clusterRoleBindingLong,
|
||||
Example: clusterRoleBindingExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().StringVar(&o.ClusterRole, "clusterrole", "", i18n.T("ClusterRole this ClusterRoleBinding should reference"))
|
||||
cmd.MarkFlagRequired("clusterrole")
|
||||
cmd.Flags().StringArrayVar(&o.Users, "user", o.Users, "Usernames to bind to the clusterrole. The flag can be repeated to add multiple users.")
|
||||
cmd.Flags().StringArrayVar(&o.Groups, "group", o.Groups, "Groups to bind to the clusterrole. The flag can be repeated to add multiple groups.")
|
||||
cmd.Flags().StringArrayVar(&o.ServiceAccounts, "serviceaccount", o.ServiceAccounts, "Service accounts to bind to the clusterrole, in the format <namespace>:<name>. The flag can be repeated to add multiple service accounts.")
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
|
||||
// Completion for relevant flags
|
||||
cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc(
|
||||
"clusterrole",
|
||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return completion.CompGetResource(f, "clusterrole", toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
}))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options
|
||||
func (o *ClusterRoleBindingOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
o.Name, err = NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs, err := f.KubernetesClientSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Client = cs.RbacV1()
|
||||
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run calls the CreateSubcommandOptions.Run in ClusterRoleBindingOptions instance
|
||||
func (o *ClusterRoleBindingOptions) Run() error {
|
||||
clusterRoleBinding, err := o.createClusterRoleBinding()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, clusterRoleBinding, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
var err error
|
||||
clusterRoleBinding, err = o.Client.ClusterRoleBindings().Create(context.TODO(), clusterRoleBinding, createOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create clusterrolebinding: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return o.PrintObj(clusterRoleBinding)
|
||||
}
|
||||
|
||||
func (o *ClusterRoleBindingOptions) createClusterRoleBinding() (*rbacv1.ClusterRoleBinding, error) {
|
||||
clusterRoleBinding := &rbacv1.ClusterRoleBinding{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "ClusterRoleBinding"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.Name,
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Kind: "ClusterRole",
|
||||
Name: o.ClusterRole,
|
||||
},
|
||||
}
|
||||
|
||||
for _, user := range o.Users {
|
||||
clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbacv1.Subject{
|
||||
Kind: rbacv1.UserKind,
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Name: user,
|
||||
})
|
||||
}
|
||||
|
||||
for _, group := range o.Groups {
|
||||
clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbacv1.Subject{
|
||||
Kind: rbacv1.GroupKind,
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Name: group,
|
||||
})
|
||||
}
|
||||
|
||||
for _, sa := range o.ServiceAccounts {
|
||||
tokens := strings.Split(sa, ":")
|
||||
if len(tokens) != 2 || tokens[0] == "" || tokens[1] == "" {
|
||||
return nil, fmt.Errorf("serviceaccount must be <namespace>:<name>")
|
||||
}
|
||||
clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbacv1.Subject{
|
||||
Kind: rbacv1.ServiceAccountKind,
|
||||
APIGroup: "",
|
||||
Namespace: tokens[0],
|
||||
Name: tokens[1],
|
||||
})
|
||||
}
|
||||
|
||||
return clusterRoleBinding, nil
|
||||
}
|
|
@ -0,0 +1,414 @@
|
|||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/hash"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
configMapLong = templates.LongDesc(i18n.T(`
|
||||
Create a config map based on a file, directory, or specified literal value.
|
||||
|
||||
A single config map may package one or more key/value pairs.
|
||||
|
||||
When creating a config map based on a file, the key will default to the basename of the file, and the value will
|
||||
default to the file content. If the basename is an invalid key, you may specify an alternate key.
|
||||
|
||||
When creating a config map based on a directory, each file whose basename is a valid key in the directory will be
|
||||
packaged into the config map. Any directory entries except regular files are ignored (e.g. subdirectories,
|
||||
symlinks, devices, pipes, etc).`))
|
||||
|
||||
configMapExample = templates.Examples(i18n.T(`
|
||||
# Create a new config map named my-config based on folder bar
|
||||
kubectl create configmap my-config --from-file=path/to/bar
|
||||
|
||||
# Create a new config map named my-config with specified keys instead of file basenames on disk
|
||||
kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt
|
||||
|
||||
# Create a new config map named my-config with key1=config1 and key2=config2
|
||||
kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2
|
||||
|
||||
# Create a new config map named my-config from the key=value pairs in the file
|
||||
kubectl create configmap my-config --from-file=path/to/bar
|
||||
|
||||
# Create a new config map named my-config from an env file
|
||||
kubectl create configmap my-config --from-env-file=path/to/foo.env --from-env-file=path/to/bar.env`))
|
||||
)
|
||||
|
||||
// ConfigMapOptions holds properties for create configmap sub-command
|
||||
type ConfigMapOptions struct {
|
||||
// PrintFlags holds options necessary for obtaining a printer
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj func(obj runtime.Object) error
|
||||
|
||||
// Name of configMap (required)
|
||||
Name string
|
||||
// Type of configMap (optional)
|
||||
Type string
|
||||
// FileSources to derive the configMap from (optional)
|
||||
FileSources []string
|
||||
// LiteralSources to derive the configMap from (optional)
|
||||
LiteralSources []string
|
||||
// EnvFileSources to derive the configMap from (optional)
|
||||
EnvFileSources []string
|
||||
// AppendHash; if true, derive a hash from the ConfigMap and append it to the name
|
||||
AppendHash bool
|
||||
|
||||
FieldManager string
|
||||
CreateAnnotation bool
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
|
||||
Client corev1client.CoreV1Interface
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewConfigMapOptions creates a new *ConfigMapOptions with default value
|
||||
func NewConfigMapOptions(ioStreams genericiooptions.IOStreams) *ConfigMapOptions {
|
||||
return &ConfigMapOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreateConfigMap creates the `create configmap` Cobra command
|
||||
func NewCmdCreateConfigMap(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewConfigMapOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Aliases: []string{"cm"},
|
||||
Short: i18n.T("Create a config map from a local file, directory or literal value"),
|
||||
Long: configMapLong,
|
||||
Example: configMapExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
|
||||
cmd.Flags().StringSliceVar(&o.FileSources, "from-file", o.FileSources, "Key file can be specified using its file path, in which case file basename will be used as configmap key, or optionally with a key and file path, in which case the given key will be used. Specifying a directory will iterate each named file in the directory whose basename is a valid configmap key.")
|
||||
cmd.Flags().StringArrayVar(&o.LiteralSources, "from-literal", o.LiteralSources, "Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
|
||||
cmd.Flags().StringSliceVar(&o.EnvFileSources, "from-env-file", o.EnvFileSources, "Specify the path to a file to read lines of key=val pairs to create a configmap.")
|
||||
cmd.Flags().BoolVar(&o.AppendHash, "append-hash", o.AppendHash, "Append a hash of the configmap to its name.")
|
||||
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete loads data from the command line environment
|
||||
func (o *ConfigMapOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
o.Name, err = NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
restConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Client, err = corev1client.NewForConfig(restConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks if ConfigMapOptions has sufficient value to run
|
||||
func (o *ConfigMapOptions) Validate() error {
|
||||
if len(o.Name) == 0 {
|
||||
return fmt.Errorf("name must be specified")
|
||||
}
|
||||
if len(o.EnvFileSources) > 0 && (len(o.FileSources) > 0 || len(o.LiteralSources) > 0) {
|
||||
return fmt.Errorf("from-env-file cannot be combined with from-file or from-literal")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run calls createConfigMap and filled in value for configMap object
|
||||
func (o *ConfigMapOptions) Run() error {
|
||||
configMap, err := o.createConfigMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, configMap, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
configMap, err = o.Client.ConfigMaps(o.Namespace).Create(context.TODO(), configMap, createOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create configmap: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return o.PrintObj(configMap)
|
||||
}
|
||||
|
||||
// createConfigMap fills in key value pair from the information given in
|
||||
// ConfigMapOptions into *corev1.ConfigMap
|
||||
func (o *ConfigMapOptions) createConfigMap() (*corev1.ConfigMap, error) {
|
||||
namespace := ""
|
||||
if o.EnforceNamespace {
|
||||
namespace = o.Namespace
|
||||
}
|
||||
|
||||
configMap := &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: corev1.SchemeGroupVersion.String(),
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.Name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
configMap.Name = o.Name
|
||||
configMap.Data = map[string]string{}
|
||||
configMap.BinaryData = map[string][]byte{}
|
||||
|
||||
if len(o.FileSources) > 0 {
|
||||
if err := handleConfigMapFromFileSources(configMap, o.FileSources); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(o.LiteralSources) > 0 {
|
||||
if err := handleConfigMapFromLiteralSources(configMap, o.LiteralSources); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(o.EnvFileSources) > 0 {
|
||||
if err := handleConfigMapFromEnvFileSources(configMap, o.EnvFileSources); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if o.AppendHash {
|
||||
hash, err := hash.ConfigMapHash(configMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configMap.Name = fmt.Sprintf("%s-%s", configMap.Name, hash)
|
||||
}
|
||||
|
||||
return configMap, nil
|
||||
}
|
||||
|
||||
// handleConfigMapFromLiteralSources adds the specified literal source
|
||||
// information into the provided configMap.
|
||||
func handleConfigMapFromLiteralSources(configMap *corev1.ConfigMap, literalSources []string) error {
|
||||
for _, literalSource := range literalSources {
|
||||
keyName, value, err := util.ParseLiteralSource(literalSource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = addKeyFromLiteralToConfigMap(configMap, keyName, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleConfigMapFromFileSources adds the specified file source information
|
||||
// into the provided configMap
|
||||
func handleConfigMapFromFileSources(configMap *corev1.ConfigMap, fileSources []string) error {
|
||||
for _, fileSource := range fileSources {
|
||||
keyName, filePath, err := util.ParseFileSource(fileSource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *os.PathError:
|
||||
return fmt.Errorf("error reading %s: %v", filePath, err.Err)
|
||||
default:
|
||||
return fmt.Errorf("error reading %s: %v", filePath, err)
|
||||
}
|
||||
|
||||
}
|
||||
if info.IsDir() {
|
||||
if strings.Contains(fileSource, "=") {
|
||||
return fmt.Errorf("cannot give a key name for a directory path")
|
||||
}
|
||||
fileList, err := os.ReadDir(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing files in %s: %v", filePath, err)
|
||||
}
|
||||
for _, item := range fileList {
|
||||
itemPath := path.Join(filePath, item.Name())
|
||||
if item.Type().IsRegular() {
|
||||
keyName = item.Name()
|
||||
err = addKeyFromFileToConfigMap(configMap, keyName, itemPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := addKeyFromFileToConfigMap(configMap, keyName, filePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleConfigMapFromEnvFileSources adds the specified env file source information
|
||||
// into the provided configMap
|
||||
func handleConfigMapFromEnvFileSources(configMap *corev1.ConfigMap, envFileSources []string) error {
|
||||
for _, envFileSource := range envFileSources {
|
||||
info, err := os.Stat(envFileSource)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *os.PathError:
|
||||
return fmt.Errorf("error reading %s: %v", envFileSource, err.Err)
|
||||
default:
|
||||
return fmt.Errorf("error reading %s: %v", envFileSource, err)
|
||||
}
|
||||
}
|
||||
if info.IsDir() {
|
||||
return fmt.Errorf("env config file cannot be a directory")
|
||||
}
|
||||
err = cmdutil.AddFromEnvFile(envFileSource, func(key, value string) error {
|
||||
return addKeyFromLiteralToConfigMap(configMap, key, value)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addKeyFromFileToConfigMap adds a key with the given name to a ConfigMap, populating
|
||||
// the value with the content of the given file path, or returns an error.
|
||||
func addKeyFromFileToConfigMap(configMap *corev1.ConfigMap, keyName, filePath string) error {
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if utf8.Valid(data) {
|
||||
return addKeyFromLiteralToConfigMap(configMap, keyName, string(data))
|
||||
}
|
||||
err = validateNewConfigMap(configMap, keyName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configMap.BinaryData[keyName] = data
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addKeyFromLiteralToConfigMap adds the given key and data to the given config map,
|
||||
// returning an error if the key is not valid or if the key already exists.
|
||||
func addKeyFromLiteralToConfigMap(configMap *corev1.ConfigMap, keyName, data string) error {
|
||||
err := validateNewConfigMap(configMap, keyName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configMap.Data[keyName] = data
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateNewConfigMap checks whether the keyname is valid
|
||||
// Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys.
|
||||
func validateNewConfigMap(configMap *corev1.ConfigMap, keyName string) error {
|
||||
if errs := validation.IsConfigMapKey(keyName); len(errs) > 0 {
|
||||
return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ","))
|
||||
}
|
||||
if _, exists := configMap.Data[keyName]; exists {
|
||||
return fmt.Errorf("cannot add key %q, another key by that name already exists in Data for ConfigMap %q", keyName, configMap.Name)
|
||||
}
|
||||
if _, exists := configMap.BinaryData[keyName]; exists {
|
||||
return fmt.Errorf("cannot add key %q, another key by that name already exists in BinaryData for ConfigMap %q", keyName, configMap.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
Copyright 2018 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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
batchv1client "k8s.io/client-go/kubernetes/typed/batch/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
cronjobLong = templates.LongDesc(i18n.T(`
|
||||
Create a cron job with the specified name.`))
|
||||
|
||||
cronjobExample = templates.Examples(`
|
||||
# Create a cron job
|
||||
kubectl create cronjob my-job --image=busybox --schedule="*/1 * * * *"
|
||||
|
||||
# Create a cron job with a command
|
||||
kubectl create cronjob my-job --image=busybox --schedule="*/1 * * * *" -- date`)
|
||||
)
|
||||
|
||||
// CreateCronJobOptions is returned by NewCreateCronJobOptions
|
||||
type CreateCronJobOptions struct {
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
|
||||
PrintObj func(obj runtime.Object) error
|
||||
|
||||
Name string
|
||||
Image string
|
||||
Schedule string
|
||||
Command []string
|
||||
Restart string
|
||||
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
Client batchv1client.BatchV1Interface
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
Builder *resource.Builder
|
||||
FieldManager string
|
||||
CreateAnnotation bool
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewCreateCronJobOptions returns an initialized CreateCronJobOptions instance
|
||||
func NewCreateCronJobOptions(ioStreams genericiooptions.IOStreams) *CreateCronJobOptions {
|
||||
return &CreateCronJobOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreateCronJob is a command to create CronJobs.
|
||||
func NewCmdCreateCronJob(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewCreateCronJobOptions(ioStreams)
|
||||
cmd := &cobra.Command{
|
||||
Use: "cronjob NAME --image=image --schedule='0/5 * * * ?' -- [COMMAND] [args...]",
|
||||
DisableFlagsInUseLine: false,
|
||||
Aliases: []string{"cj"},
|
||||
Short: i18n.T("Create a cron job with the specified name"),
|
||||
Long: cronjobLong,
|
||||
Example: cronjobExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().StringVar(&o.Image, "image", o.Image, "Image name to run.")
|
||||
cmd.MarkFlagRequired("image")
|
||||
cmd.Flags().StringVar(&o.Schedule, "schedule", o.Schedule, "A schedule in the Cron format the job should be run with.")
|
||||
cmd.MarkFlagRequired("schedule")
|
||||
cmd.Flags().StringVar(&o.Restart, "restart", o.Restart, "job's restart policy. supported values: OnFailure, Never")
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options
|
||||
func (o *CreateCronJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Name = name
|
||||
if len(args) > 1 {
|
||||
o.Command = args[1:]
|
||||
}
|
||||
if len(o.Restart) == 0 {
|
||||
o.Restart = "OnFailure"
|
||||
}
|
||||
|
||||
clientConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Client, err = batchv1client.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Builder = f.NewBuilder()
|
||||
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run performs the execution of 'create cronjob' sub command
|
||||
func (o *CreateCronJobOptions) Run() error {
|
||||
cronJob := o.createCronJob()
|
||||
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, cronJob, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
var err error
|
||||
cronJob, err = o.Client.CronJobs(o.Namespace).Create(context.TODO(), cronJob, createOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create cronjob: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return o.PrintObj(cronJob)
|
||||
}
|
||||
|
||||
func (o *CreateCronJobOptions) createCronJob() *batchv1.CronJob {
|
||||
cronjob := &batchv1.CronJob{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "CronJob"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.Name,
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: o.Schedule,
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.Name,
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: o.Name,
|
||||
Image: o.Image,
|
||||
Command: o.Command,
|
||||
},
|
||||
},
|
||||
RestartPolicy: corev1.RestartPolicy(o.Restart),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if o.EnforceNamespace {
|
||||
cronjob.Namespace = o.Namespace
|
||||
}
|
||||
return cronjob
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilrand "k8s.io/apimachinery/pkg/util/rand"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
deploymentLong = templates.LongDesc(i18n.T(`
|
||||
Create a deployment with the specified name.`))
|
||||
|
||||
deploymentExample = templates.Examples(i18n.T(`
|
||||
# Create a deployment named my-dep that runs the busybox image
|
||||
kubectl create deployment my-dep --image=busybox
|
||||
|
||||
# Create a deployment with a command
|
||||
kubectl create deployment my-dep --image=busybox -- date
|
||||
|
||||
# Create a deployment named my-dep that runs the nginx image with 3 replicas
|
||||
kubectl create deployment my-dep --image=nginx --replicas=3
|
||||
|
||||
# Create a deployment named my-dep that runs the busybox image and expose port 5701
|
||||
kubectl create deployment my-dep --image=busybox --port=5701
|
||||
|
||||
# Create a deployment named my-dep that runs multiple containers
|
||||
kubectl create deployment my-dep --image=busybox:latest --image=ubuntu:latest --image=nginx`))
|
||||
)
|
||||
|
||||
// CreateDeploymentOptions is returned by NewCmdCreateDeployment
|
||||
type CreateDeploymentOptions struct {
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
|
||||
PrintObj func(obj runtime.Object) error
|
||||
|
||||
Name string
|
||||
Images []string
|
||||
Port int32
|
||||
Replicas int32
|
||||
Command []string
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
FieldManager string
|
||||
CreateAnnotation bool
|
||||
|
||||
Client appsv1client.AppsV1Interface
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewCreateDeploymentOptions returns an initialized CreateDeploymentOptions instance
|
||||
func NewCreateDeploymentOptions(ioStreams genericiooptions.IOStreams) *CreateDeploymentOptions {
|
||||
return &CreateDeploymentOptions{
|
||||
Port: -1,
|
||||
Replicas: 1,
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreateDeployment is a macro command to create a new deployment.
|
||||
// This command is better known to users as `kubectl create deployment`.
|
||||
func NewCmdCreateDeployment(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewCreateDeploymentOptions(ioStreams)
|
||||
cmd := &cobra.Command{
|
||||
Use: "deployment NAME --image=image -- [COMMAND] [args...]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Aliases: []string{"deploy"},
|
||||
Short: i18n.T("Create a deployment with the specified name"),
|
||||
Long: deploymentLong,
|
||||
Example: deploymentExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().StringSliceVar(&o.Images, "image", o.Images, "Image names to run. A deployment can have multiple images set for multi-container pod.")
|
||||
cmd.MarkFlagRequired("image")
|
||||
cmd.Flags().Int32Var(&o.Port, "port", o.Port, "The containerPort that this deployment exposes.")
|
||||
cmd.Flags().Int32VarP(&o.Replicas, "replicas", "r", o.Replicas, "Number of replicas to create. Default is 1.")
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the options
|
||||
func (o *CreateDeploymentOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Name = name
|
||||
if len(args) > 1 {
|
||||
o.Command = args[1:]
|
||||
}
|
||||
|
||||
clientConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Client, err = appsv1client.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate makes sure there is no discrepency in provided option values
|
||||
func (o *CreateDeploymentOptions) Validate() error {
|
||||
if len(o.Images) > 1 && len(o.Command) > 0 {
|
||||
return fmt.Errorf("cannot specify multiple --image options and command")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run performs the execution of 'create deployment' sub command
|
||||
func (o *CreateDeploymentOptions) Run() error {
|
||||
deploy := o.createDeployment()
|
||||
|
||||
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, deploy, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
var err error
|
||||
deploy, err = o.Client.Deployments(o.Namespace).Create(context.TODO(), deploy, createOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create deployment: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return o.PrintObj(deploy)
|
||||
}
|
||||
|
||||
func (o *CreateDeploymentOptions) createDeployment() *appsv1.Deployment {
|
||||
labels := map[string]string{"app": o.Name}
|
||||
selector := metav1.LabelSelector{MatchLabels: labels}
|
||||
namespace := ""
|
||||
if o.EnforceNamespace {
|
||||
namespace = o.Namespace
|
||||
}
|
||||
|
||||
deploy := &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String(), Kind: "Deployment"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.Name,
|
||||
Labels: labels,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: &o.Replicas,
|
||||
Selector: &selector,
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: o.buildPodSpec(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if o.Port >= 0 && len(deploy.Spec.Template.Spec.Containers) > 0 {
|
||||
deploy.Spec.Template.Spec.Containers[0].Ports = []corev1.ContainerPort{{ContainerPort: o.Port}}
|
||||
}
|
||||
return deploy
|
||||
}
|
||||
|
||||
// buildPodSpec parses the image strings and assemble them into the Containers
|
||||
// of a PodSpec. This is all you need to create the PodSpec for a deployment.
|
||||
func (o *CreateDeploymentOptions) buildPodSpec() corev1.PodSpec {
|
||||
podSpec := corev1.PodSpec{Containers: []corev1.Container{}}
|
||||
for _, imageString := range o.Images {
|
||||
// Retain just the image name
|
||||
imageSplit := strings.Split(imageString, "/")
|
||||
name := imageSplit[len(imageSplit)-1]
|
||||
// Remove any tag or hash
|
||||
if strings.Contains(name, ":") {
|
||||
name = strings.Split(name, ":")[0]
|
||||
}
|
||||
if strings.Contains(name, "@") {
|
||||
name = strings.Split(name, "@")[0]
|
||||
}
|
||||
name = sanitizeAndUniquify(name)
|
||||
podSpec.Containers = append(podSpec.Containers, corev1.Container{
|
||||
Name: name,
|
||||
Image: imageString,
|
||||
Command: o.Command,
|
||||
})
|
||||
}
|
||||
return podSpec
|
||||
}
|
||||
|
||||
// sanitizeAndUniquify replaces characters like "." or "_" into "-" to follow DNS1123 rules.
|
||||
// Then add random suffix to make it uniquified.
|
||||
func sanitizeAndUniquify(name string) string {
|
||||
if strings.ContainsAny(name, "_.") {
|
||||
name = strings.Replace(name, "_", "-", -1)
|
||||
name = strings.Replace(name, ".", "-", -1)
|
||||
name = fmt.Sprintf("%s-%s", name, utilrand.String(5))
|
||||
}
|
||||
return name
|
||||
}
|
|
@ -0,0 +1,458 @@
|
|||
/*
|
||||
Copyright 2020 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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
networkingv1client "k8s.io/client-go/kubernetes/typed/networking/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
// Explaining the Regex below:
|
||||
// ^(?P<host>[\w\*\-\.]*) -> Indicates the host - 0-N characters of letters, number, underscore, '-', '.' and '*'
|
||||
// (?P<path>/.*) -> Indicates the path and MUST start with '/' - / + 0-N characters
|
||||
// Separator from host/path to svcname:svcport -> "="
|
||||
// (?P<svcname>[\w\-]+) -> Service Name (letters, numbers, '-') -> 1-N characters
|
||||
// Separator from svcname to svcport -> ":"
|
||||
// (?P<svcport>[\w\-]+) -> Service Port (letters, numbers, '-') -> 1-N characters
|
||||
regexHostPathSvc = `^(?P<host>[\w\*\-\.]*)(?P<path>/.*)=(?P<svcname>[\w\-]+):(?P<svcport>[\w\-]+)`
|
||||
|
||||
// This Regex is optional -> (....)?
|
||||
// (?P<istls>tls) -> Verify if the argument after "," is 'tls'
|
||||
// Optional Separator from tls to the secret name -> "=?"
|
||||
// (?P<secretname>[\w\-]+)? -> Optional secret name after the separator -> 1-N characters
|
||||
regexTLS = `(,(?P<istls>tls)=?(?P<secretname>[\w\-]+)?)?`
|
||||
|
||||
// The validation Regex is the concatenation of hostPathSvc validation regex
|
||||
// and the TLS validation regex
|
||||
ruleRegex = regexHostPathSvc + regexTLS
|
||||
|
||||
ingressLong = templates.LongDesc(i18n.T(`
|
||||
Create an ingress with the specified name.`))
|
||||
|
||||
ingressExample = templates.Examples(i18n.T(`
|
||||
# Create a single ingress called 'simple' that directs requests to foo.com/bar to svc
|
||||
# svc1:8080 with a TLS secret "my-cert"
|
||||
kubectl create ingress simple --rule="foo.com/bar=svc1:8080,tls=my-cert"
|
||||
|
||||
# Create a catch all ingress of "/path" pointing to service svc:port and Ingress Class as "otheringress"
|
||||
kubectl create ingress catch-all --class=otheringress --rule="/path=svc:port"
|
||||
|
||||
# Create an ingress with two annotations: ingress.annotation1 and ingress.annotations2
|
||||
kubectl create ingress annotated --class=default --rule="foo.com/bar=svc:port" \
|
||||
--annotation ingress.annotation1=foo \
|
||||
--annotation ingress.annotation2=bla
|
||||
|
||||
# Create an ingress with the same host and multiple paths
|
||||
kubectl create ingress multipath --class=default \
|
||||
--rule="foo.com/=svc:port" \
|
||||
--rule="foo.com/admin/=svcadmin:portadmin"
|
||||
|
||||
# Create an ingress with multiple hosts and the pathType as Prefix
|
||||
kubectl create ingress ingress1 --class=default \
|
||||
--rule="foo.com/path*=svc:8080" \
|
||||
--rule="bar.com/admin*=svc2:http"
|
||||
|
||||
# Create an ingress with TLS enabled using the default ingress certificate and different path types
|
||||
kubectl create ingress ingtls --class=default \
|
||||
--rule="foo.com/=svc:https,tls" \
|
||||
--rule="foo.com/path/subpath*=othersvc:8080"
|
||||
|
||||
# Create an ingress with TLS enabled using a specific secret and pathType as Prefix
|
||||
kubectl create ingress ingsecret --class=default \
|
||||
--rule="foo.com/*=svc:8080,tls=secret1"
|
||||
|
||||
# Create an ingress with a default backend
|
||||
kubectl create ingress ingdefault --class=default \
|
||||
--default-backend=defaultsvc:http \
|
||||
--rule="foo.com/*=svc:8080,tls=secret1"
|
||||
|
||||
`))
|
||||
)
|
||||
|
||||
// CreateIngressOptions is returned by NewCmdCreateIngress
|
||||
type CreateIngressOptions struct {
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
|
||||
PrintObj func(obj runtime.Object) error
|
||||
|
||||
Name string
|
||||
IngressClass string
|
||||
Rules []string
|
||||
Annotations []string
|
||||
DefaultBackend string
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
CreateAnnotation bool
|
||||
|
||||
Client networkingv1client.NetworkingV1Interface
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
|
||||
FieldManager string
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewCreateIngressOptions creates the CreateIngressOptions to be used later
|
||||
func NewCreateIngressOptions(ioStreams genericiooptions.IOStreams) *CreateIngressOptions {
|
||||
return &CreateIngressOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreateIngress is a macro command to create a new ingress.
|
||||
// This command is better known to users as `kubectl create ingress`.
|
||||
func NewCmdCreateIngress(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewCreateIngressOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "ingress NAME --rule=host/path=service:port[,tls[=secret]] ",
|
||||
DisableFlagsInUseLine: true,
|
||||
Aliases: []string{"ing"},
|
||||
Short: i18n.T("Create an ingress with the specified name"),
|
||||
Long: ingressLong,
|
||||
Example: ingressExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().StringVar(&o.IngressClass, "class", o.IngressClass, "Ingress Class to be used")
|
||||
cmd.Flags().StringArrayVar(&o.Rules, "rule", o.Rules, "Rule in format host/path=service:port[,tls=secretname]. Paths containing the leading character '*' are considered pathType=Prefix. tls argument is optional.")
|
||||
cmd.Flags().StringVar(&o.DefaultBackend, "default-backend", o.DefaultBackend, "Default service for backend, in format of svcname:port")
|
||||
cmd.Flags().StringArrayVar(&o.Annotations, "annotation", o.Annotations, "Annotation to insert in the ingress object, in the format annotation=value")
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the options
|
||||
func (o *CreateIngressOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Name = name
|
||||
|
||||
clientConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Client, err = networkingv1client.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate validates the Ingress object to be created
|
||||
func (o *CreateIngressOptions) Validate() error {
|
||||
if len(o.DefaultBackend) == 0 && len(o.Rules) == 0 {
|
||||
return fmt.Errorf("not enough information provided: every ingress has to either specify a default-backend (which catches all traffic) or a list of rules (which catch specific paths)")
|
||||
}
|
||||
|
||||
rulevalidation, err := regexp.Compile(ruleRegex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compile the regex")
|
||||
}
|
||||
|
||||
for _, rule := range o.Rules {
|
||||
if match := rulevalidation.MatchString(rule); !match {
|
||||
return fmt.Errorf("rule %s is invalid and should be in format host/path=svcname:svcport[,tls[=secret]]", rule)
|
||||
}
|
||||
}
|
||||
|
||||
for _, annotation := range o.Annotations {
|
||||
if an := strings.SplitN(annotation, "=", 2); len(an) != 2 {
|
||||
return fmt.Errorf("annotation %s is invalid and should be in format key=[value]", annotation)
|
||||
}
|
||||
}
|
||||
|
||||
if len(o.DefaultBackend) > 0 && len(strings.Split(o.DefaultBackend, ":")) != 2 {
|
||||
return fmt.Errorf("default-backend should be in format servicename:serviceport")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run performs the execution of 'create ingress' sub command
|
||||
func (o *CreateIngressOptions) Run() error {
|
||||
ingress := o.createIngress()
|
||||
|
||||
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, ingress, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
var err error
|
||||
ingress, err = o.Client.Ingresses(o.Namespace).Create(context.TODO(), ingress, createOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create ingress: %v", err)
|
||||
}
|
||||
}
|
||||
return o.PrintObj(ingress)
|
||||
}
|
||||
|
||||
func (o *CreateIngressOptions) createIngress() *networkingv1.Ingress {
|
||||
namespace := ""
|
||||
if o.EnforceNamespace {
|
||||
namespace = o.Namespace
|
||||
}
|
||||
|
||||
annotations := o.buildAnnotations()
|
||||
spec := o.buildIngressSpec()
|
||||
|
||||
ingress := &networkingv1.Ingress{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: networkingv1.SchemeGroupVersion.String(), Kind: "Ingress"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.Name,
|
||||
Namespace: namespace,
|
||||
Annotations: annotations,
|
||||
},
|
||||
Spec: spec,
|
||||
}
|
||||
return ingress
|
||||
}
|
||||
|
||||
func (o *CreateIngressOptions) buildAnnotations() map[string]string {
|
||||
|
||||
var annotations = make(map[string]string)
|
||||
|
||||
for _, annotation := range o.Annotations {
|
||||
an := strings.SplitN(annotation, "=", 2)
|
||||
annotations[an[0]] = an[1]
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
|
||||
// buildIngressSpec builds the .spec from the diverse arguments passed to kubectl
|
||||
func (o *CreateIngressOptions) buildIngressSpec() networkingv1.IngressSpec {
|
||||
var ingressSpec networkingv1.IngressSpec
|
||||
|
||||
if len(o.IngressClass) > 0 {
|
||||
ingressSpec.IngressClassName = &o.IngressClass
|
||||
}
|
||||
|
||||
if len(o.DefaultBackend) > 0 {
|
||||
defaultbackend := buildIngressBackendSvc(o.DefaultBackend)
|
||||
ingressSpec.DefaultBackend = &defaultbackend
|
||||
}
|
||||
ingressSpec.TLS = o.buildTLSRules()
|
||||
ingressSpec.Rules = o.buildIngressRules()
|
||||
|
||||
return ingressSpec
|
||||
}
|
||||
|
||||
func (o *CreateIngressOptions) buildTLSRules() []networkingv1.IngressTLS {
|
||||
hostAlreadyPresent := make(map[string]struct{})
|
||||
|
||||
ingressTLSs := []networkingv1.IngressTLS{}
|
||||
var secret string
|
||||
|
||||
for _, rule := range o.Rules {
|
||||
tls := strings.Split(rule, ",")
|
||||
|
||||
if len(tls) == 2 {
|
||||
ingressTLS := networkingv1.IngressTLS{}
|
||||
host := strings.SplitN(rule, "/", 2)[0]
|
||||
secret = ""
|
||||
secretName := strings.Split(tls[1], "=")
|
||||
|
||||
if len(secretName) > 1 {
|
||||
secret = secretName[1]
|
||||
}
|
||||
|
||||
idxSecret := getIndexSecret(secret, ingressTLSs)
|
||||
// We accept the same host into TLS secrets only once
|
||||
if _, ok := hostAlreadyPresent[host]; !ok {
|
||||
if idxSecret > -1 {
|
||||
ingressTLSs[idxSecret].Hosts = append(ingressTLSs[idxSecret].Hosts, host)
|
||||
hostAlreadyPresent[host] = struct{}{}
|
||||
continue
|
||||
}
|
||||
if host != "" {
|
||||
ingressTLS.Hosts = append(ingressTLS.Hosts, host)
|
||||
}
|
||||
if secret != "" {
|
||||
ingressTLS.SecretName = secret
|
||||
}
|
||||
if len(ingressTLS.SecretName) > 0 || len(ingressTLS.Hosts) > 0 {
|
||||
ingressTLSs = append(ingressTLSs, ingressTLS)
|
||||
}
|
||||
hostAlreadyPresent[host] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ingressTLSs
|
||||
}
|
||||
|
||||
// buildIngressRules builds the .spec.rules for an ingress object.
|
||||
func (o *CreateIngressOptions) buildIngressRules() []networkingv1.IngressRule {
|
||||
ingressRules := []networkingv1.IngressRule{}
|
||||
|
||||
for _, rule := range o.Rules {
|
||||
removeTLS := strings.Split(rule, ",")[0]
|
||||
hostSplit := strings.SplitN(removeTLS, "/", 2)
|
||||
host := hostSplit[0]
|
||||
ingressPath := buildHTTPIngressPath(hostSplit[1])
|
||||
ingressRule := networkingv1.IngressRule{}
|
||||
|
||||
if host != "" {
|
||||
ingressRule.Host = host
|
||||
}
|
||||
|
||||
idxHost := getIndexHost(ingressRule.Host, ingressRules)
|
||||
if idxHost > -1 {
|
||||
ingressRules[idxHost].IngressRuleValue.HTTP.Paths = append(ingressRules[idxHost].IngressRuleValue.HTTP.Paths, ingressPath)
|
||||
continue
|
||||
}
|
||||
|
||||
ingressRule.IngressRuleValue = networkingv1.IngressRuleValue{
|
||||
HTTP: &networkingv1.HTTPIngressRuleValue{
|
||||
Paths: []networkingv1.HTTPIngressPath{
|
||||
ingressPath,
|
||||
},
|
||||
},
|
||||
}
|
||||
ingressRules = append(ingressRules, ingressRule)
|
||||
}
|
||||
return ingressRules
|
||||
}
|
||||
|
||||
func buildHTTPIngressPath(pathsvc string) networkingv1.HTTPIngressPath {
|
||||
pathsvcsplit := strings.Split(pathsvc, "=")
|
||||
path := "/" + pathsvcsplit[0]
|
||||
service := pathsvcsplit[1]
|
||||
|
||||
var pathType networkingv1.PathType
|
||||
pathType = "Exact"
|
||||
|
||||
// If * in the End, turn pathType=Prefix but remove the * from the end
|
||||
if path[len(path)-1:] == "*" {
|
||||
pathType = "Prefix"
|
||||
path = path[0 : len(path)-1]
|
||||
}
|
||||
|
||||
httpIngressPath := networkingv1.HTTPIngressPath{
|
||||
Path: path,
|
||||
PathType: &pathType,
|
||||
Backend: buildIngressBackendSvc(service),
|
||||
}
|
||||
return httpIngressPath
|
||||
}
|
||||
|
||||
func buildIngressBackendSvc(service string) networkingv1.IngressBackend {
|
||||
svcname := strings.Split(service, ":")[0]
|
||||
svcport := strings.Split(service, ":")[1]
|
||||
|
||||
ingressBackend := networkingv1.IngressBackend{
|
||||
Service: &networkingv1.IngressServiceBackend{
|
||||
Name: svcname,
|
||||
Port: parseServiceBackendPort(svcport),
|
||||
},
|
||||
}
|
||||
return ingressBackend
|
||||
}
|
||||
|
||||
func parseServiceBackendPort(port string) networkingv1.ServiceBackendPort {
|
||||
var backendPort networkingv1.ServiceBackendPort
|
||||
portIntOrStr := intstr.Parse(port)
|
||||
|
||||
if portIntOrStr.Type == intstr.Int {
|
||||
backendPort.Number = portIntOrStr.IntVal
|
||||
}
|
||||
|
||||
if portIntOrStr.Type == intstr.String {
|
||||
backendPort.Name = portIntOrStr.StrVal
|
||||
}
|
||||
return backendPort
|
||||
}
|
||||
|
||||
func getIndexHost(host string, rules []networkingv1.IngressRule) int {
|
||||
for index, v := range rules {
|
||||
if v.Host == host {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func getIndexSecret(secretname string, tls []networkingv1.IngressTLS) int {
|
||||
for index, v := range tls {
|
||||
if v.SecretName == secretname {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
/*
|
||||
Copyright 2018 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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
batchv1client "k8s.io/client-go/kubernetes/typed/batch/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
var (
|
||||
jobLong = templates.LongDesc(i18n.T(`
|
||||
Create a job with the specified name.`))
|
||||
|
||||
jobExample = templates.Examples(i18n.T(`
|
||||
# Create a job
|
||||
kubectl create job my-job --image=busybox
|
||||
|
||||
# Create a job with a command
|
||||
kubectl create job my-job --image=busybox -- date
|
||||
|
||||
# Create a job from a cron job named "a-cronjob"
|
||||
kubectl create job test-job --from=cronjob/a-cronjob`))
|
||||
)
|
||||
|
||||
// CreateJobOptions is the command line options for 'create job'
|
||||
type CreateJobOptions struct {
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
|
||||
PrintObj func(obj runtime.Object) error
|
||||
|
||||
Name string
|
||||
Image string
|
||||
From string
|
||||
Command []string
|
||||
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
Client batchv1client.BatchV1Interface
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
Builder *resource.Builder
|
||||
FieldManager string
|
||||
CreateAnnotation bool
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewCreateJobOptions initializes and returns new CreateJobOptions instance
|
||||
func NewCreateJobOptions(ioStreams genericiooptions.IOStreams) *CreateJobOptions {
|
||||
return &CreateJobOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreateJob is a command to ease creating Jobs from CronJobs.
|
||||
func NewCmdCreateJob(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewCreateJobOptions(ioStreams)
|
||||
cmd := &cobra.Command{
|
||||
Use: "job NAME --image=image [--from=cronjob/name] -- [COMMAND] [args...]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a job with the specified name"),
|
||||
Long: jobLong,
|
||||
Example: jobExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().StringVar(&o.Image, "image", o.Image, "Image name to run.")
|
||||
cmd.Flags().StringVar(&o.From, "from", o.From, "The name of the resource to create a Job from (only cronjob is supported).")
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options
|
||||
func (o *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Name = name
|
||||
if len(args) > 1 {
|
||||
o.Command = args[1:]
|
||||
}
|
||||
|
||||
clientConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Client, err = batchv1client.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Builder = f.NewBuilder()
|
||||
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate makes sure provided values and valid Job options
|
||||
func (o *CreateJobOptions) Validate() error {
|
||||
if (len(o.Image) == 0 && len(o.From) == 0) || (len(o.Image) != 0 && len(o.From) != 0) {
|
||||
return fmt.Errorf("either --image or --from must be specified")
|
||||
}
|
||||
if o.Command != nil && len(o.Command) != 0 && len(o.From) != 0 {
|
||||
return fmt.Errorf("cannot specify --from and command")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run performs the execution of 'create job' sub command
|
||||
func (o *CreateJobOptions) Run() error {
|
||||
var job *batchv1.Job
|
||||
if len(o.Image) > 0 {
|
||||
job = o.createJob()
|
||||
} else {
|
||||
infos, err := o.Builder.
|
||||
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
|
||||
NamespaceParam(o.Namespace).DefaultNamespace().
|
||||
ResourceTypeOrNameArgs(false, o.From).
|
||||
Flatten().
|
||||
Latest().
|
||||
Do().
|
||||
Infos()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(infos) != 1 {
|
||||
return fmt.Errorf("from must be an existing cronjob")
|
||||
}
|
||||
|
||||
switch obj := infos[0].Object.(type) {
|
||||
case *batchv1.CronJob:
|
||||
job = o.createJobFromCronJob(obj)
|
||||
default:
|
||||
return fmt.Errorf("unknown object type %T", obj)
|
||||
}
|
||||
}
|
||||
|
||||
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, job, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
var err error
|
||||
job, err = o.Client.Jobs(o.Namespace).Create(context.TODO(), job, createOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create job: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return o.PrintObj(job)
|
||||
}
|
||||
|
||||
func (o *CreateJobOptions) createJob() *batchv1.Job {
|
||||
job := &batchv1.Job{
|
||||
// this is ok because we know exactly how we want to be serialized
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "Job"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.Name,
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: o.Name,
|
||||
Image: o.Image,
|
||||
Command: o.Command,
|
||||
},
|
||||
},
|
||||
RestartPolicy: corev1.RestartPolicyNever,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if o.EnforceNamespace {
|
||||
job.Namespace = o.Namespace
|
||||
}
|
||||
return job
|
||||
}
|
||||
|
||||
func (o *CreateJobOptions) createJobFromCronJob(cronJob *batchv1.CronJob) *batchv1.Job {
|
||||
annotations := make(map[string]string)
|
||||
annotations["cronjob.kubernetes.io/instantiate"] = "manual"
|
||||
for k, v := range cronJob.Spec.JobTemplate.Annotations {
|
||||
annotations[k] = v
|
||||
}
|
||||
|
||||
job := &batchv1.Job{
|
||||
// this is ok because we know exactly how we want to be serialized
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "Job"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.Name,
|
||||
Annotations: annotations,
|
||||
Labels: cronJob.Spec.JobTemplate.Labels,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
// we are not using metav1.NewControllerRef because it
|
||||
// sets BlockOwnerDeletion to true which additionally mandates
|
||||
// cronjobs/finalizer role and not backwards-compatible.
|
||||
APIVersion: batchv1.SchemeGroupVersion.String(),
|
||||
Kind: "CronJob",
|
||||
Name: cronJob.GetName(),
|
||||
UID: cronJob.GetUID(),
|
||||
Controller: ptr.To(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: cronJob.Spec.JobTemplate.Spec,
|
||||
}
|
||||
if o.EnforceNamespace {
|
||||
job.Namespace = o.Namespace
|
||||
}
|
||||
return job
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
Copyright 2015 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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
coreclient "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
namespaceLong = templates.LongDesc(i18n.T(`
|
||||
Create a namespace with the specified name.`))
|
||||
|
||||
namespaceExample = templates.Examples(i18n.T(`
|
||||
# Create a new namespace named my-namespace
|
||||
kubectl create namespace my-namespace`))
|
||||
)
|
||||
|
||||
// NamespaceOptions is the options for 'create namespace' sub command
|
||||
type NamespaceOptions struct {
|
||||
// PrintFlags holds options necessary for obtaining a printer
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
// Name of resource being created
|
||||
Name string
|
||||
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
CreateAnnotation bool
|
||||
FieldManager string
|
||||
|
||||
Client *coreclient.CoreV1Client
|
||||
|
||||
PrintObj func(obj runtime.Object) error
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewNamespaceOptions creates a new *NamespaceOptions with sane defaults
|
||||
func NewNamespaceOptions(ioStreams genericiooptions.IOStreams) *NamespaceOptions {
|
||||
return &NamespaceOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreateNamespace is a macro command to create a new namespace
|
||||
func NewCmdCreateNamespace(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
|
||||
o := NewNamespaceOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "namespace NAME [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Aliases: []string{"ns"},
|
||||
Short: i18n.T("Create a namespace with the specified name"),
|
||||
Long: namespaceLong,
|
||||
Example: namespaceExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options
|
||||
func (o *NamespaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
restConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Client, err = coreclient.NewForConfig(restConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Name = name
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
// Run calls the CreateSubcommandOptions.Run in NamespaceOpts instance
|
||||
func (o *NamespaceOptions) Run() error {
|
||||
namespace := o.createNamespace()
|
||||
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, namespace, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
var err error
|
||||
namespace, err = o.Client.Namespaces().Create(context.TODO(), namespace, createOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return o.PrintObj(namespace)
|
||||
}
|
||||
|
||||
// createNamespace outputs a namespace object using the configured fields
|
||||
func (o *NamespaceOptions) createNamespace() *corev1.Namespace {
|
||||
namespace := &corev1.Namespace{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String(), Kind: "Namespace"},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: o.Name},
|
||||
}
|
||||
return namespace
|
||||
}
|
||||
|
||||
// Validate validates required fields are set to support structured generation
|
||||
func (o *NamespaceOptions) Validate() error {
|
||||
if len(o.Name) == 0 {
|
||||
return fmt.Errorf("name must be specified")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
policyv1 "k8s.io/api/policy/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
policyv1client "k8s.io/client-go/kubernetes/typed/policy/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
pdbLong = templates.LongDesc(i18n.T(`
|
||||
Create a pod disruption budget with the specified name, selector, and desired minimum available pods.`))
|
||||
|
||||
pdbExample = templates.Examples(i18n.T(`
|
||||
# Create a pod disruption budget named my-pdb that will select all pods with the app=rails label
|
||||
# and require at least one of them being available at any point in time
|
||||
kubectl create poddisruptionbudget my-pdb --selector=app=rails --min-available=1
|
||||
|
||||
# Create a pod disruption budget named my-pdb that will select all pods with the app=nginx label
|
||||
# and require at least half of the pods selected to be available at any point in time
|
||||
kubectl create pdb my-pdb --selector=app=nginx --min-available=50%`))
|
||||
)
|
||||
|
||||
// PodDisruptionBudgetOpts holds the command-line options for poddisruptionbudget sub command
|
||||
type PodDisruptionBudgetOpts struct {
|
||||
// PrintFlags holds options necessary for obtaining a printer
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj func(obj runtime.Object) error
|
||||
// Name of resource being created
|
||||
Name string
|
||||
|
||||
MinAvailable string
|
||||
MaxUnavailable string
|
||||
|
||||
// A label selector to use for this budget
|
||||
Selector string
|
||||
CreateAnnotation bool
|
||||
FieldManager string
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
|
||||
Client *policyv1client.PolicyV1Client
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewPodDisruptionBudgetOpts creates a new *PodDisruptionBudgetOpts with sane defaults
|
||||
func NewPodDisruptionBudgetOpts(ioStreams genericiooptions.IOStreams) *PodDisruptionBudgetOpts {
|
||||
return &PodDisruptionBudgetOpts{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreatePodDisruptionBudget is a macro command to create a new pod disruption budget.
|
||||
func NewCmdCreatePodDisruptionBudget(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewPodDisruptionBudgetOpts(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "poddisruptionbudget NAME --selector=SELECTOR --min-available=N [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Aliases: []string{"pdb"},
|
||||
Short: i18n.T("Create a pod disruption budget with the specified name"),
|
||||
Long: pdbLong,
|
||||
Example: pdbExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
|
||||
cmd.Flags().StringVar(&o.MinAvailable, "min-available", o.MinAvailable, i18n.T("The minimum number or percentage of available pods this budget requires."))
|
||||
cmd.Flags().StringVar(&o.MaxUnavailable, "max-unavailable", o.MaxUnavailable, i18n.T("The maximum number or percentage of unavailable pods this budget requires."))
|
||||
cmd.Flags().StringVar(&o.Selector, "selector", o.Selector, i18n.T("A label selector to use for this budget. Only equality-based selector requirements are supported."))
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options
|
||||
func (o *PodDisruptionBudgetOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
o.Name, err = NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
restConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Client, err = policyv1client.NewForConfig(restConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks to the PodDisruptionBudgetOpts to see if there is sufficient information run the command
|
||||
func (o *PodDisruptionBudgetOpts) Validate() error {
|
||||
if len(o.Name) == 0 {
|
||||
return fmt.Errorf("name must be specified")
|
||||
}
|
||||
|
||||
if len(o.Selector) == 0 {
|
||||
return fmt.Errorf("a selector must be specified")
|
||||
}
|
||||
|
||||
if len(o.MaxUnavailable) == 0 && len(o.MinAvailable) == 0 {
|
||||
return fmt.Errorf("one of min-available or max-unavailable must be specified")
|
||||
}
|
||||
|
||||
if len(o.MaxUnavailable) > 0 && len(o.MinAvailable) > 0 {
|
||||
return fmt.Errorf("min-available and max-unavailable cannot be both specified")
|
||||
}
|
||||
|
||||
// The following regex matches the following values:
|
||||
// 10, 20, 30%, 50% (number and percentage)
|
||||
// but not 10Gb, 20Mb
|
||||
re := regexp.MustCompile(`^[0-9]+%?$`)
|
||||
|
||||
switch {
|
||||
case len(o.MinAvailable) > 0 && !re.MatchString(o.MinAvailable):
|
||||
return fmt.Errorf("invalid format specified for min-available")
|
||||
case len(o.MaxUnavailable) > 0 && !re.MatchString(o.MaxUnavailable):
|
||||
return fmt.Errorf("invalid format specified for max-unavailable")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run calls the CreateSubcommandOptions.Run in PodDisruptionBudgetOpts instance
|
||||
func (o *PodDisruptionBudgetOpts) Run() error {
|
||||
podDisruptionBudget, err := o.createPodDisruptionBudgets()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, podDisruptionBudget, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
podDisruptionBudget, err = o.Client.PodDisruptionBudgets(o.Namespace).Create(context.TODO(), podDisruptionBudget, createOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create poddisruptionbudgets: %v", err)
|
||||
}
|
||||
}
|
||||
return o.PrintObj(podDisruptionBudget)
|
||||
}
|
||||
|
||||
func (o *PodDisruptionBudgetOpts) createPodDisruptionBudgets() (*policyv1.PodDisruptionBudget, error) {
|
||||
namespace := ""
|
||||
if o.EnforceNamespace {
|
||||
namespace = o.Namespace
|
||||
}
|
||||
|
||||
podDisruptionBudget := &policyv1.PodDisruptionBudget{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: policyv1.SchemeGroupVersion.String(),
|
||||
Kind: "PodDisruptionBudget",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.Name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
|
||||
selector, err := metav1.ParseToLabelSelector(o.Selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
podDisruptionBudget.Spec.Selector = selector
|
||||
|
||||
switch {
|
||||
case len(o.MinAvailable) > 0:
|
||||
minAvailable := intstr.Parse(o.MinAvailable)
|
||||
podDisruptionBudget.Spec.MinAvailable = &minAvailable
|
||||
case len(o.MaxUnavailable) > 0:
|
||||
maxUnavailable := intstr.Parse(o.MaxUnavailable)
|
||||
podDisruptionBudget.Spec.MaxUnavailable = &maxUnavailable
|
||||
}
|
||||
|
||||
return podDisruptionBudget, nil
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
schedulingv1 "k8s.io/api/scheduling/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
schedulingv1client "k8s.io/client-go/kubernetes/typed/scheduling/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
pcLong = templates.LongDesc(i18n.T(`
|
||||
Create a priority class with the specified name, value, globalDefault and description.`))
|
||||
|
||||
pcExample = templates.Examples(i18n.T(`
|
||||
# Create a priority class named high-priority
|
||||
kubectl create priorityclass high-priority --value=1000 --description="high priority"
|
||||
|
||||
# Create a priority class named default-priority that is considered as the global default priority
|
||||
kubectl create priorityclass default-priority --value=1000 --global-default=true --description="default priority"
|
||||
|
||||
# Create a priority class named high-priority that cannot preempt pods with lower priority
|
||||
kubectl create priorityclass high-priority --value=1000 --description="high priority" --preemption-policy="Never"`))
|
||||
)
|
||||
|
||||
// PriorityClassOptions holds the options for 'create priorityclass' sub command
|
||||
type PriorityClassOptions struct {
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj func(obj runtime.Object) error
|
||||
|
||||
Name string
|
||||
Value int32
|
||||
GlobalDefault bool
|
||||
Description string
|
||||
PreemptionPolicy string
|
||||
FieldManager string
|
||||
CreateAnnotation bool
|
||||
|
||||
Client *schedulingv1client.SchedulingV1Client
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewPriorityClassOptions returns an initialized PriorityClassOptions instance
|
||||
func NewPriorityClassOptions(ioStreams genericiooptions.IOStreams) *PriorityClassOptions {
|
||||
return &PriorityClassOptions{
|
||||
Value: 0,
|
||||
PreemptionPolicy: "PreemptLowerPriority",
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreatePriorityClass is a macro command to create a new priorityClass.
|
||||
func NewCmdCreatePriorityClass(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewPriorityClassOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "priorityclass NAME --value=VALUE --global-default=BOOL [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Aliases: []string{"pc"},
|
||||
Short: i18n.T("Create a priority class with the specified name"),
|
||||
Long: pcLong,
|
||||
Example: pcExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().Int32Var(&o.Value, "value", o.Value, i18n.T("the value of this priority class."))
|
||||
cmd.Flags().BoolVar(&o.GlobalDefault, "global-default", o.GlobalDefault, i18n.T("global-default specifies whether this PriorityClass should be considered as the default priority."))
|
||||
cmd.Flags().StringVar(&o.Description, "description", o.Description, i18n.T("description is an arbitrary string that usually provides guidelines on when this priority class should be used."))
|
||||
cmd.Flags().StringVar(&o.PreemptionPolicy, "preemption-policy", o.PreemptionPolicy, i18n.T("preemption-policy is the policy for preempting pods with lower priority."))
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options
|
||||
func (o *PriorityClassOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
o.Name, err = NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
restConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Client, err = schedulingv1client.NewForConfig(restConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run calls the CreateSubcommandOptions.Run in the PriorityClassOptions instance
|
||||
func (o *PriorityClassOptions) Run() error {
|
||||
priorityClass, err := o.createPriorityClass()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, priorityClass, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
var err error
|
||||
priorityClass, err = o.Client.PriorityClasses().Create(context.TODO(), priorityClass, createOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create priorityclass: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return o.PrintObj(priorityClass)
|
||||
}
|
||||
|
||||
func (o *PriorityClassOptions) createPriorityClass() (*schedulingv1.PriorityClass, error) {
|
||||
preemptionPolicy := corev1.PreemptionPolicy(o.PreemptionPolicy)
|
||||
return &schedulingv1.PriorityClass{
|
||||
// this is ok because we know exactly how we want to be serialized
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: schedulingv1.SchemeGroupVersion.String(), Kind: "PriorityClass"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.Name,
|
||||
},
|
||||
Value: o.Value,
|
||||
GlobalDefault: o.GlobalDefault,
|
||||
Description: o.Description,
|
||||
PreemptionPolicy: &preemptionPolicy,
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
resourceapi "k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
coreclient "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
quotaLong = templates.LongDesc(i18n.T(`
|
||||
Create a resource quota with the specified name, hard limits, and optional scopes.`))
|
||||
|
||||
quotaExample = templates.Examples(i18n.T(`
|
||||
# Create a new resource quota named my-quota
|
||||
kubectl create quota my-quota --hard=cpu=1,memory=1G,pods=2,services=3,replicationcontrollers=2,resourcequotas=1,secrets=5,persistentvolumeclaims=10
|
||||
|
||||
# Create a new resource quota named best-effort
|
||||
kubectl create quota best-effort --hard=pods=100 --scopes=BestEffort`))
|
||||
)
|
||||
|
||||
// QuotaOpts holds the command-line options for 'create quota' sub command
|
||||
type QuotaOpts struct {
|
||||
// PrintFlags holds options necessary for obtaining a printer
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj func(obj runtime.Object) error
|
||||
// The name of a quota object.
|
||||
Name string
|
||||
// The hard resource limit string before parsing.
|
||||
Hard string
|
||||
// The scopes of a quota object before parsing.
|
||||
Scopes string
|
||||
CreateAnnotation bool
|
||||
FieldManager string
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
|
||||
Client *coreclient.CoreV1Client
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewQuotaOpts creates a new *QuotaOpts with sane defaults
|
||||
func NewQuotaOpts(ioStreams genericiooptions.IOStreams) *QuotaOpts {
|
||||
return &QuotaOpts{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreateQuota is a macro command to create a new quota
|
||||
func NewCmdCreateQuota(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewQuotaOpts(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "quota NAME [--hard=key1=value1,key2=value2] [--scopes=Scope1,Scope2] [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Aliases: []string{"resourcequota"},
|
||||
Short: i18n.T("Create a quota with the specified name"),
|
||||
Long: quotaLong,
|
||||
Example: quotaExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().StringVar(&o.Hard, "hard", o.Hard, i18n.T("A comma-delimited set of resource=quantity pairs that define a hard limit."))
|
||||
cmd.Flags().StringVar(&o.Scopes, "scopes", o.Scopes, i18n.T("A comma-delimited set of quota scopes that must all match each object tracked by the quota."))
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options
|
||||
func (o *QuotaOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
o.Name, err = NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
restConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Client, err = coreclient.NewForConfig(restConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks to the QuotaOpts to see if there is sufficient information run the command.
|
||||
func (o *QuotaOpts) Validate() error {
|
||||
if len(o.Name) == 0 {
|
||||
return fmt.Errorf("name must be specified")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run does the work
|
||||
func (o *QuotaOpts) Run() error {
|
||||
resourceQuota, err := o.createQuota()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, resourceQuota, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
resourceQuota, err = o.Client.ResourceQuotas(o.Namespace).Create(context.TODO(), resourceQuota, createOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create quota: %v", err)
|
||||
}
|
||||
}
|
||||
return o.PrintObj(resourceQuota)
|
||||
}
|
||||
|
||||
func (o *QuotaOpts) createQuota() (*corev1.ResourceQuota, error) {
|
||||
namespace := ""
|
||||
if o.EnforceNamespace {
|
||||
namespace = o.Namespace
|
||||
}
|
||||
resourceQuota := &corev1.ResourceQuota{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String(), Kind: "ResourceQuota"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.Name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
|
||||
resourceList, err := populateResourceListV1(o.Hard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scopes, err := parseScopes(o.Scopes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourceQuota.Spec.Hard = resourceList
|
||||
resourceQuota.Spec.Scopes = scopes
|
||||
|
||||
return resourceQuota, nil
|
||||
}
|
||||
|
||||
// populateResourceListV1 takes strings of form <resourceName1>=<value1>,<resourceName1>=<value2>
|
||||
// and returns ResourceList.
|
||||
func populateResourceListV1(spec string) (corev1.ResourceList, error) {
|
||||
// empty input gets a nil response to preserve generator test expected behaviors
|
||||
if spec == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
result := corev1.ResourceList{}
|
||||
resourceStatements := strings.Split(spec, ",")
|
||||
for _, resourceStatement := range resourceStatements {
|
||||
parts := strings.Split(resourceStatement, "=")
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("Invalid argument syntax %v, expected <resource>=<value>", resourceStatement)
|
||||
}
|
||||
resourceName := corev1.ResourceName(parts[0])
|
||||
resourceQuantity, err := resourceapi.ParseQuantity(parts[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[resourceName] = resourceQuantity
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func parseScopes(spec string) ([]corev1.ResourceQuotaScope, error) {
|
||||
// empty input gets a nil response to preserve test expected behaviors
|
||||
if spec == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
scopes := strings.Split(spec, ",")
|
||||
result := make([]corev1.ResourceQuotaScope, 0, len(scopes))
|
||||
for _, scope := range scopes {
|
||||
// intentionally do not verify the scope against the valid scope list. This is done by the apiserver anyway.
|
||||
|
||||
if scope == "" {
|
||||
return nil, fmt.Errorf("invalid resource quota scope \"\"")
|
||||
}
|
||||
|
||||
result = append(result, corev1.ResourceQuotaScope(scope))
|
||||
}
|
||||
return result, nil
|
||||
}
|
|
@ -0,0 +1,445 @@
|
|||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
clientgorbacv1 "k8s.io/client-go/kubernetes/typed/rbac/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
roleLong = templates.LongDesc(i18n.T(`
|
||||
Create a role with single rule.`))
|
||||
|
||||
roleExample = templates.Examples(i18n.T(`
|
||||
# Create a role named "pod-reader" that allows user to perform "get", "watch" and "list" on pods
|
||||
kubectl create role pod-reader --verb=get --verb=list --verb=watch --resource=pods
|
||||
|
||||
# Create a role named "pod-reader" with ResourceName specified
|
||||
kubectl create role pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
|
||||
|
||||
# Create a role named "foo" with API Group specified
|
||||
kubectl create role foo --verb=get,list,watch --resource=rs.apps
|
||||
|
||||
# Create a role named "foo" with SubResource specified
|
||||
kubectl create role foo --verb=get,list,watch --resource=pods,pods/status`))
|
||||
|
||||
// Valid resource verb list for validation.
|
||||
validResourceVerbs = []string{"*", "get", "delete", "list", "create", "update", "patch", "watch", "proxy", "deletecollection", "use", "bind", "escalate", "impersonate"}
|
||||
|
||||
// Specialized verbs and GroupResources
|
||||
specialVerbs = map[string][]schema.GroupResource{
|
||||
"use": {
|
||||
{
|
||||
Group: "policy",
|
||||
Resource: "podsecuritypolicies",
|
||||
},
|
||||
{
|
||||
Group: "extensions",
|
||||
Resource: "podsecuritypolicies",
|
||||
},
|
||||
},
|
||||
"bind": {
|
||||
{
|
||||
Group: "rbac.authorization.k8s.io",
|
||||
Resource: "roles",
|
||||
},
|
||||
{
|
||||
Group: "rbac.authorization.k8s.io",
|
||||
Resource: "clusterroles",
|
||||
},
|
||||
},
|
||||
"escalate": {
|
||||
{
|
||||
Group: "rbac.authorization.k8s.io",
|
||||
Resource: "roles",
|
||||
},
|
||||
{
|
||||
Group: "rbac.authorization.k8s.io",
|
||||
Resource: "clusterroles",
|
||||
},
|
||||
},
|
||||
"impersonate": {
|
||||
{
|
||||
Group: "",
|
||||
Resource: "users",
|
||||
},
|
||||
{
|
||||
Group: "",
|
||||
Resource: "serviceaccounts",
|
||||
},
|
||||
{
|
||||
Group: "",
|
||||
Resource: "groups",
|
||||
},
|
||||
{
|
||||
Group: "authentication.k8s.io",
|
||||
Resource: "userextras",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// AddSpecialVerb allows the addition of items to the `specialVerbs` map for non-k8s native resources.
|
||||
func AddSpecialVerb(verb string, gr schema.GroupResource) {
|
||||
resources, ok := specialVerbs[verb]
|
||||
if !ok {
|
||||
resources = make([]schema.GroupResource, 1)
|
||||
}
|
||||
resources = append(resources, gr)
|
||||
specialVerbs[verb] = resources
|
||||
}
|
||||
|
||||
// ResourceOptions holds the related options for '--resource' option
|
||||
type ResourceOptions struct {
|
||||
Group string
|
||||
Resource string
|
||||
SubResource string
|
||||
}
|
||||
|
||||
// CreateRoleOptions holds the options for 'create role' sub command
|
||||
type CreateRoleOptions struct {
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
|
||||
Name string
|
||||
Verbs []string
|
||||
Resources []ResourceOptions
|
||||
ResourceNames []string
|
||||
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
OutputFormat string
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
Client clientgorbacv1.RbacV1Interface
|
||||
Mapper meta.RESTMapper
|
||||
PrintObj func(obj runtime.Object) error
|
||||
FieldManager string
|
||||
CreateAnnotation bool
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewCreateRoleOptions returns an initialized CreateRoleOptions instance
|
||||
func NewCreateRoleOptions(ioStreams genericiooptions.IOStreams) *CreateRoleOptions {
|
||||
return &CreateRoleOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreateRole returnns an initialized Command instance for 'create role' sub command
|
||||
func NewCmdCreateRole(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewCreateRoleOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "role NAME --verb=verb --resource=resource.group/subresource [--resource-name=resourcename] [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a role with single rule"),
|
||||
Long: roleLong,
|
||||
Example: roleExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.RunCreateRole())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().StringSliceVar(&o.Verbs, "verb", o.Verbs, "Verb that applies to the resources contained in the rule")
|
||||
cmd.Flags().StringSlice("resource", []string{}, "Resource that the rule applies to")
|
||||
cmd.Flags().StringArrayVar(&o.ResourceNames, "resource-name", o.ResourceNames, "Resource in the white list that the rule applies to, repeat this flag for multiple items")
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options
|
||||
func (o *CreateRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Name = name
|
||||
|
||||
// Remove duplicate verbs.
|
||||
verbs := []string{}
|
||||
for _, v := range o.Verbs {
|
||||
// VerbAll respresents all kinds of verbs.
|
||||
if v == "*" {
|
||||
verbs = []string{"*"}
|
||||
break
|
||||
}
|
||||
if !arrayContains(verbs, v) {
|
||||
verbs = append(verbs, v)
|
||||
}
|
||||
}
|
||||
o.Verbs = verbs
|
||||
|
||||
// Support resource.group pattern. If no API Group specified, use "" as core API Group.
|
||||
// e.g. --resource=pods,deployments.extensions
|
||||
resources := cmdutil.GetFlagStringSlice(cmd, "resource")
|
||||
for _, r := range resources {
|
||||
sections := strings.SplitN(r, "/", 2)
|
||||
|
||||
resource := &ResourceOptions{}
|
||||
if len(sections) == 2 {
|
||||
resource.SubResource = sections[1]
|
||||
}
|
||||
|
||||
parts := strings.SplitN(sections[0], ".", 2)
|
||||
if len(parts) == 2 {
|
||||
resource.Group = parts[1]
|
||||
}
|
||||
resource.Resource = parts[0]
|
||||
|
||||
if resource.Resource == "*" && len(parts) == 1 && len(sections) == 1 {
|
||||
o.Resources = []ResourceOptions{*resource}
|
||||
break
|
||||
}
|
||||
|
||||
o.Resources = append(o.Resources, *resource)
|
||||
}
|
||||
|
||||
// Remove duplicate resource names.
|
||||
resourceNames := []string{}
|
||||
for _, n := range o.ResourceNames {
|
||||
if !arrayContains(resourceNames, n) {
|
||||
resourceNames = append(resourceNames, n)
|
||||
}
|
||||
}
|
||||
o.ResourceNames = resourceNames
|
||||
|
||||
// Complete other options for Run.
|
||||
o.Mapper, err = f.ToRESTMapper()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.OutputFormat = cmdutil.GetFlagString(cmd, "output")
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientset, err := f.KubernetesClientSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Client = clientset.RbacV1()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate makes sure there is no discrepency in provided option values
|
||||
func (o *CreateRoleOptions) Validate() error {
|
||||
if o.Name == "" {
|
||||
return fmt.Errorf("name must be specified")
|
||||
}
|
||||
|
||||
// validate verbs.
|
||||
if len(o.Verbs) == 0 {
|
||||
return fmt.Errorf("at least one verb must be specified")
|
||||
}
|
||||
|
||||
for _, v := range o.Verbs {
|
||||
if !arrayContains(validResourceVerbs, v) {
|
||||
fmt.Fprintf(o.ErrOut, "Warning: '%s' is not a standard resource verb\n", v)
|
||||
}
|
||||
}
|
||||
|
||||
// validate resources.
|
||||
if len(o.Resources) == 0 {
|
||||
return fmt.Errorf("at least one resource must be specified")
|
||||
}
|
||||
|
||||
return o.validateResource()
|
||||
}
|
||||
|
||||
func (o *CreateRoleOptions) validateResource() error {
|
||||
for _, r := range o.Resources {
|
||||
if len(r.Resource) == 0 {
|
||||
return fmt.Errorf("resource must be specified if apiGroup/subresource specified")
|
||||
}
|
||||
if r.Resource == "*" {
|
||||
return nil
|
||||
}
|
||||
|
||||
resource := schema.GroupVersionResource{Resource: r.Resource, Group: r.Group}
|
||||
groupVersionResource, err := o.Mapper.ResourceFor(schema.GroupVersionResource{Resource: r.Resource, Group: r.Group})
|
||||
if err == nil {
|
||||
resource = groupVersionResource
|
||||
}
|
||||
|
||||
for _, v := range o.Verbs {
|
||||
if groupResources, ok := specialVerbs[v]; ok {
|
||||
match := false
|
||||
for _, extra := range groupResources {
|
||||
if resource.Resource == extra.Resource && resource.Group == extra.Group {
|
||||
match = true
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
return fmt.Errorf("can not perform '%s' on '%s' in group '%s'", v, resource.Resource, resource.Group)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunCreateRole performs the execution of 'create role' sub command
|
||||
func (o *CreateRoleOptions) RunCreateRole() error {
|
||||
role := &rbacv1.Role{
|
||||
// this is ok because we know exactly how we want to be serialized
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "Role"},
|
||||
}
|
||||
role.Name = o.Name
|
||||
rules, err := generateResourcePolicyRules(o.Mapper, o.Verbs, o.Resources, o.ResourceNames, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
role.Rules = rules
|
||||
if o.EnforceNamespace {
|
||||
role.Namespace = o.Namespace
|
||||
}
|
||||
|
||||
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, role, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create role.
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
role, err = o.Client.Roles(o.Namespace).Create(context.TODO(), role, createOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return o.PrintObj(role)
|
||||
}
|
||||
|
||||
func arrayContains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func generateResourcePolicyRules(mapper meta.RESTMapper, verbs []string, resources []ResourceOptions, resourceNames []string, nonResourceURLs []string) ([]rbacv1.PolicyRule, error) {
|
||||
// groupResourceMapping is a apigroup-resource map. The key of this map is api group, while the value
|
||||
// is a string array of resources under this api group.
|
||||
// E.g. groupResourceMapping = {"extensions": ["replicasets", "deployments"], "batch":["jobs"]}
|
||||
groupResourceMapping := map[string][]string{}
|
||||
|
||||
// This loop does the following work:
|
||||
// 1. Constructs groupResourceMapping based on input resources.
|
||||
// 2. Prevents pointing to non-existent resources.
|
||||
// 3. Transfers resource short name to long name. E.g. rs.extensions is transferred to replicasets.extensions
|
||||
for _, r := range resources {
|
||||
resource := schema.GroupVersionResource{Resource: r.Resource, Group: r.Group}
|
||||
groupVersionResource, err := mapper.ResourceFor(schema.GroupVersionResource{Resource: r.Resource, Group: r.Group})
|
||||
if err == nil {
|
||||
resource = groupVersionResource
|
||||
}
|
||||
|
||||
if len(r.SubResource) > 0 {
|
||||
resource.Resource = resource.Resource + "/" + r.SubResource
|
||||
}
|
||||
if !arrayContains(groupResourceMapping[resource.Group], resource.Resource) {
|
||||
groupResourceMapping[resource.Group] = append(groupResourceMapping[resource.Group], resource.Resource)
|
||||
}
|
||||
}
|
||||
|
||||
// Create separate rule for each of the api group.
|
||||
rules := []rbacv1.PolicyRule{}
|
||||
for _, g := range sets.StringKeySet(groupResourceMapping).List() {
|
||||
rule := rbacv1.PolicyRule{}
|
||||
rule.Verbs = verbs
|
||||
rule.Resources = groupResourceMapping[g]
|
||||
rule.APIGroups = []string{g}
|
||||
rule.ResourceNames = resourceNames
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
|
||||
if len(nonResourceURLs) > 0 {
|
||||
rule := rbacv1.PolicyRule{}
|
||||
rule.Verbs = verbs
|
||||
rule.NonResourceURLs = nonResourceURLs
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
|
||||
return rules, nil
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
rbacclientv1 "k8s.io/client-go/kubernetes/typed/rbac/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
roleBindingLong = templates.LongDesc(i18n.T(`
|
||||
Create a role binding for a particular role or cluster role.`))
|
||||
|
||||
roleBindingExample = templates.Examples(i18n.T(`
|
||||
# Create a role binding for user1, user2, and group1 using the admin cluster role
|
||||
kubectl create rolebinding admin --clusterrole=admin --user=user1 --user=user2 --group=group1
|
||||
|
||||
# Create a role binding for serviceaccount monitoring:sa-dev using the admin role
|
||||
kubectl create rolebinding admin-binding --role=admin --serviceaccount=monitoring:sa-dev`))
|
||||
)
|
||||
|
||||
// RoleBindingOptions holds the options for 'create rolebinding' sub command
|
||||
type RoleBindingOptions struct {
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj func(obj runtime.Object) error
|
||||
|
||||
Name string
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
ClusterRole string
|
||||
Role string
|
||||
Users []string
|
||||
Groups []string
|
||||
ServiceAccounts []string
|
||||
FieldManager string
|
||||
CreateAnnotation bool
|
||||
|
||||
Client rbacclientv1.RbacV1Interface
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewRoleBindingOptions creates a new *RoleBindingOptions with sane defaults
|
||||
func NewRoleBindingOptions(ioStreams genericiooptions.IOStreams) *RoleBindingOptions {
|
||||
return &RoleBindingOptions{
|
||||
Users: []string{},
|
||||
Groups: []string{},
|
||||
ServiceAccounts: []string{},
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreateRoleBinding returns an initialized Command instance for 'create rolebinding' sub command
|
||||
func NewCmdCreateRoleBinding(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewRoleBindingOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rolebinding NAME --clusterrole=NAME|--role=NAME [--user=username] [--group=groupname] [--serviceaccount=namespace:serviceaccountname] [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a role binding for a particular role or cluster role"),
|
||||
Long: roleBindingLong,
|
||||
Example: roleBindingExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().StringVar(&o.ClusterRole, "clusterrole", "", i18n.T("ClusterRole this RoleBinding should reference"))
|
||||
cmd.Flags().StringVar(&o.Role, "role", "", i18n.T("Role this RoleBinding should reference"))
|
||||
cmd.Flags().StringArrayVar(&o.Users, "user", o.Users, "Usernames to bind to the role. The flag can be repeated to add multiple users.")
|
||||
cmd.Flags().StringArrayVar(&o.Groups, "group", o.Groups, "Groups to bind to the role. The flag can be repeated to add multiple groups.")
|
||||
cmd.Flags().StringArrayVar(&o.ServiceAccounts, "serviceaccount", o.ServiceAccounts, "Service accounts to bind to the role, in the format <namespace>:<name>. The flag can be repeated to add multiple service accounts.")
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options
|
||||
func (o *RoleBindingOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
o.Name, err = NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clientConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Client, err = rbacclientv1.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate validates required fields are set
|
||||
func (o *RoleBindingOptions) Validate() error {
|
||||
if len(o.Name) == 0 {
|
||||
return fmt.Errorf("name must be specified")
|
||||
}
|
||||
if (len(o.ClusterRole) == 0) == (len(o.Role) == 0) {
|
||||
return fmt.Errorf("exactly one of clusterrole or role must be specified")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run performs the execution of 'create rolebinding' sub command
|
||||
func (o *RoleBindingOptions) Run() error {
|
||||
roleBinding, err := o.createRoleBinding()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, roleBinding, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
roleBinding, err = o.Client.RoleBindings(o.Namespace).Create(context.TODO(), roleBinding, createOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create rolebinding: %v", err)
|
||||
}
|
||||
}
|
||||
return o.PrintObj(roleBinding)
|
||||
}
|
||||
|
||||
func (o *RoleBindingOptions) createRoleBinding() (*rbacv1.RoleBinding, error) {
|
||||
namespace := ""
|
||||
if o.EnforceNamespace {
|
||||
namespace = o.Namespace
|
||||
}
|
||||
|
||||
roleBinding := &rbacv1.RoleBinding{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "RoleBinding"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.Name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(o.Role) > 0:
|
||||
roleBinding.RoleRef = rbacv1.RoleRef{
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Kind: "Role",
|
||||
Name: o.Role,
|
||||
}
|
||||
case len(o.ClusterRole) > 0:
|
||||
roleBinding.RoleRef = rbacv1.RoleRef{
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Kind: "ClusterRole",
|
||||
Name: o.ClusterRole,
|
||||
}
|
||||
}
|
||||
|
||||
for _, user := range o.Users {
|
||||
roleBinding.Subjects = append(roleBinding.Subjects, rbacv1.Subject{
|
||||
Kind: rbacv1.UserKind,
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Name: user,
|
||||
})
|
||||
}
|
||||
|
||||
for _, group := range o.Groups {
|
||||
roleBinding.Subjects = append(roleBinding.Subjects, rbacv1.Subject{
|
||||
Kind: rbacv1.GroupKind,
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Name: group,
|
||||
})
|
||||
}
|
||||
|
||||
for _, sa := range o.ServiceAccounts {
|
||||
tokens := strings.Split(sa, ":")
|
||||
if len(tokens) != 2 || tokens[0] == "" || tokens[1] == "" {
|
||||
return nil, fmt.Errorf("serviceaccount must be <namespace>:<name>")
|
||||
}
|
||||
roleBinding.Subjects = append(roleBinding.Subjects, rbacv1.Subject{
|
||||
Kind: rbacv1.ServiceAccountKind,
|
||||
APIGroup: "",
|
||||
Namespace: tokens[0],
|
||||
Name: tokens[1],
|
||||
})
|
||||
}
|
||||
return roleBinding, nil
|
||||
}
|
|
@ -0,0 +1,421 @@
|
|||
/*
|
||||
Copyright 2015 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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/hash"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
// NewCmdCreateSecret groups subcommands to create various types of secrets.
|
||||
// This is the entry point of create_secret.go which will be called by create.go
|
||||
func NewCmdCreateSecret(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "secret (docker-registry | generic | tls)",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a secret using a specified subcommand"),
|
||||
Long: secretLong,
|
||||
Run: cmdutil.DefaultSubCommandRun(ioStreams.ErrOut),
|
||||
}
|
||||
cmd.AddCommand(NewCmdCreateSecretDockerRegistry(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateSecretTLS(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateSecretGeneric(f, ioStreams))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
var (
|
||||
secretLong = templates.LongDesc(i18n.T(`
|
||||
Create a secret with specified type.
|
||||
|
||||
A docker-registry type secret is for accessing a container registry.
|
||||
|
||||
A generic type secret indicate an Opaque secret type.
|
||||
|
||||
A tls type secret holds TLS certificate and its associated key.`))
|
||||
|
||||
secretForGenericLong = templates.LongDesc(i18n.T(`
|
||||
Create a secret based on a file, directory, or specified literal value.
|
||||
|
||||
A single secret may package one or more key/value pairs.
|
||||
|
||||
When creating a secret based on a file, the key will default to the basename of the file, and the value will
|
||||
default to the file content. If the basename is an invalid key or you wish to chose your own, you may specify
|
||||
an alternate key.
|
||||
|
||||
When creating a secret based on a directory, each file whose basename is a valid key in the directory will be
|
||||
packaged into the secret. Any directory entries except regular files are ignored (e.g. subdirectories,
|
||||
symlinks, devices, pipes, etc).`))
|
||||
|
||||
secretForGenericExample = templates.Examples(i18n.T(`
|
||||
# Create a new secret named my-secret with keys for each file in folder bar
|
||||
kubectl create secret generic my-secret --from-file=path/to/bar
|
||||
|
||||
# Create a new secret named my-secret with specified keys instead of names on disk
|
||||
kubectl create secret generic my-secret --from-file=ssh-privatekey=path/to/id_rsa --from-file=ssh-publickey=path/to/id_rsa.pub
|
||||
|
||||
# Create a new secret named my-secret with key1=supersecret and key2=topsecret
|
||||
kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret
|
||||
|
||||
# Create a new secret named my-secret using a combination of a file and a literal
|
||||
kubectl create secret generic my-secret --from-file=ssh-privatekey=path/to/id_rsa --from-literal=passphrase=topsecret
|
||||
|
||||
# Create a new secret named my-secret from env files
|
||||
kubectl create secret generic my-secret --from-env-file=path/to/foo.env --from-env-file=path/to/bar.env`))
|
||||
)
|
||||
|
||||
// CreateSecretOptions holds the options for 'create secret' sub command
|
||||
type CreateSecretOptions struct {
|
||||
// PrintFlags holds options necessary for obtaining a printer
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj func(obj runtime.Object) error
|
||||
|
||||
// Name of secret (required)
|
||||
Name string
|
||||
// Type of secret (optional)
|
||||
Type string
|
||||
// FileSources to derive the secret from (optional)
|
||||
FileSources []string
|
||||
// LiteralSources to derive the secret from (optional)
|
||||
LiteralSources []string
|
||||
// EnvFileSources to derive the secret from (optional)
|
||||
EnvFileSources []string
|
||||
// AppendHash; if true, derive a hash from the Secret data and type and append it to the name
|
||||
AppendHash bool
|
||||
|
||||
FieldManager string
|
||||
CreateAnnotation bool
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
|
||||
Client corev1client.CoreV1Interface
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewSecretOptions creates a new *CreateSecretOptions with default value
|
||||
func NewSecretOptions(ioStreams genericiooptions.IOStreams) *CreateSecretOptions {
|
||||
return &CreateSecretOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreateSecretGeneric is a command to create generic secrets from files, directories, or literal values
|
||||
func NewCmdCreateSecretGeneric(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewSecretOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "generic NAME [--type=string] [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a secret from a local file, directory, or literal value"),
|
||||
Long: secretForGenericLong,
|
||||
Example: secretForGenericExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
|
||||
cmd.Flags().StringSliceVar(&o.FileSources, "from-file", o.FileSources, "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used. Specifying a directory will iterate each named file in the directory that is a valid secret key.")
|
||||
cmd.Flags().StringArrayVar(&o.LiteralSources, "from-literal", o.LiteralSources, "Specify a key and literal value to insert in secret (i.e. mykey=somevalue)")
|
||||
cmd.Flags().StringSliceVar(&o.EnvFileSources, "from-env-file", o.EnvFileSources, "Specify the path to a file to read lines of key=val pairs to create a secret.")
|
||||
cmd.Flags().StringVar(&o.Type, "type", o.Type, i18n.T("The type of secret to create"))
|
||||
cmd.Flags().BoolVar(&o.AppendHash, "append-hash", o.AppendHash, "Append a hash of the secret to its name.")
|
||||
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete loads data from the command line environment
|
||||
func (o *CreateSecretOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
o.Name, err = NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
restConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Client, err = corev1client.NewForConfig(restConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks if CreateSecretOptions has sufficient value to run
|
||||
func (o *CreateSecretOptions) Validate() error {
|
||||
if len(o.Name) == 0 {
|
||||
return fmt.Errorf("name must be specified")
|
||||
}
|
||||
if len(o.EnvFileSources) > 0 && (len(o.FileSources) > 0 || len(o.LiteralSources) > 0) {
|
||||
return fmt.Errorf("from-env-file cannot be combined with from-file or from-literal")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run calls createSecret which will create secret based on CreateSecretOptions
|
||||
// and makes an API call to the server
|
||||
func (o *CreateSecretOptions) Run() error {
|
||||
secret, err := o.createSecret()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = util.CreateOrUpdateAnnotation(o.CreateAnnotation, secret, scheme.DefaultJSONEncoder())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
secret, err = o.Client.Secrets(o.Namespace).Create(context.TODO(), secret, createOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create secret %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return o.PrintObj(secret)
|
||||
}
|
||||
|
||||
// createSecret fills in key value pair from the information given in
|
||||
// CreateSecretOptions into *corev1.Secret
|
||||
func (o *CreateSecretOptions) createSecret() (*corev1.Secret, error) {
|
||||
namespace := ""
|
||||
if o.EnforceNamespace {
|
||||
namespace = o.Namespace
|
||||
}
|
||||
secret := newSecretObj(o.Name, namespace, corev1.SecretType(o.Type))
|
||||
if len(o.LiteralSources) > 0 {
|
||||
if err := handleSecretFromLiteralSources(secret, o.LiteralSources); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(o.FileSources) > 0 {
|
||||
if err := handleSecretFromFileSources(secret, o.FileSources); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(o.EnvFileSources) > 0 {
|
||||
if err := handleSecretFromEnvFileSources(secret, o.EnvFileSources); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if o.AppendHash {
|
||||
hash, err := hash.SecretHash(secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secret.Name = fmt.Sprintf("%s-%s", secret.Name, hash)
|
||||
}
|
||||
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
// newSecretObj will create a new Secret Object given name, namespace and secretType
|
||||
func newSecretObj(name, namespace string, secretType corev1.SecretType) *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: corev1.SchemeGroupVersion.String(),
|
||||
Kind: "Secret",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Type: secretType,
|
||||
Data: map[string][]byte{},
|
||||
}
|
||||
}
|
||||
|
||||
// handleSecretFromLiteralSources adds the specified literal source
|
||||
// information into the provided secret
|
||||
func handleSecretFromLiteralSources(secret *corev1.Secret, literalSources []string) error {
|
||||
for _, literalSource := range literalSources {
|
||||
keyName, value, err := util.ParseLiteralSource(literalSource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = addKeyFromLiteralToSecret(secret, keyName, []byte(value)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleSecretFromFileSources adds the specified file source information into the provided secret
|
||||
func handleSecretFromFileSources(secret *corev1.Secret, fileSources []string) error {
|
||||
for _, fileSource := range fileSources {
|
||||
keyName, filePath, err := util.ParseFileSource(fileSource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileInfo, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *os.PathError:
|
||||
return fmt.Errorf("error reading %s: %v", filePath, err.Err)
|
||||
default:
|
||||
return fmt.Errorf("error reading %s: %v", filePath, err)
|
||||
}
|
||||
}
|
||||
// if the filePath is a directory
|
||||
if fileInfo.IsDir() {
|
||||
if strings.Contains(fileSource, "=") {
|
||||
return fmt.Errorf("cannot give a key name for a directory path")
|
||||
}
|
||||
fileList, err := os.ReadDir(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing files in %s: %v", filePath, err)
|
||||
}
|
||||
for _, item := range fileList {
|
||||
itemPath := path.Join(filePath, item.Name())
|
||||
if item.Type().IsRegular() {
|
||||
keyName = item.Name()
|
||||
if err := addKeyFromFileToSecret(secret, keyName, itemPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
// if the filepath is a file
|
||||
} else {
|
||||
if err := addKeyFromFileToSecret(secret, keyName, filePath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleSecretFromEnvFileSources adds the specified env files source information
|
||||
// into the provided secret
|
||||
func handleSecretFromEnvFileSources(secret *corev1.Secret, envFileSources []string) error {
|
||||
for _, envFileSource := range envFileSources {
|
||||
info, err := os.Stat(envFileSource)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *os.PathError:
|
||||
return fmt.Errorf("error reading %s: %v", envFileSource, err.Err)
|
||||
default:
|
||||
return fmt.Errorf("error reading %s: %v", envFileSource, err)
|
||||
}
|
||||
}
|
||||
if info.IsDir() {
|
||||
return fmt.Errorf("env secret file cannot be a directory")
|
||||
}
|
||||
err = cmdutil.AddFromEnvFile(envFileSource, func(key, value string) error {
|
||||
return addKeyFromLiteralToSecret(secret, key, []byte(value))
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addKeyFromFileToSecret adds a key with the given name to a Secret, populating
|
||||
// the value with the content of the given file path, or returns an error.
|
||||
func addKeyFromFileToSecret(secret *corev1.Secret, keyName, filePath string) error {
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return addKeyFromLiteralToSecret(secret, keyName, data)
|
||||
}
|
||||
|
||||
// addKeyFromLiteralToSecret adds the given key and data to the given secret,
|
||||
// returning an error if the key is not valid or if the key already exists.
|
||||
func addKeyFromLiteralToSecret(secret *corev1.Secret, keyName string, data []byte) error {
|
||||
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
|
||||
return fmt.Errorf("%q is not valid key name for a Secret %s", keyName, strings.Join(errs, ";"))
|
||||
}
|
||||
if _, entryExists := secret.Data[keyName]; entryExists {
|
||||
return fmt.Errorf("cannot add key %s, another key by that name already exists", keyName)
|
||||
}
|
||||
secret.Data[keyName] = data
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
Copyright 2021 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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/hash"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
secretForDockerRegistryLong = templates.LongDesc(i18n.T(`
|
||||
Create a new secret for use with Docker registries.
|
||||
|
||||
Dockercfg secrets are used to authenticate against Docker registries.
|
||||
|
||||
When using the Docker command line to push images, you can authenticate to a given registry by running:
|
||||
'$ docker login DOCKER_REGISTRY_SERVER --username=DOCKER_USER --password=DOCKER_PASSWORD --email=DOCKER_EMAIL'.
|
||||
|
||||
That produces a ~/.dockercfg file that is used by subsequent 'docker push' and 'docker pull' commands to
|
||||
authenticate to the registry. The email address is optional.
|
||||
|
||||
When creating applications, you may have a Docker registry that requires authentication. In order for the
|
||||
nodes to pull images on your behalf, they must have the credentials. You can provide this information
|
||||
by creating a dockercfg secret and attaching it to your service account.`))
|
||||
|
||||
secretForDockerRegistryExample = templates.Examples(i18n.T(`
|
||||
# If you do not already have a .dockercfg file, create a dockercfg secret directly
|
||||
kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
|
||||
|
||||
# Create a new secret named my-secret from ~/.docker/config.json
|
||||
kubectl create secret docker-registry my-secret --from-file=.dockerconfigjson=path/to/.docker/config.json`))
|
||||
)
|
||||
|
||||
// DockerConfigJSON represents a local docker auth config file
|
||||
// for pulling images.
|
||||
type DockerConfigJSON struct {
|
||||
Auths DockerConfig `json:"auths" datapolicy:"token"`
|
||||
// +optional
|
||||
HttpHeaders map[string]string `json:"HttpHeaders,omitempty" datapolicy:"token"`
|
||||
}
|
||||
|
||||
// DockerConfig represents the config file used by the docker CLI.
|
||||
// This config that represents the credentials that should be used
|
||||
// when pulling images from specific image repositories.
|
||||
type DockerConfig map[string]DockerConfigEntry
|
||||
|
||||
// DockerConfigEntry holds the user information that grant the access to docker registry
|
||||
type DockerConfigEntry struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty" datapolicy:"password"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Auth string `json:"auth,omitempty" datapolicy:"token"`
|
||||
}
|
||||
|
||||
// CreateSecretDockerRegistryOptions holds the options for 'create secret docker-registry' sub command
|
||||
type CreateSecretDockerRegistryOptions struct {
|
||||
// PrintFlags holds options necessary for obtaining a printer
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj func(obj runtime.Object) error
|
||||
|
||||
// Name of secret (required)
|
||||
Name string
|
||||
// FileSources to derive the secret from (optional)
|
||||
FileSources []string
|
||||
// Username for registry (required)
|
||||
Username string
|
||||
// Email for registry (optional)
|
||||
Email string
|
||||
// Password for registry (required)
|
||||
Password string `datapolicy:"password"`
|
||||
// Server for registry (required)
|
||||
Server string
|
||||
// AppendHash; if true, derive a hash from the Secret and append it to the name
|
||||
AppendHash bool
|
||||
|
||||
FieldManager string
|
||||
CreateAnnotation bool
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
|
||||
Client corev1client.CoreV1Interface
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewSecretDockerRegistryOptions creates a new *CreateSecretDockerRegistryOptions with default value
|
||||
func NewSecretDockerRegistryOptions(ioStreams genericiooptions.IOStreams) *CreateSecretDockerRegistryOptions {
|
||||
return &CreateSecretDockerRegistryOptions{
|
||||
Server: "https://index.docker.io/v1/",
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreateSecretDockerRegistry is a macro command for creating secrets to work with Docker registries
|
||||
func NewCmdCreateSecretDockerRegistry(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewSecretDockerRegistryOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "docker-registry NAME --docker-username=user --docker-password=password --docker-email=email [--docker-server=string] [--from-file=[key=]source] [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a secret for use with a Docker registry"),
|
||||
Long: secretForDockerRegistryLong,
|
||||
Example: secretForDockerRegistryExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
|
||||
cmd.Flags().StringVar(&o.Username, "docker-username", o.Username, i18n.T("Username for Docker registry authentication"))
|
||||
cmd.Flags().StringVar(&o.Password, "docker-password", o.Password, i18n.T("Password for Docker registry authentication"))
|
||||
cmd.Flags().StringVar(&o.Email, "docker-email", o.Email, i18n.T("Email for Docker registry"))
|
||||
cmd.Flags().StringVar(&o.Server, "docker-server", o.Server, i18n.T("Server location for Docker registry"))
|
||||
cmd.Flags().BoolVar(&o.AppendHash, "append-hash", o.AppendHash, "Append a hash of the secret to its name.")
|
||||
cmd.Flags().StringSliceVar(&o.FileSources, "from-file", o.FileSources, "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used. Specifying a directory will iterate each named file in the directory that is a valid secret key.")
|
||||
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete loads data from the command line environment
|
||||
func (o *CreateSecretDockerRegistryOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
o.Name, err = NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
restConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Client, err = corev1client.NewForConfig(restConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks if CreateSecretDockerRegistryOptions has sufficient value to run
|
||||
func (o *CreateSecretDockerRegistryOptions) Validate() error {
|
||||
if len(o.Name) == 0 {
|
||||
return fmt.Errorf("name must be specified")
|
||||
}
|
||||
if len(o.FileSources) == 0 && (len(o.Username) == 0 || len(o.Password) == 0 || len(o.Server) == 0) {
|
||||
return fmt.Errorf("either --from-file or the combination of --docker-username, --docker-password and --docker-server is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run calls createSecretDockerRegistry which will create secretDockerRegistry based on CreateSecretDockerRegistryOptions
|
||||
// and makes an API call to the server
|
||||
func (o *CreateSecretDockerRegistryOptions) Run() error {
|
||||
secretDockerRegistry, err := o.createSecretDockerRegistry()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = util.CreateOrUpdateAnnotation(o.CreateAnnotation, secretDockerRegistry, scheme.DefaultJSONEncoder())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
secretDockerRegistry, err = o.Client.Secrets(o.Namespace).Create(context.TODO(), secretDockerRegistry, createOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create secret %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return o.PrintObj(secretDockerRegistry)
|
||||
}
|
||||
|
||||
// createSecretDockerRegistry fills in key value pair from the information given in
|
||||
// CreateSecretDockerRegistryOptions into *corev1.Secret
|
||||
func (o *CreateSecretDockerRegistryOptions) createSecretDockerRegistry() (*corev1.Secret, error) {
|
||||
namespace := ""
|
||||
if o.EnforceNamespace {
|
||||
namespace = o.Namespace
|
||||
}
|
||||
secretDockerRegistry := newSecretObj(o.Name, namespace, corev1.SecretTypeDockerConfigJson)
|
||||
if len(o.FileSources) > 0 {
|
||||
if err := handleSecretFromFileSources(secretDockerRegistry, o.FileSources); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
dockerConfigJSONContent, err := handleDockerCfgJSONContent(o.Username, o.Password, o.Email, o.Server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretDockerRegistry.Data[corev1.DockerConfigJsonKey] = dockerConfigJSONContent
|
||||
}
|
||||
if o.AppendHash {
|
||||
hash, err := hash.SecretHash(secretDockerRegistry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretDockerRegistry.Name = fmt.Sprintf("%s-%s", secretDockerRegistry.Name, hash)
|
||||
}
|
||||
return secretDockerRegistry, nil
|
||||
}
|
||||
|
||||
// handleDockerCfgJSONContent serializes a ~/.docker/config.json file
|
||||
func handleDockerCfgJSONContent(username, password, email, server string) ([]byte, error) {
|
||||
dockerConfigAuth := DockerConfigEntry{
|
||||
Username: username,
|
||||
Password: password,
|
||||
Email: email,
|
||||
Auth: encodeDockerConfigFieldAuth(username, password),
|
||||
}
|
||||
dockerConfigJSON := DockerConfigJSON{
|
||||
Auths: map[string]DockerConfigEntry{server: dockerConfigAuth},
|
||||
}
|
||||
|
||||
return json.Marshal(dockerConfigJSON)
|
||||
}
|
||||
|
||||
// encodeDockerConfigFieldAuth returns base64 encoding of the username and password string
|
||||
func encodeDockerConfigFieldAuth(username, password string) string {
|
||||
fieldValue := username + ":" + password
|
||||
return base64.StdEncoding.EncodeToString([]byte(fieldValue))
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
Copyright 2021 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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/hash"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
secretForTLSLong = templates.LongDesc(i18n.T(`
|
||||
Create a TLS secret from the given public/private key pair.
|
||||
|
||||
The public/private key pair must exist beforehand. The public key certificate must be .PEM encoded and match
|
||||
the given private key.`))
|
||||
|
||||
secretForTLSExample = templates.Examples(i18n.T(`
|
||||
# Create a new TLS secret named tls-secret with the given key pair
|
||||
kubectl create secret tls tls-secret --cert=path/to/tls.crt --key=path/to/tls.key`))
|
||||
)
|
||||
|
||||
// CreateSecretTLSOptions holds the options for 'create secret tls' sub command
|
||||
type CreateSecretTLSOptions struct {
|
||||
// PrintFlags holds options necessary for obtaining a printer
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj func(obj runtime.Object) error
|
||||
|
||||
// Name is the name of this TLS secret.
|
||||
Name string
|
||||
// Key is the path to the user's private key.
|
||||
Key string
|
||||
// Cert is the path to the user's public key certificate.
|
||||
Cert string
|
||||
// AppendHash; if true, derive a hash from the Secret and append it to the name
|
||||
AppendHash bool
|
||||
|
||||
FieldManager string
|
||||
CreateAnnotation bool
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
|
||||
Client corev1client.CoreV1Interface
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewSecretTLSOptions creates a new *CreateSecretTLSOptions with default value
|
||||
func NewSecretTLSOptions(ioStrems genericiooptions.IOStreams) *CreateSecretTLSOptions {
|
||||
return &CreateSecretTLSOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStrems,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreateSecretTLS is a macro command for creating secrets to work with TLS client or server
|
||||
func NewCmdCreateSecretTLS(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewSecretTLSOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "tls NAME --cert=path/to/cert/file --key=path/to/key/file [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a TLS secret"),
|
||||
Long: secretForTLSLong,
|
||||
Example: secretForTLSExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
|
||||
cmd.Flags().StringVar(&o.Cert, "cert", o.Cert, i18n.T("Path to PEM encoded public key certificate."))
|
||||
cmd.Flags().StringVar(&o.Key, "key", o.Key, i18n.T("Path to private key associated with given certificate."))
|
||||
cmd.Flags().BoolVar(&o.AppendHash, "append-hash", o.AppendHash, "Append a hash of the secret to its name.")
|
||||
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete loads data from the command line environment
|
||||
func (o *CreateSecretTLSOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
o.Name, err = NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
restConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Client, err = corev1client.NewForConfig(restConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks if CreateSecretTLSOptions hass sufficient value to run
|
||||
func (o *CreateSecretTLSOptions) Validate() error {
|
||||
// TODO: This is not strictly necessary. We can generate a self signed cert
|
||||
// if no key/cert is given. The only requirement is that we either get both
|
||||
// or none. See test/e2e/ingress_utils for self signed cert generation.
|
||||
if len(o.Key) == 0 || len(o.Cert) == 0 {
|
||||
return fmt.Errorf("key and cert must be specified")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run calls createSecretTLS which will create secretTLS based on CreateSecretTLSOptions
|
||||
// and makes an API call to the server
|
||||
func (o *CreateSecretTLSOptions) Run() error {
|
||||
secretTLS, err := o.createSecretTLS()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = util.CreateOrUpdateAnnotation(o.CreateAnnotation, secretTLS, scheme.DefaultJSONEncoder())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
secretTLS, err = o.Client.Secrets(o.Namespace).Create(context.TODO(), secretTLS, createOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create secret %v", err)
|
||||
}
|
||||
}
|
||||
return o.PrintObj(secretTLS)
|
||||
}
|
||||
|
||||
// createSecretTLS fills in key value pair from the information given in
|
||||
// CreateSecretTLSOptions into *corev1.Secret
|
||||
func (o *CreateSecretTLSOptions) createSecretTLS() (*corev1.Secret, error) {
|
||||
namespace := ""
|
||||
if o.EnforceNamespace {
|
||||
namespace = o.Namespace
|
||||
}
|
||||
tlsCert, err := readFile(o.Cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsKey, err := readFile(o.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := tls.X509KeyPair(tlsCert, tlsKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: Add more validation.
|
||||
// 1. If the certificate contains intermediates, it is a valid chain.
|
||||
// 2. Format etc.
|
||||
|
||||
secretTLS := newSecretObj(o.Name, namespace, corev1.SecretTypeTLS)
|
||||
secretTLS.Data[corev1.TLSCertKey] = []byte(tlsCert)
|
||||
secretTLS.Data[corev1.TLSPrivateKeyKey] = []byte(tlsKey)
|
||||
if o.AppendHash {
|
||||
hash, err := hash.SecretHash(secretTLS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretTLS.Name = fmt.Sprintf("%s-%s", secretTLS.Name, hash)
|
||||
}
|
||||
|
||||
return secretTLS, nil
|
||||
}
|
||||
|
||||
// readFile just reads a file into a byte array.
|
||||
func readFile(file string) ([]byte, error) {
|
||||
b, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("Cannot read file %v, %v", file, err)
|
||||
}
|
||||
return b, nil
|
||||
}
|
|
@ -0,0 +1,412 @@
|
|||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
utilsnet "k8s.io/utils/net"
|
||||
)
|
||||
|
||||
// NewCmdCreateService is a macro command to create a new service
|
||||
func NewCmdCreateService(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "service",
|
||||
Aliases: []string{"svc"},
|
||||
Short: i18n.T("Create a service using a specified subcommand"),
|
||||
Long: i18n.T("Create a service using a specified subcommand."),
|
||||
Run: cmdutil.DefaultSubCommandRun(ioStreams.ErrOut),
|
||||
}
|
||||
cmd.AddCommand(NewCmdCreateServiceClusterIP(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateServiceNodePort(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateServiceLoadBalancer(f, ioStreams))
|
||||
cmd.AddCommand(NewCmdCreateServiceExternalName(f, ioStreams))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ServiceOptions holds the options for 'create service' sub command
|
||||
type ServiceOptions struct {
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj func(obj runtime.Object) error
|
||||
|
||||
Name string
|
||||
TCP []string
|
||||
Type corev1.ServiceType
|
||||
ClusterIP string
|
||||
NodePort int
|
||||
ExternalName string
|
||||
|
||||
FieldManager string
|
||||
CreateAnnotation bool
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
|
||||
Client corev1client.CoreV1Interface
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewServiceOptions creates a ServiceOptions struct
|
||||
func NewServiceOptions(ioStreams genericiooptions.IOStreams, serviceType corev1.ServiceType) *ServiceOptions {
|
||||
return &ServiceOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
Type: serviceType,
|
||||
}
|
||||
}
|
||||
|
||||
// Complete completes all the required options
|
||||
func (o *ServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
name, err := NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Name = name
|
||||
|
||||
clientConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Client, err = corev1client.NewForConfig(clientConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate if the options are valid
|
||||
func (o *ServiceOptions) Validate() error {
|
||||
if o.ClusterIP == corev1.ClusterIPNone && o.Type != corev1.ServiceTypeClusterIP {
|
||||
return fmt.Errorf("ClusterIP=None can only be used with ClusterIP service type")
|
||||
}
|
||||
if o.ClusterIP != corev1.ClusterIPNone && len(o.TCP) == 0 && o.Type != corev1.ServiceTypeExternalName {
|
||||
return fmt.Errorf("at least one tcp port specifier must be provided")
|
||||
}
|
||||
if o.Type == corev1.ServiceTypeExternalName {
|
||||
if errs := validation.IsDNS1123Subdomain(o.ExternalName); len(errs) != 0 {
|
||||
return fmt.Errorf("invalid service external name %s", o.ExternalName)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ServiceOptions) createService() (*corev1.Service, error) {
|
||||
ports := []corev1.ServicePort{}
|
||||
for _, tcpString := range o.TCP {
|
||||
port, targetPort, err := parsePorts(tcpString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
portName := strings.Replace(tcpString, ":", "-", -1)
|
||||
ports = append(ports, corev1.ServicePort{
|
||||
Name: portName,
|
||||
Port: port,
|
||||
TargetPort: targetPort,
|
||||
Protocol: corev1.Protocol("TCP"),
|
||||
NodePort: int32(o.NodePort),
|
||||
})
|
||||
}
|
||||
|
||||
// setup default label and selector
|
||||
labels := map[string]string{}
|
||||
labels["app"] = o.Name
|
||||
selector := map[string]string{}
|
||||
selector["app"] = o.Name
|
||||
|
||||
namespace := ""
|
||||
if o.EnforceNamespace {
|
||||
namespace = o.Namespace
|
||||
}
|
||||
|
||||
service := corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.Name,
|
||||
Labels: labels,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Type: o.Type,
|
||||
Selector: selector,
|
||||
Ports: ports,
|
||||
ExternalName: o.ExternalName,
|
||||
},
|
||||
}
|
||||
if len(o.ClusterIP) > 0 {
|
||||
service.Spec.ClusterIP = o.ClusterIP
|
||||
}
|
||||
return &service, nil
|
||||
}
|
||||
|
||||
// Run the service command
|
||||
func (o *ServiceOptions) Run() error {
|
||||
service, err := o.createService()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, service, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
var err error
|
||||
service, err = o.Client.Services(o.Namespace).Create(context.TODO(), service, createOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create %s service: %v", o.Type, err)
|
||||
}
|
||||
}
|
||||
return o.PrintObj(service)
|
||||
}
|
||||
|
||||
var (
|
||||
serviceClusterIPLong = templates.LongDesc(i18n.T(`
|
||||
Create a ClusterIP service with the specified name.`))
|
||||
|
||||
serviceClusterIPExample = templates.Examples(i18n.T(`
|
||||
# Create a new ClusterIP service named my-cs
|
||||
kubectl create service clusterip my-cs --tcp=5678:8080
|
||||
|
||||
# Create a new ClusterIP service named my-cs (in headless mode)
|
||||
kubectl create service clusterip my-cs --clusterip="None"`))
|
||||
)
|
||||
|
||||
// NewCmdCreateServiceClusterIP is a command to create a ClusterIP service
|
||||
func NewCmdCreateServiceClusterIP(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewServiceOptions(ioStreams, corev1.ServiceTypeClusterIP)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "clusterip NAME [--tcp=<port>:<targetPort>] [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a ClusterIP service"),
|
||||
Long: serviceClusterIPLong,
|
||||
Example: serviceClusterIPExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmd.Flags().StringSliceVar(&o.TCP, "tcp", o.TCP, "Port pairs can be specified as '<port>:<targetPort>'.")
|
||||
cmd.Flags().StringVar(&o.ClusterIP, "clusterip", o.ClusterIP, i18n.T("Assign your own ClusterIP or set to 'None' for a 'headless' service (no loadbalancing)."))
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
var (
|
||||
serviceNodePortLong = templates.LongDesc(i18n.T(`
|
||||
Create a NodePort service with the specified name.`))
|
||||
|
||||
serviceNodePortExample = templates.Examples(i18n.T(`
|
||||
# Create a new NodePort service named my-ns
|
||||
kubectl create service nodeport my-ns --tcp=5678:8080`))
|
||||
)
|
||||
|
||||
// NewCmdCreateServiceNodePort is a macro command for creating a NodePort service
|
||||
func NewCmdCreateServiceNodePort(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewServiceOptions(ioStreams, corev1.ServiceTypeNodePort)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "nodeport NAME [--tcp=port:targetPort] [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a NodePort service"),
|
||||
Long: serviceNodePortLong,
|
||||
Example: serviceNodePortExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmd.Flags().IntVar(&o.NodePort, "node-port", o.NodePort, "Port used to expose the service on each node in a cluster.")
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
cmd.Flags().StringSliceVar(&o.TCP, "tcp", o.TCP, "Port pairs can be specified as '<port>:<targetPort>'.")
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
var (
|
||||
serviceLoadBalancerLong = templates.LongDesc(i18n.T(`
|
||||
Create a LoadBalancer service with the specified name.`))
|
||||
|
||||
serviceLoadBalancerExample = templates.Examples(i18n.T(`
|
||||
# Create a new LoadBalancer service named my-lbs
|
||||
kubectl create service loadbalancer my-lbs --tcp=5678:8080`))
|
||||
)
|
||||
|
||||
// NewCmdCreateServiceLoadBalancer is a macro command for creating a LoadBalancer service
|
||||
func NewCmdCreateServiceLoadBalancer(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewServiceOptions(ioStreams, corev1.ServiceTypeLoadBalancer)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "loadbalancer NAME [--tcp=port:targetPort] [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create a LoadBalancer service"),
|
||||
Long: serviceLoadBalancerLong,
|
||||
Example: serviceLoadBalancerExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmd.Flags().StringSliceVar(&o.TCP, "tcp", o.TCP, "Port pairs can be specified as '<port>:<targetPort>'.")
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
var (
|
||||
serviceExternalNameLong = templates.LongDesc(i18n.T(`
|
||||
Create an ExternalName service with the specified name.
|
||||
|
||||
ExternalName service references to an external DNS address instead of
|
||||
only pods, which will allow application authors to reference services
|
||||
that exist off platform, on other clusters, or locally.`))
|
||||
|
||||
serviceExternalNameExample = templates.Examples(i18n.T(`
|
||||
# Create a new ExternalName service named my-ns
|
||||
kubectl create service externalname my-ns --external-name bar.com`))
|
||||
)
|
||||
|
||||
// NewCmdCreateServiceExternalName is a macro command for creating an ExternalName service
|
||||
func NewCmdCreateServiceExternalName(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewServiceOptions(ioStreams, corev1.ServiceTypeExternalName)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "externalname NAME --external-name external.name [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Create an ExternalName service"),
|
||||
Long: serviceExternalNameLong,
|
||||
Example: serviceExternalNameExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmd.Flags().StringSliceVar(&o.TCP, "tcp", o.TCP, "Port pairs can be specified as '<port>:<targetPort>'.")
|
||||
cmd.Flags().StringVar(&o.ExternalName, "external-name", o.ExternalName, i18n.T("External name of service"))
|
||||
cmd.MarkFlagRequired("external-name")
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func parsePorts(portString string) (int32, intstr.IntOrString, error) {
|
||||
portStringSlice := strings.Split(portString, ":")
|
||||
|
||||
port, err := utilsnet.ParsePort(portStringSlice[0], true)
|
||||
if err != nil {
|
||||
return 0, intstr.FromInt32(0), err
|
||||
}
|
||||
|
||||
if len(portStringSlice) == 1 {
|
||||
port32 := int32(port)
|
||||
return port32, intstr.FromInt32(port32), nil
|
||||
}
|
||||
|
||||
var targetPort intstr.IntOrString
|
||||
if portNum, err := strconv.Atoi(portStringSlice[1]); err != nil {
|
||||
if errs := validation.IsValidPortName(portStringSlice[1]); len(errs) != 0 {
|
||||
return 0, intstr.FromInt32(0), fmt.Errorf(strings.Join(errs, ","))
|
||||
}
|
||||
targetPort = intstr.FromString(portStringSlice[1])
|
||||
} else {
|
||||
if errs := validation.IsValidPortNum(portNum); len(errs) != 0 {
|
||||
return 0, intstr.FromInt32(0), fmt.Errorf(strings.Join(errs, ","))
|
||||
}
|
||||
targetPort = intstr.FromInt32(int32(portNum))
|
||||
}
|
||||
return int32(port), targetPort, nil
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
coreclient "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
var (
|
||||
serviceAccountLong = templates.LongDesc(i18n.T(`
|
||||
Create a service account with the specified name.`))
|
||||
|
||||
serviceAccountExample = templates.Examples(i18n.T(`
|
||||
# Create a new service account named my-service-account
|
||||
kubectl create serviceaccount my-service-account`))
|
||||
)
|
||||
|
||||
// ServiceAccountOpts holds the options for 'create serviceaccount' sub command
|
||||
type ServiceAccountOpts struct {
|
||||
// PrintFlags holds options necessary for obtaining a printer
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj func(obj runtime.Object) error
|
||||
// Name of resource being created
|
||||
Name string
|
||||
DryRunStrategy cmdutil.DryRunStrategy
|
||||
ValidationDirective string
|
||||
CreateAnnotation bool
|
||||
FieldManager string
|
||||
|
||||
Namespace string
|
||||
EnforceNamespace bool
|
||||
|
||||
Mapper meta.RESTMapper
|
||||
Client *coreclient.CoreV1Client
|
||||
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
// NewServiceAccountOpts creates a new *ServiceAccountOpts with sane defaults
|
||||
func NewServiceAccountOpts(ioStreams genericiooptions.IOStreams) *ServiceAccountOpts {
|
||||
return &ServiceAccountOpts{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreateServiceAccount is a macro command to create a new service account
|
||||
func NewCmdCreateServiceAccount(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewServiceAccountOpts(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "serviceaccount NAME [--dry-run=server|client|none]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Aliases: []string{"sa"},
|
||||
Short: i18n.T("Create a service account with the specified name"),
|
||||
Long: serviceAccountLong,
|
||||
Example: serviceAccountExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options
|
||||
func (o *ServiceAccountOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
o.Name, err = NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
restConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Client, err = coreclient.NewForConfig(restConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
|
||||
|
||||
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
|
||||
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks ServiceAccountOpts to see if there is sufficient information run the command.
|
||||
func (o *ServiceAccountOpts) Validate() error {
|
||||
if len(o.Name) == 0 {
|
||||
return fmt.Errorf("name must be specified")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run makes the api call to the server
|
||||
func (o *ServiceAccountOpts) Run() error {
|
||||
serviceAccount, err := o.createServiceAccount()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, serviceAccount, scheme.DefaultJSONEncoder()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if o.DryRunStrategy != cmdutil.DryRunClient {
|
||||
createOptions := metav1.CreateOptions{}
|
||||
if o.FieldManager != "" {
|
||||
createOptions.FieldManager = o.FieldManager
|
||||
}
|
||||
createOptions.FieldValidation = o.ValidationDirective
|
||||
if o.DryRunStrategy == cmdutil.DryRunServer {
|
||||
createOptions.DryRun = []string{metav1.DryRunAll}
|
||||
}
|
||||
serviceAccount, err = o.Client.ServiceAccounts(o.Namespace).Create(context.TODO(), serviceAccount, createOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create serviceaccount: %v", err)
|
||||
}
|
||||
}
|
||||
return o.PrintObj(serviceAccount)
|
||||
}
|
||||
|
||||
func (o *ServiceAccountOpts) createServiceAccount() (*corev1.ServiceAccount, error) {
|
||||
namespace := ""
|
||||
if o.EnforceNamespace {
|
||||
namespace = o.Namespace
|
||||
}
|
||||
serviceAccount := &corev1.ServiceAccount{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String(), Kind: "ServiceAccount"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: o.Name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
serviceAccount.Name = o.Name
|
||||
return serviceAccount, nil
|
||||
}
|
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
Copyright 2022 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 create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util/completion"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
"k8s.io/kubectl/pkg/util/term"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
// TokenOptions is the data required to perform a token request operation.
|
||||
type TokenOptions struct {
|
||||
// PrintFlags holds options necessary for obtaining a printer
|
||||
PrintFlags *genericclioptions.PrintFlags
|
||||
PrintObj func(obj runtime.Object) error
|
||||
|
||||
// Flags hold the parsed CLI flags.
|
||||
Flags *pflag.FlagSet
|
||||
|
||||
// Name and namespace of service account to create a token for
|
||||
Name string
|
||||
Namespace string
|
||||
|
||||
// BoundObjectKind is the kind of object to bind the token to. Optional. Can be Pod or Secret.
|
||||
BoundObjectKind string
|
||||
// BoundObjectName is the name of the object to bind the token to. Required if BoundObjectKind is set.
|
||||
BoundObjectName string
|
||||
// BoundObjectUID is the uid of the object to bind the token to. If unset, defaults to the current uid of the bound object.
|
||||
BoundObjectUID string
|
||||
|
||||
// Audiences indicate the valid audiences for the requested token. If unset, defaults to the Kubernetes API server audiences.
|
||||
Audiences []string
|
||||
|
||||
// Duration is the requested token lifetime. Optional.
|
||||
Duration time.Duration
|
||||
|
||||
// CoreClient is the API client used to request the token. Required.
|
||||
CoreClient corev1client.CoreV1Interface
|
||||
|
||||
// IOStreams are the output streams for the operation. Required.
|
||||
genericiooptions.IOStreams
|
||||
}
|
||||
|
||||
var (
|
||||
tokenLong = templates.LongDesc(`Request a service account token.`)
|
||||
|
||||
tokenExample = templates.Examples(`
|
||||
# Request a token to authenticate to the kube-apiserver as the service account "myapp" in the current namespace
|
||||
kubectl create token myapp
|
||||
|
||||
# Request a token for a service account in a custom namespace
|
||||
kubectl create token myapp --namespace myns
|
||||
|
||||
# Request a token with a custom expiration
|
||||
kubectl create token myapp --duration 10m
|
||||
|
||||
# Request a token with a custom audience
|
||||
kubectl create token myapp --audience https://example.com
|
||||
|
||||
# Request a token bound to an instance of a Secret object
|
||||
kubectl create token myapp --bound-object-kind Secret --bound-object-name mysecret
|
||||
|
||||
# Request a token bound to an instance of a Secret object with a specific UID
|
||||
kubectl create token myapp --bound-object-kind Secret --bound-object-name mysecret --bound-object-uid 0d4691ed-659b-4935-a832-355f77ee47cc
|
||||
`)
|
||||
)
|
||||
|
||||
func boundObjectKindToAPIVersions() map[string]string {
|
||||
kinds := map[string]string{
|
||||
"Pod": "v1",
|
||||
"Secret": "v1",
|
||||
}
|
||||
if os.Getenv("KUBECTL_NODE_BOUND_TOKENS") == "true" {
|
||||
kinds["Node"] = "v1"
|
||||
}
|
||||
return kinds
|
||||
}
|
||||
|
||||
func NewTokenOpts(ioStreams genericiooptions.IOStreams) *TokenOptions {
|
||||
return &TokenOptions{
|
||||
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdCreateToken returns an initialized Command for 'create token' sub command
|
||||
func NewCmdCreateToken(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
|
||||
o := NewTokenOpts(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "token SERVICE_ACCOUNT_NAME",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: "Request a service account token",
|
||||
Long: tokenLong,
|
||||
Example: tokenExample,
|
||||
ValidArgsFunction: completion.ResourceNameCompletionFunc(f, "serviceaccount"),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := o.Complete(f, cmd, args); err != nil {
|
||||
cmdutil.CheckErr(err)
|
||||
return
|
||||
}
|
||||
if err := o.Validate(); err != nil {
|
||||
cmdutil.CheckErr(err)
|
||||
return
|
||||
}
|
||||
if err := o.Run(); err != nil {
|
||||
cmdutil.CheckErr(err)
|
||||
return
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmd.Flags().StringArrayVar(&o.Audiences, "audience", o.Audiences, "Audience of the requested token. If unset, defaults to requesting a token for use with the Kubernetes API server. May be repeated to request a token valid for multiple audiences.")
|
||||
|
||||
cmd.Flags().DurationVar(&o.Duration, "duration", o.Duration, "Requested lifetime of the issued token. If not set or if set to 0, the lifetime will be determined by the server automatically. The server may return a token with a longer or shorter lifetime.")
|
||||
|
||||
cmd.Flags().StringVar(&o.BoundObjectKind, "bound-object-kind", o.BoundObjectKind, "Kind of an object to bind the token to. "+
|
||||
"Supported kinds are "+strings.Join(sets.StringKeySet(boundObjectKindToAPIVersions()).List(), ", ")+". "+
|
||||
"If set, --bound-object-name must be provided.")
|
||||
cmd.Flags().StringVar(&o.BoundObjectName, "bound-object-name", o.BoundObjectName, "Name of an object to bind the token to. "+
|
||||
"The token will expire when the object is deleted. "+
|
||||
"Requires --bound-object-kind.")
|
||||
cmd.Flags().StringVar(&o.BoundObjectUID, "bound-object-uid", o.BoundObjectUID, "UID of an object to bind the token to. "+
|
||||
"Requires --bound-object-kind and --bound-object-name. "+
|
||||
"If unset, the UID of the existing object is used.")
|
||||
|
||||
o.Flags = cmd.Flags()
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes all the required options
|
||||
func (o *TokenOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
|
||||
o.Name, err = NameFromCommandArgs(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := f.KubernetesClientSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.CoreClient = client.CoreV1()
|
||||
|
||||
printer, err := o.PrintFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PrintObj = func(obj runtime.Object) error {
|
||||
return printer.PrintObj(obj, o.Out)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate makes sure provided values for TokenOptions are valid
|
||||
func (o *TokenOptions) Validate() error {
|
||||
if o.CoreClient == nil {
|
||||
return fmt.Errorf("no client provided")
|
||||
}
|
||||
if len(o.Name) == 0 {
|
||||
return fmt.Errorf("service account name is required")
|
||||
}
|
||||
if len(o.Namespace) == 0 {
|
||||
return fmt.Errorf("--namespace is required")
|
||||
}
|
||||
if o.Duration < 0 {
|
||||
return fmt.Errorf("--duration must be greater than or equal to 0")
|
||||
}
|
||||
if o.Duration%time.Second != 0 {
|
||||
return fmt.Errorf("--duration cannot be expressed in units less than seconds")
|
||||
}
|
||||
for _, aud := range o.Audiences {
|
||||
if len(aud) == 0 {
|
||||
return fmt.Errorf("--audience must not be an empty string")
|
||||
}
|
||||
}
|
||||
|
||||
if len(o.BoundObjectKind) == 0 {
|
||||
if len(o.BoundObjectName) > 0 {
|
||||
return fmt.Errorf("--bound-object-name can only be set if --bound-object-kind is provided")
|
||||
}
|
||||
if len(o.BoundObjectUID) > 0 {
|
||||
return fmt.Errorf("--bound-object-uid can only be set if --bound-object-kind is provided")
|
||||
}
|
||||
} else {
|
||||
if _, ok := boundObjectKindToAPIVersions()[o.BoundObjectKind]; !ok {
|
||||
return fmt.Errorf("supported --bound-object-kind values are %s", strings.Join(sets.StringKeySet(boundObjectKindToAPIVersions()).List(), ", "))
|
||||
}
|
||||
if len(o.BoundObjectName) == 0 {
|
||||
return fmt.Errorf("--bound-object-name is required if --bound-object-kind is provided")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run requests a token
|
||||
func (o *TokenOptions) Run() error {
|
||||
request := &authenticationv1.TokenRequest{
|
||||
Spec: authenticationv1.TokenRequestSpec{
|
||||
Audiences: o.Audiences,
|
||||
},
|
||||
}
|
||||
if o.Duration > 0 {
|
||||
request.Spec.ExpirationSeconds = pointer.Int64(int64(o.Duration / time.Second))
|
||||
}
|
||||
if len(o.BoundObjectKind) > 0 {
|
||||
request.Spec.BoundObjectRef = &authenticationv1.BoundObjectReference{
|
||||
Kind: o.BoundObjectKind,
|
||||
APIVersion: boundObjectKindToAPIVersions()[o.BoundObjectKind],
|
||||
Name: o.BoundObjectName,
|
||||
UID: types.UID(o.BoundObjectUID),
|
||||
}
|
||||
}
|
||||
|
||||
response, err := o.CoreClient.ServiceAccounts(o.Namespace).CreateToken(context.TODO(), o.Name, request, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create token: %v", err)
|
||||
}
|
||||
if len(response.Status.Token) == 0 {
|
||||
return fmt.Errorf("failed to create token: no token in server response")
|
||||
}
|
||||
|
||||
if o.PrintFlags.OutputFlagSpecified() {
|
||||
return o.PrintObj(response)
|
||||
}
|
||||
|
||||
if term.IsTerminal(o.Out) {
|
||||
// include a newline when printing interactively
|
||||
fmt.Fprintf(o.Out, "%s\n", response.Status.Token)
|
||||
} else {
|
||||
// otherwise just print the token
|
||||
fmt.Fprintf(o.Out, "%s", response.Status.Token)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
Copyright 2018 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 generate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
)
|
||||
|
||||
// GeneratorFunc returns the generators for the provided command
|
||||
type GeneratorFunc func(cmdName string) map[string]Generator
|
||||
|
||||
// GeneratorParam is a parameter for a generator
|
||||
// TODO: facilitate structured json generator input schemes
|
||||
type GeneratorParam struct {
|
||||
Name string
|
||||
Required bool
|
||||
}
|
||||
|
||||
// Generator is an interface for things that can generate API objects from input
|
||||
// parameters. One example is the "expose" generator that is capable of exposing
|
||||
// new replication controllers and services, among other things.
|
||||
type Generator interface {
|
||||
// Generate creates an API object given a set of parameters
|
||||
Generate(params map[string]interface{}) (runtime.Object, error)
|
||||
// ParamNames returns the list of parameters that this generator uses
|
||||
ParamNames() []GeneratorParam
|
||||
}
|
||||
|
||||
// StructuredGenerator is an interface for things that can generate API objects not using parameter injection
|
||||
type StructuredGenerator interface {
|
||||
// StructuredGenerator creates an API object using pre-configured parameters
|
||||
StructuredGenerate() (runtime.Object, error)
|
||||
}
|
||||
|
||||
func IsZero(i interface{}) bool {
|
||||
if i == nil {
|
||||
return true
|
||||
}
|
||||
return reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface())
|
||||
}
|
||||
|
||||
// ValidateParams ensures that all required params are present in the params map
|
||||
func ValidateParams(paramSpec []GeneratorParam, params map[string]interface{}) error {
|
||||
allErrs := []error{}
|
||||
for ix := range paramSpec {
|
||||
if paramSpec[ix].Required {
|
||||
value, found := params[paramSpec[ix].Name]
|
||||
if !found || IsZero(value) {
|
||||
allErrs = append(allErrs, fmt.Errorf("Parameter: %s is required", paramSpec[ix].Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
return utilerrors.NewAggregate(allErrs)
|
||||
}
|
||||
|
||||
// AnnotateFlags annotates all flags that are used by generators.
|
||||
func AnnotateFlags(cmd *cobra.Command, generators map[string]Generator) {
|
||||
// Iterate over all generators and mark any flags used by them.
|
||||
for name, generator := range generators {
|
||||
generatorParams := map[string]struct{}{}
|
||||
for _, param := range generator.ParamNames() {
|
||||
generatorParams[param.Name] = struct{}{}
|
||||
}
|
||||
|
||||
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
|
||||
if _, found := generatorParams[flag.Name]; !found {
|
||||
// This flag is not used by the current generator
|
||||
// so skip it.
|
||||
return
|
||||
}
|
||||
if flag.Annotations == nil {
|
||||
flag.Annotations = map[string][]string{}
|
||||
}
|
||||
if annotations := flag.Annotations["generator"]; annotations == nil {
|
||||
flag.Annotations["generator"] = []string{}
|
||||
}
|
||||
flag.Annotations["generator"] = append(flag.Annotations["generator"], name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// EnsureFlagsValid ensures that no invalid flags are being used against a
|
||||
func EnsureFlagsValid(cmd *cobra.Command, generators map[string]Generator, generatorInUse string) error {
|
||||
AnnotateFlags(cmd, generators)
|
||||
|
||||
allErrs := []error{}
|
||||
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
|
||||
// If the flag hasn't changed, don't validate it.
|
||||
if !flag.Changed {
|
||||
return
|
||||
}
|
||||
// Look into the flag annotations for the generators that can use it.
|
||||
if annotations := flag.Annotations["generator"]; len(annotations) > 0 {
|
||||
annotationMap := map[string]struct{}{}
|
||||
for _, ann := range annotations {
|
||||
annotationMap[ann] = struct{}{}
|
||||
}
|
||||
// If the current generator is not annotated, then this flag shouldn't
|
||||
// be used with it.
|
||||
if _, found := annotationMap[generatorInUse]; !found {
|
||||
allErrs = append(allErrs, fmt.Errorf("cannot use --%s with --generator=%s", flag.Name, generatorInUse))
|
||||
}
|
||||
}
|
||||
})
|
||||
return utilerrors.NewAggregate(allErrs)
|
||||
}
|
||||
|
||||
// MakeParams is a utility that creates generator parameters from a command line
|
||||
func MakeParams(cmd *cobra.Command, params []GeneratorParam) map[string]interface{} {
|
||||
result := map[string]interface{}{}
|
||||
for ix := range params {
|
||||
f := cmd.Flags().Lookup(params[ix].Name)
|
||||
if f != nil {
|
||||
result[params[ix].Name] = f.Value.String()
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func MakeProtocols(protocols map[string]string) string {
|
||||
out := []string{}
|
||||
for key, value := range protocols {
|
||||
out = append(out, fmt.Sprintf("%s/%s", key, value))
|
||||
}
|
||||
return strings.Join(out, ",")
|
||||
}
|
||||
|
||||
func ParseProtocols(protocols interface{}) (map[string]string, error) {
|
||||
protocolsString, isString := protocols.(string)
|
||||
if !isString {
|
||||
return nil, fmt.Errorf("expected string, found %v", protocols)
|
||||
}
|
||||
if len(protocolsString) == 0 {
|
||||
return nil, fmt.Errorf("no protocols passed")
|
||||
}
|
||||
portProtocolMap := map[string]string{}
|
||||
protocolsSlice := strings.Split(protocolsString, ",")
|
||||
for ix := range protocolsSlice {
|
||||
portProtocol := strings.Split(protocolsSlice[ix], "/")
|
||||
if len(portProtocol) != 2 {
|
||||
return nil, fmt.Errorf("unexpected port protocol mapping: %s", protocolsSlice[ix])
|
||||
}
|
||||
if len(portProtocol[0]) == 0 {
|
||||
return nil, fmt.Errorf("unexpected empty port")
|
||||
}
|
||||
if len(portProtocol[1]) == 0 {
|
||||
return nil, fmt.Errorf("unexpected empty protocol")
|
||||
}
|
||||
portProtocolMap[portProtocol[0]] = portProtocol[1]
|
||||
}
|
||||
return portProtocolMap, nil
|
||||
}
|
||||
|
||||
// ParseLabels turns a string representation of a label set into a map[string]string
|
||||
func ParseLabels(labelSpec interface{}) (map[string]string, error) {
|
||||
labelString, isString := labelSpec.(string)
|
||||
if !isString {
|
||||
return nil, fmt.Errorf("expected string, found %v", labelSpec)
|
||||
}
|
||||
if len(labelString) == 0 {
|
||||
return nil, fmt.Errorf("no label spec passed")
|
||||
}
|
||||
labels := map[string]string{}
|
||||
labelSpecs := strings.Split(labelString, ",")
|
||||
for ix := range labelSpecs {
|
||||
labelSpec := strings.Split(labelSpecs[ix], "=")
|
||||
if len(labelSpec) != 2 {
|
||||
return nil, fmt.Errorf("unexpected label spec: %s", labelSpecs[ix])
|
||||
}
|
||||
if len(labelSpec[0]) == 0 {
|
||||
return nil, fmt.Errorf("unexpected empty label key")
|
||||
}
|
||||
labels[labelSpec[0]] = labelSpec[1]
|
||||
}
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
func GetBool(params map[string]string, key string, defValue bool) (bool, error) {
|
||||
if val, found := params[key]; !found {
|
||||
return defValue, nil
|
||||
} else {
|
||||
return strconv.ParseBool(val)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
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 hash
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// ConfigMapHash returns a hash of the ConfigMap.
|
||||
// The Data, Kind, and Name are taken into account.
|
||||
func ConfigMapHash(cm *v1.ConfigMap) (string, error) {
|
||||
encoded, err := encodeConfigMap(cm)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
h, err := encodeHash(hash(encoded))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// SecretHash returns a hash of the Secret.
|
||||
// The Data, Kind, Name, and Type are taken into account.
|
||||
func SecretHash(sec *v1.Secret) (string, error) {
|
||||
encoded, err := encodeSecret(sec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
h, err := encodeHash(hash(encoded))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// encodeConfigMap encodes a ConfigMap.
|
||||
// Data, Kind, and Name are taken into account.
|
||||
func encodeConfigMap(cm *v1.ConfigMap) (string, error) {
|
||||
// json.Marshal sorts the keys in a stable order in the encoding
|
||||
m := map[string]interface{}{
|
||||
"kind": "ConfigMap",
|
||||
"name": cm.Name,
|
||||
"data": cm.Data,
|
||||
}
|
||||
if cm.Immutable != nil {
|
||||
m["immutable"] = *cm.Immutable
|
||||
}
|
||||
if len(cm.BinaryData) > 0 {
|
||||
m["binaryData"] = cm.BinaryData
|
||||
}
|
||||
data, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// encodeSecret encodes a Secret.
|
||||
// Data, Kind, Name, and Type are taken into account.
|
||||
func encodeSecret(sec *v1.Secret) (string, error) {
|
||||
m := map[string]interface{}{
|
||||
"kind": "Secret",
|
||||
"type": sec.Type,
|
||||
"name": sec.Name,
|
||||
"data": sec.Data,
|
||||
}
|
||||
if sec.Immutable != nil {
|
||||
m["immutable"] = *sec.Immutable
|
||||
}
|
||||
// json.Marshal sorts the keys in a stable order in the encoding
|
||||
data, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// encodeHash extracts the first 40 bits of the hash from the hex string
|
||||
// (1 hex char represents 4 bits), and then maps vowels and vowel-like hex
|
||||
// characters to consonants to prevent bad words from being formed (the theory
|
||||
// is that no vowels makes it really hard to make bad words). Since the string
|
||||
// is hex, the only vowels it can contain are 'a' and 'e'.
|
||||
// We picked some arbitrary consonants to map to from the same character set as GenerateName.
|
||||
// See: https://github.com/kubernetes/apimachinery/blob/dc1f89aff9a7509782bde3b68824c8043a3e58cc/pkg/util/rand/rand.go#L75
|
||||
// If the hex string contains fewer than ten characters, returns an error.
|
||||
func encodeHash(hex string) (string, error) {
|
||||
if len(hex) < 10 {
|
||||
return "", fmt.Errorf("the hex string must contain at least 10 characters")
|
||||
}
|
||||
enc := []rune(hex[:10])
|
||||
for i := range enc {
|
||||
switch enc[i] {
|
||||
case '0':
|
||||
enc[i] = 'g'
|
||||
case '1':
|
||||
enc[i] = 'h'
|
||||
case '3':
|
||||
enc[i] = 'k'
|
||||
case 'a':
|
||||
enc[i] = 'm'
|
||||
case 'e':
|
||||
enc[i] = 't'
|
||||
}
|
||||
}
|
||||
return string(enc), nil
|
||||
}
|
||||
|
||||
// hash hashes `data` with sha256 and returns the hex string
|
||||
func hash(data string) string {
|
||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
|
||||
}
|
|
@ -1682,6 +1682,7 @@ k8s.io/kube-openapi/pkg/validation/strfmt/bson
|
|||
k8s.io/kubectl/pkg/apps
|
||||
k8s.io/kubectl/pkg/cmd/apiresources
|
||||
k8s.io/kubectl/pkg/cmd/apply
|
||||
k8s.io/kubectl/pkg/cmd/create
|
||||
k8s.io/kubectl/pkg/cmd/delete
|
||||
k8s.io/kubectl/pkg/cmd/describe
|
||||
k8s.io/kubectl/pkg/cmd/exec
|
||||
|
@ -1694,6 +1695,7 @@ k8s.io/kubectl/pkg/cmd/util/editor/crlf
|
|||
k8s.io/kubectl/pkg/cmd/util/podcmd
|
||||
k8s.io/kubectl/pkg/cmd/wait
|
||||
k8s.io/kubectl/pkg/describe
|
||||
k8s.io/kubectl/pkg/generate
|
||||
k8s.io/kubectl/pkg/metricsutil
|
||||
k8s.io/kubectl/pkg/polymorphichelpers
|
||||
k8s.io/kubectl/pkg/rawhttp
|
||||
|
@ -1704,6 +1706,7 @@ k8s.io/kubectl/pkg/util/completion
|
|||
k8s.io/kubectl/pkg/util/deployment
|
||||
k8s.io/kubectl/pkg/util/event
|
||||
k8s.io/kubectl/pkg/util/fieldpath
|
||||
k8s.io/kubectl/pkg/util/hash
|
||||
k8s.io/kubectl/pkg/util/i18n
|
||||
k8s.io/kubectl/pkg/util/interrupt
|
||||
k8s.io/kubectl/pkg/util/openapi
|
||||
|
|
Loading…
Reference in New Issue