Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zarf Component Chart Values/Outputs #1137

Open
runyontr opened this issue Dec 16, 2022 · 21 comments · Fixed by #1144
Open

Zarf Component Chart Values/Outputs #1137

runyontr opened this issue Dec 16, 2022 · 21 comments · Fixed by #1144

Comments

@runyontr
Copy link
Contributor

runyontr commented Dec 16, 2022

As a package developer, I would like to be able to reference objects that were created by previous components.

For example, as part of #939, the ability to define a terraform component that creates cloud resources for an application, e.g. an RDS database and then inject the credentials for that database correctly into the application as one component, would provide the ability for a Zarf package creator to declare how the application should be run in production.

Explicitly, when deploying a terraform module like this,

# This file would live in the ./terraform/ folder so the foobar component can call it correctly

resource "random_integer" "sample" {
  min = 1
  max = 50000
}

output "number" {
  value = random_integer.sample.result
}

I would like to be able to use the outputs from the terraform module.

kind: ZarfPackageConfig
metadata:
  name: outputs
  description: 'Test component to demonstrate how outputs from terraform modules can be used'


components:
  - name: foobar
    description: "Sample terraform that creates things that we'll need later"
    required: true
    terraform:
      module: ./terraform/

  - name: script
    description: "This is a zebra and they have ###ZARF_COMPONENT_FOOBAR_TF_OUTPUT_NUMBER###"

In this example, the component named foobar has terraform outputs available that get templated like variables. This could be done via a new type of variable outlined by ###ZARF_COMPONENT_<NAME>_<TYPE>_OUTPUT_<OUTPUTNAME>###.

Another option could be to add definitions to have the outputs be saved as a variable with a definition like:

  - name: foobar
    description: "Sample terraform that creates things that we'll need later"
    required: true
    terraform:
      module: ./terraform/
      output:
        baz: number

  - name: script
    description: "This is a zebra and they have ###ZARF_PKG_VAR_BAZ###"

Where the variable baz is assigned the output of the number output from the terraform module and then treated like a variable.

@mikevanhemert
Copy link

Love this for passing information from one component to the next within a single Zarf package. Zarf's build-time constants and deploy-time variables are great but lacking when a component produces something dynamic that needs to be consumed by a downstream component.

The first option described feels too brittle. The second option with saved outputs is a better / more generic solution.

Take, for bad example, Zarf component needs-db that needs a database URL and creds at deploy time. A terraform component can write the same outputs as an on-prem component, meaning the needs-db can work equally well when it's database was stood up in AWS by Terraform as when it is deployed on-prem and needs to interface with a database provisioned on targeted hardware.

graph TD;
    terraform-->needs-db;
    on-prem-->needs-db;
Loading

I would be interested in expanding this capability. Has there been thought/debate on adding a simple key:value store to Zarf? It would be built during package deploy runtime, consist of variables and their values, and be written to / read from a Zarf managed cluster.

Using the bad example above, upgrading the needs-db component can be done without the either of the component it depends on because their outputs are persisted and read from within the cluster.

This key:value capability could also be used for more complicated situations like saving Terraform state like in #1140

@runyontr
Copy link
Contributor Author

runyontr commented Jan 4, 2023

I think the second option

 - name: foobar
    description: "Sample terraform that creates things that we'll need later"
    required: true
    terraform:
      module: ./terraform/
      output:
        baz: number

  - name: script
    description: "This is a zebra and they have ###ZARF_PKG_VAR_BAZ###"

would work well for the exact reasons you mentioned. it allows for better flexibility in HOW those variables get set and allow for better component composibility.

For implementation details, would the ZarfComponent's each need to implement their own output/vars/something else field in the component that would set the global variable cache within the Zarf runtime since each component would have different types of data it could export as a variable. E.g. for Helm Charts, it could be the Chart Release Name, or Chart Version, or maybe even a values path. So the structs may change like this:

// ZarfChart defines a helm chart to be deployed.
type ZarfChart struct {
	Name        string   `json:"name" jsonschema:"description=The name of the chart to deploy; this should be the name of the chart as it is installed in the helm repo"`
	ReleaseName string   `json:"releaseName,omitempty" jsonschema:"description=The name of the release to create; defaults to the name of the chart"`
	Url         string   `json:"url,omitempty" jsonschema:"oneof_required=url,description=The URL of the chart repository or git url if the chart is using a git repo instead of helm repo"`
	Version     string   `json:"version" jsonschema:"description=The version of the chart to deploy; for git-based charts this is also the tag of the git repo"`
	Namespace   string   `json:"namespace" jsonschema:"description=The namespace to deploy the chart to"`
	ValuesFiles []string `json:"valuesFiles,omitempty" jsonschema:"description=List of values files to include in the package; these will be merged together"`
	GitPath     string   `json:"gitPath,omitempty" jsonschema:"description=The path to the chart in the repo if using a git repo instead of a helm repo"`
	LocalPath   string   `json:"localPath,omitempty" jsonschema:"oneof_required=localPath,description=The path to the chart folder"`
	NoWait      bool     `json:"noWait,omitempty" jsonschema:"description=Wait for chart resources to be ready before continuing"`
+        Outputs  map[string]string `json:"outputs,omitempty" jsonschema:"description=outputs from the component to be saved as variables"`
}

// ZarfManifest defines raw manifests Zarf will deploy as a helm chart
type ZarfManifest struct {
	Name                       string   `json:"name" jsonschema:"description=A name to give this collection of manifests; this will become the name of the dynamically-created helm chart"`
	Namespace                  string   `json:"namespace,omitempty" jsonschema:"description=The namespace to deploy the manifests to"`
	Files                      []string `json:"files,omitempty" jsonschema:"description=List of individual K8s YAML files to deploy (in order)"`
	KustomizeAllowAnyDirectory bool     `json:"kustomizeAllowAnyDirectory,omitempty" jsonschema:"description=Allow traversing directory above the current directory if needed for kustomization"`
	Kustomizations             []string `json:"kustomizations,omitempty" jsonschema:"description=List of kustomization paths to include in the package"`
	NoWait                     bool     `json:"noWait,omitempty" jsonschema:"description=Wait for manifest resources to be ready before continuing"`
+        Outputs  map[string]string `json:"outputs,omitempty" jsonschema:"description=outputs from the component to be saved as variables"`
}

so that the Zarf.yaml could be

components:
  - name: component-name
    charts:
      - name: chart-name
        localPath: path/to/chart
        output:
          baz: ".Release.Name" // not sure I love the chart templating engine here, but it seems powerful
          istio: ".Values.istio.enabled"

@Racer159
Copy link
Contributor

Racer159 commented Jan 4, 2023

I think the output sub block of option 2 could work well, but I would argue that we shouldn't re-template the zarf.yaml file if we can avoid it. IMO these would become component variables (template-able with ZARF_VAR_ and then scripts (now actions) should ideally pick that up). If we could swing an easy way to do it a global outputs field at the component level (vice chart/terraform/etc level) would be cool as well. If the action saved a yaml/json file we could pick that back up agnostically.

@jeff-mccoy
Copy link
Contributor

Agree with @Racer159 variables would be saner than package variables because of how they work under the hood. Also thinking about how you actually map outputs, what would that look like? How would we know to map things say given a helm chart install or script run, how would we know how to handle multiple outputs? Would there be some stdio string parsing happening?

@Racer159
Copy link
Contributor

Racer159 commented Jan 5, 2023

I think we could just handle them globally for now (requiring people to name them appropriately for now) such that any downstream component that used a ZARF_VAR_ with that name would just get templated with it. To me picking up the correct string (which I think this should be limited to for now) would be the tougher part across all of the various technologies Zarf uses.

For helm charts something like this could make sense:

components:
  - name: component-name
    charts:
      - name: chart-name
        localPath: path/to/chart
        outputs:
          - name: BAZ
            file: Chart.yaml
            key: ".Release.Name"
          - name: ISTIO
            file: values.yaml
            key: ".Values.istio.enabled"

Don't love them under each and every type of thing though since you would also need it for files, manifests and more potentially...

@runyontr
Copy link
Contributor Author

runyontr commented Jan 5, 2023

I was picturing something more crafted to each type of component rather than making outputs generic/consistent across all the components. This would obvious require a lot of documentation and nuance, but each type of ZarfComponent has its own type of data that would be useful to export, and it might be easier/more flexible to allow each component to define how to define that... but that does create some frustrations when trying to get exports from different types of components

Terraform

Any of the outputs available from the terraform module

Chart

  • Anything available in the templating engine, like has been shown
  • Metadata about the chart

Files

  • Something closer to what @Racer159 identified in being able to parse through a file and pull out particular json/yaml values

Scripts

  • the stdout, stderr or both

Manifests

  • Maybe referencing the objects that get created from the manifest?

Plugins

  • As SPIKE: Zarf Plugin System implementation #1136 gets resolved, we can allow plugins to define how they want to see the configuration for what/how new variables get define without prescribing to a schema within the Zarf.yaml, but we'll have to have a particular data type for how Zarf gets the data back from the plugin (ZarfComponent) to facilitate that standardized interaction

In my head this is the back and forth between Zarf (global) and the components:

graph 
    subgraph zarfcomponent[Zarf Component1]

        Template[Template Component] --> ComponentDeploy[Deploy Component]
        ComponentDeploy --> CheckStatus[Check Deploy Result]
        CheckStatus -->  CreateOutputs[Create Outputs Map]
        
    end
    subgraph zarfcomponent2[Zarf Component2]

        Template2[Template Component] --> ComponentDeploy2[Deploy Component]
        ComponentDeploy2 --> CheckStatus2[Check Deploy Result]
        CheckStatus2 -->  CreateOutputs2[Create Outputs Map]
    end
    subgraph zarf
        load[Load Zarf.yaml] --> LoadVariables
        LoadVariables --> Compose
        Compose[Compose Components] --> Deploy
        Deploy --Pass Global Variables --> Template
        CreateOutputs --Pass Outputs Back --> CheckResults
        CheckResults[Check Result] --> UpdateVariables
        UpdateVariables[Update Global Vars] --> Deploy2
        Deploy2[Deploy] --Pass Global Variables --> Template2
        CreateOutputs2 --Pass Outputs Back --> CheckResults2
        CheckResults2[Check Result] --> UpdateVariables2[Update Global Vars]
    end
    
Loading

@mikevanhemert
Copy link

Instead of placing outputs at each type of thing, does it make sense for the component to have an outputs block and implement different ways to populate the outputs? The example below could work for Terraform outputs and a Helm value.

components:
  - name: example
    # assume one or more chart, repo, manifest, script block, terraform or whtaever
    outputs:
      - name: S3_BUCKET
        terraform: s3_bucket
      - name: domain
        namespace: bigbang
        helm: istio-system-istio
        yq: .gateways.public.servers[0].hosts[0]

Tangentially, has there been any consideration for component upgrades? Assume component A generates a dynamic value that is used by component B. As a zarf package maintainer, I want to upgrade component B with a slim package that only contains B. How can I ensure that the human deploying the package will be successful? Will they be prompted for a value? Do I, as the package maintainer, need to extract the dynamic value A generated? I would like the global variables to be persisted in-cluster but don't know the bigger ramifications of doing business this way.

@runyontr
Copy link
Contributor Author

#1144

This PR is also looking to provide the ability to create new variables inside of action ZarfComponents.

@jeff-mccoy will this capability be able to be used for non-action components by chaining them together as separate components:

-  name: helm
    chart: 
        ...
-  name: export-variable
    actions:
      onDeploy:
        before:
        - cmd: "helm get values -n bigbang bigbang  | jq .istio.enabled"
          setVariable: ISTIO_ENABLED

or will the action command have a known running order (before or after) that would let things like:

-  name: helm
    chart: 
        ...
    actions:
      onDeploy:
        before:
        - cmd: "helm get values -n bigbang bigbang  | jq .istio.enabled"
          setVariable: ISTIO_ENABLED

@jeff-mccoy
Copy link
Contributor

@runyontr that's correct, as soon as you set the value in an action, the very next action and any future component can access the value. I was actually wondering how best to grab the data from something like helm and this answer that question. I would like to explore handling multiple variables at once, something like

-  name: helm
    chart: 
        ...
    actions:
      onDeploy:
        before:
        - cmd: "helm get values -n bigbang bigbang -o json"
          setJSONValues: BIGBANG

@jeff-mccoy
Copy link
Contributor

We don't need that to be in this PR, I just wanted to establish basic structure for the actions conversion in this first PR.

@jeff-mccoy
Copy link
Contributor

Added an example showing another component file template using the declared and dynamic variables.
Screenshot 2023-01-17 at 10 18 52 AM
Screenshot 2023-01-17 at 10 17 50 AM
Screenshot 2023-01-17 at 10 17 48 AM

@jeff-mccoy
Copy link
Contributor

After we settle on this implementation, I think we can reasonably talk about KV persistence from a package deployment. We would have to ensure uniqueness, so that will be a challenge, but I imagine something along the lines of

kind: ZarfPackageConfig
metadata:
  name: component-actions
  description: "Component actions examples"
  peristVariables: true

We could use the package name, but it's not a very strong guarantee of uniqueness. Alternatively, we could require something like "package name + some provided unique key" or a hash of the package metadata, etc.

@jeff-mccoy jeff-mccoy added this to the v0.24.0 milestone Jan 17, 2023
@jeff-mccoy jeff-mccoy mentioned this issue Jan 17, 2023
12 tasks
@jeff-mccoy jeff-mccoy self-assigned this Jan 17, 2023
@Racer159
Copy link
Contributor

@runyontr that's correct, as soon as you set the value in an action, the very next action and any future component can access the value. I was actually wondering how best to grab the data from something like helm and this answer that question. I would like to explore handling multiple variables at once, something like

-  name: helm
    chart: 
        ...
    actions:
      onDeploy:
        before:
        - cmd: "helm get values -n bigbang bigbang -o json"
          setJSONValues: BIGBANG

This would actually be pretty cool. Something like this would be really powerful:

-  name: helm
    chart: 
        ...
    actions:
      onDeploy:
        before:
        - cmd: "helm get values -n bigbang bigbang -o json"
          set:
            type: json
            values: 
              - name: BIGBANG_DOMAIN
                path: ".domain"

@jeff-mccoy
Copy link
Contributor

that's probably a smarter way to do it tbh 😆

@jeff-mccoy
Copy link
Contributor

I bet we have enough helm code in the codebase to actually make that more native too...pull that out of actions even.

@runyontr
Copy link
Contributor Author

runyontr commented Jan 17, 2023

This kinda goes back to the conversation we were having around:

Does each noun have its own "output" ability that's coded (e.g. the values, or helm name), or is there a generic way to make outputs that's implemented globally. I like this structure, but it could be something like:

-  name: helm
    chart: 
      name: bigbang
      output:
      -  name: BIGBANG_DOMAIN
          values: "{{ .Values.domain }}"
      - name: BIGBANG_NAMESPACE
         values: "{{  .Release.Namespace }}"

where the string that's provided to the values field literally gets helm templated by zarf and provided to the variable. I think its very natural to have a command output be the hook to export variables for an action, and a HELM templating of a string be the output api for Helm.

It would also localize implementation detail for that capability once plugins become a thing and we can provide each plugin its own ability to export values

@jeff-mccoy
Copy link
Contributor

Yeah I feel pretty strongly we should be consistent with the verb here. We did setVariable on actions due to feedback from PB folks on preference for explicit wording on keys... and showOutput was a verb in scripts, so out or output might be confusing there. I don't love setVariable and it might be confusing on helm or manifest nouns--but I do think we need to pick something and be consistent.

@jeff-mccoy
Copy link
Contributor

For example, instead of values: "{{ Values..domain }}", fromValues: "{{ Values..domain }}" I think would be more explicit.

@Racer159
Copy link
Contributor

Racer159 commented Jan 17, 2023

We already internally use just set to describe setting a zarf variable. Would that work here:

 -  name: helm
    chart: 
      name: bigbang
      set:
      - name: BIGBANG_DOMAIN
        value: "{{ .Values.domain }}"
      - name: BIGBANG_NAMESPACE
        value: "{{  .Release.Namespace }}"

@jeff-mccoy
Copy link
Contributor

Yeah though I still think setVariable is more explicit than set. I prefer shorter, but reasonable feedback has been longer is better than assuming.

@Racer159
Copy link
Contributor

Racer159 commented Jan 17, 2023

I actually think there is room for both set and setVariable (with setVariable as the simpler option and set being the configurable one). My main point is I would go for set over output/out. We can intro the simple one first and then see how to build a more complex version with more feedback, but I also don't think we will need to drop the simple version either.

@github-project-automation github-project-automation bot moved this from New Requests to Done in Zarf Project Board Jan 24, 2023
@jeff-mccoy jeff-mccoy reopened this Jan 24, 2023
@jeff-mccoy jeff-mccoy removed this from the v0.24.x milestone Feb 2, 2023
Noxsios pushed a commit that referenced this issue Mar 8, 2023
## Related Issues
Related to #939 
Fixes #1137, #1022

### Backwards compatible replacement of simple `scripts` with complex
`actions`
- [x] Define types
- [x] `package create` transparent migration
- [x] `package deploy` transparent migration
- [x] Introduce deprecation pattern into `src/pkg/packager`
- [x] Package`Create` actions: `Before`, `After`, `Success`, `Failure`
- [x] Package`Deploy` actions: `Before`, `After`, `Success`, `Failure` 
- [x] Package`Remove` actions: `Before`, `After`, `Success`, `Failure`
  
### Other refactors
- [x] Expand exec to support env variables, add config struct
- [x] Add common cmd defaults
- [x] Add set working dir support 
- [x] Inject deploy-time variables as env variables for `onDeploy`
- [x] Expand sha256sum function to support any generic crypto hash type

Co-authored-by: Wayne Starr <[email protected]>
Co-authored-by: Jon Perry <[email protected]>
@Racer159 Racer159 changed the title Zarf Component Outputs Zarf Component Chart Values/Outputs Mar 27, 2023
@salaxander salaxander added this to Zarf Jul 22, 2024
@github-project-automation github-project-automation bot moved this to Backlog in Zarf Jul 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Backlog
Development

Successfully merging a pull request may close this issue.

4 participants