# GitOps Action

The GitOps action creates commits and pushes changes to a git repository.

Common use cases:

* Enable developers to provision and manage Infrastructure as Code through a GUI interface, while DevOps and Platform engineers maintain their preferred tooling workflow
* Implement guardrail-driven access for developers to make infrastructure changes
* Use native support for Flux and Kustomization to automatically identify git repositories and files using `originAnnotations`

```
edit-kubernetes-manifests-gitops.yaml
# Source: mission-control-playbooks-flux/templates/edit.yaml

apiVersion: mission-control.flanksource.com/v1

kind: Playbook

metadata:

  name: kustomize-edit

spec:

  title: 'Edit'

  category: Flux

  description: Updates the source of a GitOps managed object by submitting a Git PR

  icon: flux

  parameters:

    - default: 'chore: update $(.config.type)/$(.config.name)'

      label: Commit Message

      name: commit_message

    # Retrieves the JSON configuration for the selected config item, passing it through the `neat` function

    # to strip out runtime information like managedFields and status, and then converts back to YAML

    # for easy editing

    - default: $(.config.config | toJSON | neat | json | toYAML)

      label: "YAML"

      name: yamlInput

      properties:

        size: large

      type: code

    # Lookup the Git repository from Flux source of the Kustomzation that created the select config item

    - default: '$(.git.git.url)'

      label: Git Repo

      name: url

    # Lookup the path of the config item in the Git repository using origin annotations

    - default: '$(.git.git.file)'

      label: File

      name: file

  configs:

    # This playbook can only be run against Kubernetes objects created from a FluxCD Kustomization CRD

    - labelSelector: 'kustomize.toolkit.fluxcd.io/name'

  actions:

    - name: Create Pull Request With Changes

      gitops:

        repo:

          connection: connection://mission-control/github

          type: github

          url: $(.git.git.url | strings.ReplaceAll "ssh://git@" "https://")

          base: "$(.git.git.branch)"

          branch: edit-manifest-$(random.Alpha 8)

        commit:

          # Use the user submitting the playbook as the author of the git commits

          author: '$(.user.name)'

          email: '$(.user.email)'

          message: $(.params.commit_message)

        pr:

          title: $(.params.commit_message)

        patches:

          - path: '$(.params.file)'

            # patch the file using YQ, finding the document in a multi-doc yaml file using Kind and Name

            yq: |

              select(

                .kind=="$(.config.config | jq `.kind`)" and

                .metadata.name=="$(.config.config | jq `.metadata.name`)"

              ) |= $(.params.yamlInput | yaml | toJSON)
```

| Field         | Description                                                                            | Scheme                                                                                                                                                   |
| ------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name*`       | Step Name                                                                              | `string`                                                                                                                                                 |
| `gitops`      | Gitops Action                                                                          | [Gitops](#gitops)                                                                                                                                        |
| `delay`       | A delay before running the action e.g. `8h`                                            | [`Duration`](/docs/reference/types.md#duration) or [CEL](/docs/reference/scripting/cel.md) with [Playbook Context](/docs/reference/playbooks/context.md) |
| `filter`      | Conditionally run an action                                                            | [CEL](/docs/reference/scripting/cel.md) with [Playbook Context](/docs/reference/playbooks/context.md)                                                    |
| `runsOn`      | Which [runner](/docs/guide/playbooks/concepts/runners.md) (agent) to run the action on | [`[]Agent`](/docs/reference/types.md#agent)                                                                                                              |
| `templatesOn` | Where templating (and secret management) of actions should occur                       | `host` or `agent`                                                                                                                                        |
| `timeout`     | Timeout on this action.                                                                | [Duration](/docs/reference/types.md#duration)                                                                                                            |

## Gitops[​](#gitops "Direct link to Gitops")

| Field     | Description                                                                          | Scheme                       |
| --------- | ------------------------------------------------------------------------------------ | ---------------------------- |
| `commit*` | Commit details                                                                       | [Commit](#commit-detail)     |
| `repo*`   | The repository details                                                               | [Repository](#repository)    |
| `files`   | List of files to create                                                              | [\[\]File](#file)            |
| `patches` | List of JQ/YQ patches to apply                                                       | [\[\]Patch](#patch)          |
| `pr`      | Create a Pull Request instead of a commit (Only available for `github` and `gitlab`) | [PullRequest](#pull-request) |

> You must specify `patches` and/or<!-- --> `files`

### Repository[​](#repository "Direct link to Repository")

| Field          | Description                                                                                              | Scheme                                            |
| -------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| `base`         | Branch to clone. Defaults to `main`                                                                      | `string`                                          |
| `branch`       | The branch to update. Defaults to base                                                                   | `string`                                          |
| `connection`   | Connection name to use the credentials for the git repo. When set, `username` and `password` are ignored | `string`                                          |
| `force`        | Overwrite history when pushing                                                                           | `boolean`                                         |
| `password`     | Password or access token to authenticate with                                                            | [EnvVar](/docs/reference/env-var.md)              |
| `skipExisting` | Do not push to branches that already exist                                                               | `boolean`                                         |
| `type`         | Git repository type                                                                                      | `github`, `gitlab`, `azure_devops`, `http`, `git` |
| `url`          | URL of the git repository                                                                                | `string`                                          |
| `username`     | Username to authenticate with                                                                            | [EnvVar](/docs/reference/env-var.md)              |

> You must specify `connection` or `url` <!-- -->but not both

warning

`force` overwrites the remote branch history. Any commits a collaborator pushed to that branch since the last clone are discarded. Leave `force` unset unless the branch belongs solely to this playbook.

### Commit Detail[​](#commit-detail "Direct link to Commit Detail")

| Field     | Description    | Scheme   | Required | Templatable |
| --------- | -------------- | -------- | -------- | ----------- |
| `author`  | Author name    | `string` | `true`   | `true`      |
| `email`   | Author email   | `string` | `true`   |             |
| `message` | Commit message | `string` | `true`   |             |

### Pull Request[​](#pull-request "Direct link to Pull Request")

| Field               | Description                                         | Scheme     | Required | Templatable |
| ------------------- | --------------------------------------------------- | ---------- | -------- | ----------- |
| `title`             | Title of the Pull request                           | `string`   | `true`   | `true`      |
| `tags`              | Tags to add to the PR                               | `[]string` |          | `true`      |
| `autoMerge.enabled` | Merge the pull request automatically when supported | `bool`     |          |             |
| `autoMerge.rebase`  | Rebase instead of merging                           | `bool`     |          |             |

### Patch[​](#patch "Direct link to Patch")

Patches modify existing files on the git repo. You can either use `yq` to modify yaml files or `jq` to modify json files.

| Field  | Description                                     | Scheme   | Required | Templatable |
| ------ | ----------------------------------------------- | -------- | -------- | ----------- |
| `path` | Path to patch                                   | `string` | `true`   | `true`      |
| `yq`   | yq query                                        | `string` | `false`  | `true`      |
| `jq`   | jq query                                        | `string` | `false`  | `true`      |
| `if`   | CEL expression to conditionally apply the patch | `string` | `false`  |             |

### File[​](#file "Direct link to File")

Files create or delete existing files on the git repo.

| Field     | Description                                                               | Scheme   | Required | Templatable |
| --------- | ------------------------------------------------------------------------- | -------- | -------- | ----------- |
| `path`    | Path to file                                                              | `string` | `true`   | `true`      |
| `content` | Content of the file. Use the `$delete` keyword to delete an existing file | `string` | `true`   | `true`      |

## Custom Origin Annotation[​](#custom-origin-annotation "Direct link to Custom Origin Annotation")

In the previous example, we demonstrated a playbook that edited Flux-created manifests. However, there was a limitation - it contained hardcoded variables for the git repository, git branch, and kustomization file path. When managing configurations across multiple clusters, this approach would require duplicating the playbook for each cluster due to different git repositories and kustomization paths. The same duplication would be needed even for multiple Flux kustomizations within a single cluster.

A more efficient approach is to create a single generic GitOps playbook that works with configurations from any Flux source and kustomization.

When you run a GitOps action against a config resource containing a `config.kubernetes.io/origin` annotation, the following [variables](/docs/reference/playbooks/env-vars.md) become automatically available:

| Field            | Description                                                                                                                                 | Scheme   |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `git.branch`     | The primary branch                                                                                                                          | `string` |
| `git.file`       | Name of the file that created the config resource the playbook is run against.                                                              | `string` |
| `git.url`        | URL of the git repository                                                                                                                   | `string` |
| `kustomize.path` | Path of the kustomization file that created the config resource the playbook is run against. This comes from the spec of the kustomization. | `string` |

These env vars are extracted by traversing up the Flux Kustomization and Git Repository that created the config resource the playbook runs against.

To tag all the resources with that annotation, add `originAnnotations` to the `buildMetadata` field as shown below:

Setting origin annotation

```
kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1

kind: Kustomization

buildMetadata: [originAnnotations]

resources:

- monitoring
```

The above kustomization would add the following annotation to the config resource:

```
monitoring/alertmanager.yaml
apiVersion: apps/v1

kind: Deployment

metadata:

  annotations:

    config.kubernetes.io/origin: |

      path: monitoring/alertmanager.yaml

  ...
```

### Example[​](#example "Direct link to Example")

* Wihout origin annotation
* With origin annotation

```
edit-kubernetes-manifests-gitops.yaml
apiVersion: mission-control.flanksource.com/v1

kind: Playbook

metadata:

  name: edit-kubernetes-manifests-gitops

spec:

  title: 'Edit Kustomize Resource'

  icon: flux

  parameters:

    - default: 'chore: update $(.config.type)/$(.config.name)'

      label: Commit Message

      name: commit_message

    - default: $(.config.config | toJSON | neat | json | toYAML)

      label: ""

      name: yamlInput

      properties:

        size: large

      type: code

  configs:

    - labelSelector: 'kustomize.toolkit.fluxcd.io/name'

  env:

    - name: file_path

      value: {{ .config.config | jq `.metadata.annotations["config.kubernetes.io/origin"]` | yaml).path }}

    - name: kustomization_path

      value:  {{ (catalog_traverse .config.id  "Kubernetes::Kustomization").Config | json | jq `.spec.path` }}

    - name: git_url

      value:  {{ (catalog_traverse .config.id  "Kubernetes::Kustomization/Kubernetes::GitRepository").Config | json | jq `.spec.url` }}

    - name: git_branch

      value:  {{ (catalog_traverse .config.id  "Kubernetes::Kustomization/Kubernetes::GitRepository").Config | json | jq `.spec.ref.branch` }}

  actions:

    - name: Create Pull Request With Changes

      gitops:

        repo:

          url: '$(.env.git_url)'

          connection: 'connection://default/github'

          base: '$(.env.git_branch)'

          branch: edit-manifest-$(random.Alpha 8)

        commit:

          author: '$(.user.name)'

          email: '$(.user.email)'

          message: $(.params.commit_message)

        pr:

          title: $(.params.commit_message)

        patches:

          - path: '$(filepath.Join .env.kustomization_path .env.file_path)'

            yq: |

              select(

                .kind=="$(.config.config | jq `.kind`)" and

                .metadata.name=="$(.config.config | jq `.metadata.name`)"

              ) |= $(.params.yamlInput | yaml | toJSON)
```

```
edit-kubernetes-manifests-gitops.yaml
apiVersion: mission-control.flanksource.com/v1

kind: Playbook

metadata:

  name: edit-kubernetes-manifests-gitops

spec:

  title: 'Edit Kustomize Resource'

  icon: flux

  parameters:

    - default: 'chore: update $(.config.type)/$(.config.name)'

      label: Commit Message

      name: commit_message

    - default: $(.config.config | toJSON | neat | json | toYAML)

      label: ''

      name: yamlInput

      properties:

        size: large

      type: code

  configs:

    - labelSelector: 'kustomize.toolkit.fluxcd.io/name'

  actions:

    - name: Create Pull Request With Changes

      gitops:

        repo:

          url: '$(.env.git.url)'

          connection: 'connection://default/github'

          base: '$(.env.git.branch)'

          branch: edit-manifest-$(random.Alpha 8)

        commit:

          author: '$(.user.name)'

          email: '$(.user.email)'

          message: $(.params.commit_message)

        pr:

          title: $(.params.commit_message)

        patches:

          - path: '$(filepath.Join .env.kustomization.path .env.git.file)'

            yq: |

              select(

                .kind=="$(.config.config | jq `.kind`)" and

                .metadata.name=="$(.config.config | jq `.metadata.name`)"

              ) |= $(.params.yamlInput | yaml | toJSON)
```

## Templating[​](#templating "Direct link to Templating")

### CEL Expressions[​](#cel-expressions "Direct link to CEL Expressions")

The following variables can be used within the [CEL](/docs/reference/scripting/cel.md) expressions of `filter`, `if`, `delays` and `parameters.default`:

| Field        | Description                                   | Schema                                                     |
| ------------ | --------------------------------------------- | ---------------------------------------------------------- |
| `config`     | Config passed to the playbook                 | [`ConfigItem`](/docs/reference/config-db/.md)              |
| `check`      | Canary Check passed to the playbook           | [`Check`](/docs/reference/canary-checker/check.md)         |
| `playbook`   | Playbook passed to the playbook               | [`Playbook`](/docs/reference/playbooks/.md)                |
| `run`        | Current run                                   | [`Run`](/docs/reference/playbooks/.md#run)                 |
| `params`     | User provided parameters to the playbook      | `map[string]any`                                           |
| `request`    | Webhook request                               | [`Webhook Request`](/docs/reference/playbooks/webhooks.md) |
| `env`        | Environment variables defined on the playbook | `map[string]any`                                           |
| `user.name`  | Name of the user who invoked the action       | `string`                                                   |
| `user.email` | Email of the user who invoked the action      | `string`                                                   |
| `agent.id`   | ID of the agent the resource belongs to.      | `string`                                                   |
| `agent.name` | Name of the agent the resource belongs to.    | `string`                                                   |

Conditionally Running Actions

Playbook actions can be selectively executed based on CEL expressions. These expressions must either return

* a boolean value (`true` indicating run the action & skip the action otherwise)
* or a special function among the ones listed below

| Function    | Description                                                 |
| ----------- | ----------------------------------------------------------- |
| `always()`  | run no matter what; even if the playbook is cancelled/fails |
| `failure()` | run if any of the previous actions failed                   |
| `skip()`    | skip running this action                                    |
| `success()` | run only if all previous actions succeeded (default)        |
| `timeout()` | run only if any of the previous actions timed out           |

```
delete-kubernetes-pod.yaml
---

apiVersion: mission-control.flanksource.com/v1

kind: Playbook

metadata:

  name: notify-send-with-filter

spec:

  parameters:

    - name: message

      label: The message for notification

      default: '{{.config.name}}'

  configs:

    - types: 

        - Kubernetes::Pod

  actions:

    - name: Send notification

      exec:

        script: notify-send "{{.config.name}} was created"

    - name: Bad script

      exec:

        script: deltaforce

    - name: Send all success notification

      if: success() # this filter practically skips this action as the second action above always fails

      exec:

        script: notify-send "Everything went successfully"

    - name: Send notification regardless

      if: always()

      exec:

        script: notify-send "a Pod config was created"
```

Defaulting Parameters

```
delete-kubernetes-pod.yaml
apiVersion:

  mission-control.flanksource.com/v1

  kind: Playbook

  metadata:

    name: edit

  spec:

    title: 'Edit Kustomize Resource'

    icon: flux

    parameters:

    - default: 'chore: update $(.config.type)/$(.config.name)'

      name: commit_message
```

### Go Templating[​](#go-templating "Direct link to Go Templating")

When templating `actions` with [Go Templates](/docs/reference/scripting/gotemplate.md), the context variables are available as fields of the template's context object `.` eg `.config`, `.user.email`

Templating Actions

```
delete-kubernetes-pod.yaml
apiVersion: mission-control.flanksource.com/v1

kind: Playbook

metadata:

  name: scale-deployment

spec:

  description: Scale Deployment

  configs:

    - types:

        - Kubernetes::Deployment

  parameters:

    - name: replicas

      label: The new desired number of replicas.

  actions:

    - name: kubectl scale

      exec:

        script: |

          kubectl scale --replicas={{.params.replicas}} \

            --namespace={{.config.tags.namespace}} \

            deployment {{.config.name}}
```

### Functions[​](#functions "Direct link to Functions")

| Function              | Description                                    | Return          |
| --------------------- | ---------------------------------------------- | --------------- |
| `getLastAction()`     | Returns the result of the action that just run | Action Specific |
| `getAction({action})` | Return the result of a specific action         | Action Specific |

Printing out Results

Reusing Action Results

```
action-results.yaml
apiVersion: mission-control.flanksource.com/v1

kind: Playbook

metadata:

  name: use-previous-action-result

spec:

  description: Creates a file with the content of the config

  configs:

    - types:

        - Kubernetes::Pod

  actions:

    - name: Fetch all changes

      sql:

        query: SELECT id FROM config_changes WHERE config_id = '{{.config.id}}'

        driver: postgres

        connection: connection://postgres/local

    - name: Send notification

      if: 'last_result().count > 0'

      notification:

        title: 'Changes summary for {{.config.name}}'

        connection: connection://slack/flanksource

        message: |

          {{$rows:=index last_result "count"}}

          Found {{$rows}} changes
```
