workflow_engine.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. package workflow
  2. import (
  3. "bytes"
  4. "context"
  5. "fmt"
  6. "text/template"
  7. "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  8. "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  9. "github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
  10. templatev2 "github.com/external-secrets/external-secrets/pkg/template/v2"
  11. "github.com/external-secrets/external-secrets/pkg/utils"
  12. "github.com/go-logr/logr"
  13. corev1 "k8s.io/api/core/v1"
  14. "sigs.k8s.io/controller-runtime/pkg/client"
  15. )
  16. type WorkflowRunner struct {
  17. ctx context.Context
  18. client client.Client
  19. namespace string
  20. workflows []v1alpha1.WorkflowItem
  21. log logr.Logger
  22. inputs WorkflowInputs
  23. }
  24. type WorkflowOutput struct {
  25. Data map[string]string `json:"data"`
  26. Metadata map[string]map[string]string `json:"metadata"`
  27. }
  28. type WorkflowInputs struct {
  29. Workflow *WorkflowOutput `json:"workflow"`
  30. Workflows map[string]*WorkflowOutput `json:"workflows"`
  31. }
  32. func NewWorkflowRunner(ctx context.Context, client client.Client, namespace string, workflows []v1alpha1.WorkflowItem, log logr.Logger) *WorkflowRunner {
  33. return &WorkflowRunner{
  34. ctx: ctx,
  35. client: client,
  36. namespace: namespace,
  37. workflows: workflows,
  38. log: log,
  39. inputs: WorkflowInputs{
  40. Workflows: make(map[string]*WorkflowOutput),
  41. Workflow: newWorkflowOutput(),
  42. },
  43. }
  44. }
  45. func (w *WorkflowRunner) Run() error {
  46. for _, workflow := range w.workflows {
  47. err := w.runWorkflow(workflow)
  48. if err != nil {
  49. return fmt.Errorf("error running workflow %s: %w", workflow.Name, err)
  50. }
  51. }
  52. return nil
  53. }
  54. func (w *WorkflowRunner) runWorkflow(workflow v1alpha1.WorkflowItem) error {
  55. log := w.log.WithValues("workflow", workflow.Name)
  56. log.Info("running workflow")
  57. // workflowData persists the state across all steps in the workflow
  58. workflowData := newWorkflowOutput()
  59. w.inputs.Workflow = workflowData
  60. w.inputs.Workflows[workflow.Name] = workflowData
  61. for _, step := range workflow.Steps {
  62. stepData, err := w.runStep(step, workflowData, log)
  63. if err != nil {
  64. return fmt.Errorf("error running step %s: %w", step.Name, err)
  65. }
  66. // accumulate and update workflow output after each step
  67. workflowData = mergeStepOutput(workflowData, stepData)
  68. w.inputs.Workflow = workflowData
  69. w.inputs.Workflows[workflow.Name] = workflowData
  70. }
  71. return nil
  72. }
  73. func newWorkflowOutput() *WorkflowOutput {
  74. return &WorkflowOutput{
  75. Data: make(map[string]string),
  76. Metadata: map[string]map[string]string{
  77. "labels": make(map[string]string),
  78. "annotations": make(map[string]string),
  79. },
  80. }
  81. }
  82. func mergeStepOutput(step1, step2 *WorkflowOutput) *WorkflowOutput {
  83. return &WorkflowOutput{
  84. Data: mergeStringMap(step1.Data, step2.Data),
  85. Metadata: map[string]map[string]string{
  86. "labels": mergeStringMap(step1.Metadata["labels"], step2.Metadata["labels"]),
  87. "annotations": mergeStringMap(step1.Metadata["annotations"], step2.Metadata["annotations"]),
  88. },
  89. }
  90. }
  91. func mergeStringMap(map1, map2 map[string]string) map[string]string {
  92. for k, v := range map2 {
  93. map1[k] = v
  94. }
  95. return map1
  96. }
  97. func (w *WorkflowRunner) runStep(step v1alpha1.WorkflowStep, workflowOutput *WorkflowOutput, workflowLog logr.Logger) (*WorkflowOutput, error) {
  98. log := workflowLog.WithValues("step", step.Name)
  99. log.Info("running step")
  100. stepOutput := newWorkflowOutput()
  101. if step.Pull != nil {
  102. if err := w.runPullStep(step.Pull, stepOutput, log); err != nil {
  103. return nil, fmt.Errorf("error running pull step: %w", err)
  104. }
  105. }
  106. if step.Push != nil {
  107. if err := w.runPushStep(step.Push, workflowOutput, log); err != nil {
  108. return nil, fmt.Errorf("error running push step: %w", err)
  109. }
  110. }
  111. if step.Template != nil {
  112. if err := w.runTemplateStep(step.Template, stepOutput, log); err != nil {
  113. return nil, fmt.Errorf("error running template step: %w", err)
  114. }
  115. }
  116. if step.Manifests != nil {
  117. if err := w.runManifestsStep(step.Manifests, stepOutput, log); err != nil {
  118. return nil, fmt.Errorf("error running manifests step: %w", err)
  119. }
  120. }
  121. return stepOutput, nil
  122. }
  123. const (
  124. errFetchTplFrom = "error fetching templateFrom data: %w"
  125. errExecTpl = "could not execute template: %w"
  126. )
  127. func (w *WorkflowRunner) runTemplateStep(tpl *v1alpha1.WorkflowTemplate, stepOutput *WorkflowOutput, log logr.Logger) error {
  128. log.Info("running template step")
  129. for k, v := range tpl.Data {
  130. templatedValue, err := templateValue(string(v), w.inputs.ToMap())
  131. if err != nil {
  132. return fmt.Errorf(errExecTpl, err)
  133. }
  134. templatedKey, err := templateValue(k, w.inputs.ToMap())
  135. if err != nil {
  136. return fmt.Errorf(errExecTpl, err)
  137. }
  138. delete(stepOutput.Data, k)
  139. stepOutput.Data[templatedKey] = templatedValue
  140. }
  141. for k, v := range tpl.Metadata.Annotations {
  142. templatedValue, err := templateValue(string(v), w.inputs.ToMap())
  143. if err != nil {
  144. return fmt.Errorf(errExecTpl, err)
  145. }
  146. delete(stepOutput.Metadata["annotations"], k)
  147. stepOutput.Metadata["annotations"][k] = templatedValue
  148. }
  149. for k, v := range tpl.Metadata.Labels {
  150. templatedValue, err := templateValue(string(v), w.inputs.ToMap())
  151. if err != nil {
  152. return fmt.Errorf(errExecTpl, err)
  153. }
  154. delete(stepOutput.Metadata["labels"], k)
  155. stepOutput.Metadata["labels"][k] = templatedValue
  156. }
  157. return nil
  158. }
  159. func (w *WorkflowRunner) runManifestsStep(manifests []string, stepOutput *WorkflowOutput, log logr.Logger) error {
  160. return fmt.Errorf("not implemented")
  161. }
  162. func templateValue(tpl string, data any) (string, error) {
  163. t, err := template.New("template-step").
  164. Funcs(templatev2.FuncMap()).
  165. Option("missingkey=error").
  166. Parse(tpl)
  167. if err != nil {
  168. return "", fmt.Errorf("error parsing template: %w", err)
  169. }
  170. buf := bytes.NewBuffer(nil)
  171. err = t.Execute(buf, data)
  172. if err != nil {
  173. return "", fmt.Errorf("error executing template: %w", err)
  174. }
  175. return buf.String(), nil
  176. }
  177. func (w *WorkflowRunner) runPullStep(pull *v1alpha1.WorkflowStepPull, stepOutput *WorkflowOutput, log logr.Logger) error {
  178. log.Info("running pull step")
  179. mgr := secretstore.NewManager(w.client, "", true)
  180. defer mgr.Close(w.ctx)
  181. for i, data := range pull.Data {
  182. client, err := mgr.Get(w.ctx, pull.Source.SecretStoreRef, w.namespace, nil)
  183. if err != nil {
  184. return fmt.Errorf("error getting client for secret store [%d]: %w", i, err)
  185. }
  186. secretData, err := client.GetSecret(w.ctx, data.RemoteRef)
  187. if err != nil {
  188. return fmt.Errorf("error getting secret data [%d]: %w", i, err)
  189. }
  190. stepOutput.Data[data.SecretKey] = string(secretData)
  191. }
  192. for i, remoteRef := range pull.DataFrom {
  193. var secretMap map[string][]byte
  194. var err error
  195. if remoteRef.Find != nil {
  196. secretMap, err = w.handleFindAllSecrets(pull.Source, remoteRef, mgr, i)
  197. } else if remoteRef.Extract != nil {
  198. secretMap, err = w.handleExtractSecrets(pull.Source, remoteRef, mgr, i)
  199. } else if remoteRef.SourceRef != nil && remoteRef.SourceRef.GeneratorRef != nil {
  200. secretMap, err = w.handleGenerateSecrets(pull.Source, remoteRef, i)
  201. }
  202. if err != nil {
  203. return err
  204. }
  205. for k, v := range secretMap {
  206. stepOutput.Data[k] = string(v)
  207. }
  208. }
  209. return nil
  210. }
  211. func (w *WorkflowRunner) handleFindAllSecrets(sourceRef v1beta1.StoreSourceRef, remoteRef v1beta1.ExternalSecretDataFromRemoteRef, mgr *secretstore.Manager, i int) (map[string][]byte, error) {
  212. client, err := mgr.Get(w.ctx, sourceRef.SecretStoreRef, w.namespace, remoteRef.SourceRef)
  213. if err != nil {
  214. return nil, err
  215. }
  216. secretMap, err := client.GetAllSecrets(w.ctx, *remoteRef.Find)
  217. if err != nil {
  218. return nil, err
  219. }
  220. secretMap, err = utils.RewriteMap(remoteRef.Rewrite, secretMap)
  221. if err != nil {
  222. return nil, fmt.Errorf("unable to rewrite map .dataFrom[%d]: %w", i, err)
  223. }
  224. secretMap, err = utils.DecodeMap(remoteRef.Find.DecodingStrategy, secretMap)
  225. if err != nil {
  226. return nil, fmt.Errorf("unablet to decode map %s[%d]: %w", ".dataFrom", i, err)
  227. }
  228. return secretMap, err
  229. }
  230. func (w *WorkflowRunner) handleExtractSecrets(sourceRef v1beta1.StoreSourceRef, remoteRef v1beta1.ExternalSecretDataFromRemoteRef, mgr *secretstore.Manager, i int) (map[string][]byte, error) {
  231. client, err := mgr.Get(w.ctx, sourceRef.SecretStoreRef, w.namespace, remoteRef.SourceRef)
  232. if err != nil {
  233. return nil, err
  234. }
  235. secretMap, err := client.GetSecretMap(w.ctx, *remoteRef.Extract)
  236. if err != nil {
  237. return nil, err
  238. }
  239. secretMap, err = utils.RewriteMap(remoteRef.Rewrite, secretMap)
  240. if err != nil {
  241. return nil, fmt.Errorf("unable to rewrite map at .dataFrom[%d]: %w", i, err)
  242. }
  243. if len(remoteRef.Rewrite) == 0 {
  244. secretMap, err = utils.ConvertKeys(remoteRef.Extract.ConversionStrategy, secretMap)
  245. if err != nil {
  246. return nil, fmt.Errorf(errConvert, err)
  247. }
  248. }
  249. secretMap, err = utils.DecodeMap(remoteRef.Extract.DecodingStrategy, secretMap)
  250. if err != nil {
  251. return nil, fmt.Errorf("unable to decode map at %s[%d]: %w", ".dataFrom", i, err)
  252. }
  253. return secretMap, err
  254. }
  255. func (w *WorkflowRunner) handleGenerateSecrets(sourceRef v1beta1.StoreSourceRef, remoteRef v1beta1.ExternalSecretDataFromRemoteRef, i int) (map[string][]byte, error) {
  256. // TODO
  257. return nil, fmt.Errorf("not implemented")
  258. }
  259. func (w *WorkflowRunner) runPushStep(push *v1alpha1.WorkflowStepPush, stepOutput *WorkflowOutput, log logr.Logger) error {
  260. log.Info("running push step")
  261. mgr := secretstore.NewManager(w.client, "", true)
  262. defer mgr.Close(w.ctx)
  263. byteData := make(map[string][]byte)
  264. for k, v := range stepOutput.Data {
  265. byteData[k] = []byte(v)
  266. }
  267. secret := corev1.Secret{
  268. Data: byteData,
  269. }
  270. for i, data := range push.Data {
  271. client, err := mgr.Get(w.ctx, push.Destination.SecretStoreRef, w.namespace, nil)
  272. if err != nil {
  273. return fmt.Errorf("error getting client for secret store [%d]: %w", i, err)
  274. }
  275. err = client.PushSecret(w.ctx, &secret, data)
  276. if err != nil {
  277. return fmt.Errorf("error setting secret data [%d]: %w", i, err)
  278. }
  279. }
  280. return nil
  281. }
  282. func (wo WorkflowOutput) ToMap() map[string]interface{} {
  283. return map[string]interface{}{
  284. "data": wo.Data,
  285. "metadata": wo.Metadata,
  286. }
  287. }
  288. func (wi WorkflowInputs) ToMap() map[string]interface{} {
  289. workflows := make(map[string]interface{})
  290. for k, v := range wi.Workflows {
  291. workflows[k] = v.ToMap()
  292. }
  293. return map[string]interface{}{
  294. "workflow": wi.Workflow.ToMap(),
  295. "workflows": workflows,
  296. }
  297. }