template.go 6.6 KB

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