1/*
2Copyright 2019 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package add
18
19import (
20	"fmt"
21
22	"github.com/spf13/cobra"
23	"sigs.k8s.io/kustomize/pkg/commands/kustfile"
24	"sigs.k8s.io/kustomize/pkg/fs"
25	"sigs.k8s.io/kustomize/pkg/ifc"
26	"sigs.k8s.io/kustomize/pkg/loader"
27	"sigs.k8s.io/kustomize/pkg/types"
28)
29
30// newCmdAddSecret returns a new command.
31func newCmdAddSecret(fSys fs.FileSystem, kf ifc.KunstructuredFactory) *cobra.Command {
32	var flags flagsAndArgs
33	cmd := &cobra.Command{
34		Use:   "secret NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--type=Opaque|kubernetes.io/tls]",
35		Short: "Adds a secret to the kustomization file.",
36		Long:  "",
37		Example: `
38	# Adds a secret to the kustomization file (with a specified key)
39	kustomize edit add secret my-secret --from-file=my-key=file/path --from-literal=my-literal=12345
40
41	# Adds a secret to the kustomization file (key is the filename)
42	kustomize edit add secret my-secret --from-file=file/path
43
44	# Adds a secret from env-file
45	kustomize edit add secret my-secret --from-env-file=env/path.env
46`,
47		RunE: func(_ *cobra.Command, args []string) error {
48			err := flags.ExpandFileSource(fSys)
49			if err != nil {
50				return err
51			}
52
53			err = flags.Validate(args)
54			if err != nil {
55				return err
56			}
57
58			// Load the kustomization file.
59			mf, err := kustfile.NewKustomizationFile(fSys)
60			if err != nil {
61				return err
62			}
63
64			kustomization, err := mf.Read()
65			if err != nil {
66				return err
67			}
68
69			// Add the flagsAndArgs map to the kustomization file.
70			kf.Set(loader.NewFileLoaderAtCwd(fSys))
71			err = addSecret(kustomization, flags, kf)
72			if err != nil {
73				return err
74			}
75
76			// Write out the kustomization file with added secret.
77			return mf.Write(kustomization)
78		},
79	}
80
81	cmd.Flags().StringSliceVar(
82		&flags.FileSources,
83		"from-file",
84		[]string{},
85		"Key file can be specified using its file path, in which case file basename will be used as secret "+
86			"key, or optionally with a key and file path, in which case the given key will be used.  Specifying a "+
87			"directory will iterate each named file in the directory whose basename is a valid secret key.")
88	cmd.Flags().StringArrayVar(
89		&flags.LiteralSources,
90		"from-literal",
91		[]string{},
92		"Specify a key and literal value to insert in secret (i.e. mykey=somevalue)")
93	cmd.Flags().StringVar(
94		&flags.EnvFileSource,
95		"from-env-file",
96		"",
97		"Specify the path to a file to read lines of key=val pairs to create a secret (i.e. a Docker .env file).")
98	cmd.Flags().StringVar(
99		&flags.Type,
100		"type",
101		"Opaque",
102		"Specify the secret type this can be 'Opaque' (default), or 'kubernetes.io/tls'")
103
104	return cmd
105}
106
107// addSecret adds a secret to a kustomization file.
108// Note: error may leave kustomization file in an undefined state.
109// Suggest passing a copy of kustomization file.
110func addSecret(
111	k *types.Kustomization,
112	flags flagsAndArgs, kf ifc.KunstructuredFactory) error {
113	secretArgs := makeSecretArgs(k, flags.Name, flags.Type)
114	err := mergeFlagsIntoSecretArgs(&secretArgs.DataSources, flags)
115	if err != nil {
116		return err
117	}
118	// Validate by trying to create corev1.secret.
119	_, err = kf.MakeSecret(secretArgs, k.GeneratorOptions)
120	if err != nil {
121		return err
122	}
123	return nil
124}
125
126func makeSecretArgs(m *types.Kustomization, name, secretType string) *types.SecretArgs {
127	for i, v := range m.SecretGenerator {
128		if name == v.Name {
129			return &m.SecretGenerator[i]
130		}
131	}
132	// secret not found, create new one and add it to the kustomization file.
133	secret := &types.SecretArgs{GeneratorArgs: types.GeneratorArgs{Name: name}, Type: secretType}
134	m.SecretGenerator = append(m.SecretGenerator, *secret)
135	return &m.SecretGenerator[len(m.SecretGenerator)-1]
136}
137
138func mergeFlagsIntoSecretArgs(src *types.DataSources, flags flagsAndArgs) error {
139	src.LiteralSources = append(src.LiteralSources, flags.LiteralSources...)
140	src.FileSources = append(src.FileSources, flags.FileSources...)
141	if src.EnvSource != "" && src.EnvSource != flags.EnvFileSource {
142		return fmt.Errorf("updating existing env source '%s' not allowed", src.EnvSource)
143	}
144	src.EnvSource = flags.EnvFileSource
145	return nil
146}
147