template.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package main
  14. import (
  15. "context"
  16. "fmt"
  17. "os"
  18. "path/filepath"
  19. "github.com/spf13/cobra"
  20. corev1 "k8s.io/api/core/v1"
  21. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  22. "k8s.io/apimachinery/pkg/runtime"
  23. "sigs.k8s.io/yaml"
  24. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  25. "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  26. "github.com/external-secrets/external-secrets/pkg/controllers/templating"
  27. "github.com/external-secrets/external-secrets/runtime/template"
  28. )
  29. // version is filled during build time.
  30. var version string
  31. var (
  32. templateFile string
  33. secretDataFile string
  34. outputFile string
  35. templateFromConfigMapFile string
  36. templateFromSecretFile string
  37. showVersion bool
  38. )
  39. func init() {
  40. rootCmd.AddCommand(templateCmd)
  41. templateCmd.Flags().StringVar(&templateFile, "source-templated-object", "", "Link to a file containing the object that contains the template")
  42. templateCmd.Flags().StringVar(&secretDataFile, "source-secret-data-file", "", "Link to a file containing secret data in form of map[string][]byte")
  43. templateCmd.Flags().StringVar(&templateFromConfigMapFile, "template-from-config-map", "", "Link to a file containing config map data for TemplateFrom.ConfigMap")
  44. templateCmd.Flags().StringVar(&templateFromSecretFile, "template-from-secret", "", "Link to a file containing config map data for TemplateFrom.Secret")
  45. templateCmd.Flags().StringVar(&outputFile, "output", "", "If set, the output will be written to this file")
  46. templateCmd.Flags().BoolVar(&showVersion, "version", false, "If set, only print the version and exit")
  47. }
  48. var templateCmd = &cobra.Command{
  49. Use: "template",
  50. Short: "given an input and a template provides an output",
  51. Long: `Given an input that mimics a secret's data section and a template it produces an output of the render template.`,
  52. RunE: templateRun,
  53. }
  54. func templateRun(_ *cobra.Command, _ []string) error {
  55. if version == "" {
  56. version = "0.0.0-dev"
  57. }
  58. if showVersion {
  59. fmt.Printf("version: %s\n", version)
  60. os.Exit(0)
  61. }
  62. ctx := context.Background()
  63. obj := &unstructured.Unstructured{}
  64. content, err := os.ReadFile(filepath.Clean(templateFile))
  65. if err != nil {
  66. return fmt.Errorf("could not read template file: %w", err)
  67. }
  68. if err := yaml.Unmarshal(content, obj); err != nil {
  69. return fmt.Errorf("could not unmarshal template: %w", err)
  70. }
  71. tmpl, err := fetchTemplateFromSourceObject(obj)
  72. if err != nil {
  73. return err
  74. }
  75. data := map[string][]byte{}
  76. sourceDataContent, err := os.ReadFile(filepath.Clean(secretDataFile))
  77. if err != nil {
  78. return fmt.Errorf("could not read source secret file: %w", err)
  79. }
  80. if err := yaml.Unmarshal(sourceDataContent, &data); err != nil {
  81. return fmt.Errorf("could not unmarshal secret: %w", err)
  82. }
  83. execute, err := template.EngineForVersion(tmpl.EngineVersion)
  84. if err != nil {
  85. return err
  86. }
  87. targetSecret := &corev1.Secret{}
  88. p := &templating.Parser{
  89. TargetSecret: targetSecret,
  90. DataMap: data,
  91. Exec: execute,
  92. }
  93. if err := setupFromConfigAndFromSecret(p); err != nil {
  94. return fmt.Errorf("could not setup from secret: %w", err)
  95. }
  96. if err := executeTemplate(ctx, p, tmpl); err != nil {
  97. return fmt.Errorf("could not render template: %w", err)
  98. }
  99. out := os.Stdout
  100. if outputFile != "" {
  101. f, err := os.Create(filepath.Clean(outputFile))
  102. if err != nil {
  103. return fmt.Errorf("could not create output file: %w", err)
  104. }
  105. defer func() {
  106. _ = f.Close()
  107. }()
  108. out = f
  109. }
  110. // display the resulting secret
  111. content, err = yaml.Marshal(targetSecret)
  112. if err != nil {
  113. return fmt.Errorf("could not marshal secret: %w", err)
  114. }
  115. _, err = fmt.Fprintln(out, string(content))
  116. return err
  117. }
  118. func fetchTemplateFromSourceObject(obj *unstructured.Unstructured) (*esv1.ExternalSecretTemplate, error) {
  119. var tmpl *esv1.ExternalSecretTemplate
  120. switch obj.GetKind() {
  121. case "ExternalSecret":
  122. es := &esv1.ExternalSecret{}
  123. if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, es); err != nil {
  124. return nil, err
  125. }
  126. tmpl = es.Spec.Target.Template
  127. case "PushSecret":
  128. ps := &v1alpha1.PushSecret{}
  129. if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, ps); err != nil {
  130. return nil, err
  131. }
  132. tmpl = ps.Spec.Template
  133. default:
  134. return nil, fmt.Errorf("unsupported template kind %s", obj.GetKind())
  135. }
  136. return tmpl, nil
  137. }
  138. func executeTemplate(ctx context.Context, p *templating.Parser, tmpl *esv1.ExternalSecretTemplate) error {
  139. // apply templates defined in template.templateFrom
  140. err := p.MergeTemplateFrom(ctx, "default", tmpl)
  141. if err != nil {
  142. return fmt.Errorf("could not merge template: %w", err)
  143. }
  144. // apply data templates
  145. // NOTE: explicitly defined template.data templates take precedence over templateFrom
  146. err = p.MergeMap(tmpl.Data, esv1.TemplateTargetData)
  147. if err != nil {
  148. return fmt.Errorf("could not merge data: %w", err)
  149. }
  150. // apply templates for labels
  151. // NOTE: this only works for v2 templates
  152. err = p.MergeMap(tmpl.Metadata.Labels, esv1.TemplateTargetLabels)
  153. if err != nil {
  154. return fmt.Errorf("could not merge labels: %w", err)
  155. }
  156. // apply template for annotations
  157. // NOTE: this only works for v2 templates
  158. err = p.MergeMap(tmpl.Metadata.Annotations, esv1.TemplateTargetAnnotations)
  159. if err != nil {
  160. return fmt.Errorf("could not merge annotations: %w", err)
  161. }
  162. return err
  163. }
  164. func setupFromConfigAndFromSecret(p *templating.Parser) error {
  165. if templateFromConfigMapFile != "" {
  166. var configMap corev1.ConfigMap
  167. configMapContent, err := os.ReadFile(filepath.Clean(templateFromConfigMapFile))
  168. if err != nil {
  169. return err
  170. }
  171. if err := yaml.Unmarshal(configMapContent, &configMap); err != nil {
  172. return fmt.Errorf("could not unmarshal configmap: %w", err)
  173. }
  174. p.TemplateFromConfigMap = &configMap
  175. }
  176. if templateFromSecretFile != "" {
  177. var secret corev1.Secret
  178. secretContent, err := os.ReadFile(filepath.Clean(templateFromSecretFile))
  179. if err != nil {
  180. return err
  181. }
  182. if err := yaml.Unmarshal(secretContent, &secret); err != nil {
  183. return fmt.Errorf("could not unmarshal secret: %w", err)
  184. }
  185. p.TemplateFromSecret = &secret
  186. }
  187. return nil
  188. }