1
16
17 package create
18
19 import (
20 "context"
21 "encoding/base64"
22 "encoding/json"
23 "fmt"
24
25 "github.com/spf13/cobra"
26 corev1 "k8s.io/api/core/v1"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/cli-runtime/pkg/genericclioptions"
30 "k8s.io/cli-runtime/pkg/genericiooptions"
31 corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
32 cmdutil "k8s.io/kubectl/pkg/cmd/util"
33 "k8s.io/kubectl/pkg/scheme"
34 "k8s.io/kubectl/pkg/util"
35 "k8s.io/kubectl/pkg/util/hash"
36 "k8s.io/kubectl/pkg/util/i18n"
37 "k8s.io/kubectl/pkg/util/templates"
38 )
39
40 var (
41 secretForDockerRegistryLong = templates.LongDesc(i18n.T(`
42 Create a new secret for use with Docker registries.
43
44 Dockercfg secrets are used to authenticate against Docker registries.
45
46 When using the Docker command line to push images, you can authenticate to a given registry by running:
47 '$ docker login DOCKER_REGISTRY_SERVER --username=DOCKER_USER --password=DOCKER_PASSWORD --email=DOCKER_EMAIL'.
48
49 That produces a ~/.dockercfg file that is used by subsequent 'docker push' and 'docker pull' commands to
50 authenticate to the registry. The email address is optional.
51
52 When creating applications, you may have a Docker registry that requires authentication. In order for the
53 nodes to pull images on your behalf, they must have the credentials. You can provide this information
54 by creating a dockercfg secret and attaching it to your service account.`))
55
56 secretForDockerRegistryExample = templates.Examples(i18n.T(`
57 # If you do not already have a .dockercfg file, create a dockercfg secret directly
58 kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
59
60 # Create a new secret named my-secret from ~/.docker/config.json
61 kubectl create secret docker-registry my-secret --from-file=.dockerconfigjson=path/to/.docker/config.json`))
62 )
63
64
65
66 type DockerConfigJSON struct {
67 Auths DockerConfig `json:"auths" datapolicy:"token"`
68
69 HttpHeaders map[string]string `json:"HttpHeaders,omitempty" datapolicy:"token"`
70 }
71
72
73
74
75 type DockerConfig map[string]DockerConfigEntry
76
77
78 type DockerConfigEntry struct {
79 Username string `json:"username,omitempty"`
80 Password string `json:"password,omitempty" datapolicy:"password"`
81 Email string `json:"email,omitempty"`
82 Auth string `json:"auth,omitempty" datapolicy:"token"`
83 }
84
85
86 type CreateSecretDockerRegistryOptions struct {
87
88 PrintFlags *genericclioptions.PrintFlags
89 PrintObj func(obj runtime.Object) error
90
91
92 Name string
93
94 FileSources []string
95
96 Username string
97
98 Email string
99
100 Password string `datapolicy:"password"`
101
102 Server string
103
104 AppendHash bool
105
106 FieldManager string
107 CreateAnnotation bool
108 Namespace string
109 EnforceNamespace bool
110
111 Client corev1client.CoreV1Interface
112 DryRunStrategy cmdutil.DryRunStrategy
113 ValidationDirective string
114
115 genericiooptions.IOStreams
116 }
117
118
119 func NewSecretDockerRegistryOptions(ioStreams genericiooptions.IOStreams) *CreateSecretDockerRegistryOptions {
120 return &CreateSecretDockerRegistryOptions{
121 Server: "https://index.docker.io/v1/",
122 PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
123 IOStreams: ioStreams,
124 }
125 }
126
127
128 func NewCmdCreateSecretDockerRegistry(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
129 o := NewSecretDockerRegistryOptions(ioStreams)
130
131 cmd := &cobra.Command{
132 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]",
133 DisableFlagsInUseLine: true,
134 Short: i18n.T("Create a secret for use with a Docker registry"),
135 Long: secretForDockerRegistryLong,
136 Example: secretForDockerRegistryExample,
137 Run: func(cmd *cobra.Command, args []string) {
138 cmdutil.CheckErr(o.Complete(f, cmd, args))
139 cmdutil.CheckErr(o.Validate())
140 cmdutil.CheckErr(o.Run())
141 },
142 }
143
144 o.PrintFlags.AddFlags(cmd)
145
146 cmdutil.AddApplyAnnotationFlags(cmd)
147 cmdutil.AddValidateFlags(cmd)
148 cmdutil.AddDryRunFlag(cmd)
149
150 cmd.Flags().StringVar(&o.Username, "docker-username", o.Username, i18n.T("Username for Docker registry authentication"))
151 cmd.Flags().StringVar(&o.Password, "docker-password", o.Password, i18n.T("Password for Docker registry authentication"))
152 cmd.Flags().StringVar(&o.Email, "docker-email", o.Email, i18n.T("Email for Docker registry"))
153 cmd.Flags().StringVar(&o.Server, "docker-server", o.Server, i18n.T("Server location for Docker registry"))
154 cmd.Flags().BoolVar(&o.AppendHash, "append-hash", o.AppendHash, "Append a hash of the secret to its name.")
155 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.")
156
157 cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
158
159 return cmd
160 }
161
162
163 func (o *CreateSecretDockerRegistryOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
164 var err error
165 o.Name, err = NameFromCommandArgs(cmd, args)
166 if err != nil {
167 return err
168 }
169
170 restConfig, err := f.ToRESTConfig()
171 if err != nil {
172 return err
173 }
174
175 o.Client, err = corev1client.NewForConfig(restConfig)
176 if err != nil {
177 return err
178 }
179
180 o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
181
182 o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
183 if err != nil {
184 return err
185 }
186
187 o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
188 if err != nil {
189 return err
190 }
191
192 cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
193 printer, err := o.PrintFlags.ToPrinter()
194 if err != nil {
195 return err
196 }
197
198 o.PrintObj = func(obj runtime.Object) error {
199 return printer.PrintObj(obj, o.Out)
200 }
201
202 o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
203 if err != nil {
204 return err
205 }
206
207 return nil
208 }
209
210
211 func (o *CreateSecretDockerRegistryOptions) Validate() error {
212 if len(o.Name) == 0 {
213 return fmt.Errorf("name must be specified")
214 }
215 if len(o.FileSources) == 0 && (len(o.Username) == 0 || len(o.Password) == 0 || len(o.Server) == 0) {
216 return fmt.Errorf("either --from-file or the combination of --docker-username, --docker-password and --docker-server is required")
217 }
218 return nil
219 }
220
221
222
223 func (o *CreateSecretDockerRegistryOptions) Run() error {
224 secretDockerRegistry, err := o.createSecretDockerRegistry()
225 if err != nil {
226 return err
227 }
228 err = util.CreateOrUpdateAnnotation(o.CreateAnnotation, secretDockerRegistry, scheme.DefaultJSONEncoder())
229 if err != nil {
230 return err
231 }
232 if o.DryRunStrategy != cmdutil.DryRunClient {
233 createOptions := metav1.CreateOptions{}
234 if o.FieldManager != "" {
235 createOptions.FieldManager = o.FieldManager
236 }
237 createOptions.FieldValidation = o.ValidationDirective
238 if o.DryRunStrategy == cmdutil.DryRunServer {
239 createOptions.DryRun = []string{metav1.DryRunAll}
240 }
241 secretDockerRegistry, err = o.Client.Secrets(o.Namespace).Create(context.TODO(), secretDockerRegistry, createOptions)
242 if err != nil {
243 return fmt.Errorf("failed to create secret %v", err)
244 }
245 }
246
247 return o.PrintObj(secretDockerRegistry)
248 }
249
250
251
252 func (o *CreateSecretDockerRegistryOptions) createSecretDockerRegistry() (*corev1.Secret, error) {
253 namespace := ""
254 if o.EnforceNamespace {
255 namespace = o.Namespace
256 }
257 secretDockerRegistry := newSecretObj(o.Name, namespace, corev1.SecretTypeDockerConfigJson)
258 if len(o.FileSources) > 0 {
259 if err := handleSecretFromFileSources(secretDockerRegistry, o.FileSources); err != nil {
260 return nil, err
261 }
262 } else {
263 dockerConfigJSONContent, err := handleDockerCfgJSONContent(o.Username, o.Password, o.Email, o.Server)
264 if err != nil {
265 return nil, err
266 }
267 secretDockerRegistry.Data[corev1.DockerConfigJsonKey] = dockerConfigJSONContent
268 }
269 if o.AppendHash {
270 hash, err := hash.SecretHash(secretDockerRegistry)
271 if err != nil {
272 return nil, err
273 }
274 secretDockerRegistry.Name = fmt.Sprintf("%s-%s", secretDockerRegistry.Name, hash)
275 }
276 return secretDockerRegistry, nil
277 }
278
279
280 func handleDockerCfgJSONContent(username, password, email, server string) ([]byte, error) {
281 dockerConfigAuth := DockerConfigEntry{
282 Username: username,
283 Password: password,
284 Email: email,
285 Auth: encodeDockerConfigFieldAuth(username, password),
286 }
287 dockerConfigJSON := DockerConfigJSON{
288 Auths: map[string]DockerConfigEntry{server: dockerConfigAuth},
289 }
290
291 return json.Marshal(dockerConfigJSON)
292 }
293
294
295 func encodeDockerConfigFieldAuth(username, password string) string {
296 fieldValue := username + ":" + password
297 return base64.StdEncoding.EncodeToString([]byte(fieldValue))
298 }
299
View as plain text