Skip to content

Commit

Permalink
VAULT-31181: Add pipeline tool to Vault (#28536)
Browse files Browse the repository at this point in the history
As the Vault pipeline and release processes evolve over time, so too must the tooling that drives them. Historically we've utilized a combination of CI features and shell scripts that are wrapped into make targets to drive our CI. While this 
approach has worked, it requires careful consideration of what features to use (bash in CI almost never matches bash in developer machines, etc.) and often requires a deep understanding of several CLI tools (jq, etc). `make` itself also has limitations in user experience, e.g. passing flags.

As we're all in on Github Actions as our pipeline coordinator, continuing to utilize and build CLI tools to perform our pipeline tasks makes sense. This PR adds a new CLI tool called `pipeline` which we can use to build new isolated tasks that we can string together in Github Actions. We intend to use this utility as the interface for future release automation work, see VAULT-27514.

For the first task in this new `pipeline` tool, I've chosen to build two small sub-commands:

* `pipeline releases list-versions` - Allows us to list Vault versions between a range. The range is configurable either by setting `--upper` and/or `--lower` bounds, or by using the `--nminus` to set the N-X to go back from the current branches version. As CE and ENT do not have version parity we also consider the `--edition`, as well as none-to-many `--skip` flags to exclude specific versions.

* `pipeline generate enos-dynamic-config` - Which creates dynamic enos configuration based on the branch and the current list of release versions. It takes largely the same flags as the `release list-versions` command, however it also expects a `--dir` for the enos directory and a `--file` where the dynamic configuration will be written. This allows us to dynamically update and feed the latest versions into our sampling algorithm to get coverage over all supported prior versions.

We then integrate these new tools into the pipeline itself and cache the dynamic config on a weekly basis. We also cache the pipeline tool itself as it will likely become a repository for pipeline specific tooling. The caching strategy for the `pipeline` tool itself will make most workflows that require it super fast.


Signed-off-by: Ryan Cragun <[email protected]>
  • Loading branch information
ryancragun authored Oct 23, 2024
1 parent afd023e commit ce58852
Show file tree
Hide file tree
Showing 33 changed files with 2,024 additions and 108 deletions.
52 changes: 52 additions & 0 deletions .github/actions/create-dynamic-config/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

---
name: Create dynamic pipeline configuration
description: Create dynamic test configuration by restoring existing valid config or creating new config

inputs:
github-token:
description: An elevated Github token to access private HashiCorp modules.
vault-edition:
description: The vault edition to use when generating the dynamic config
vault-version:
description: The vault version to use when generating the dynamic config

runs:
using: composite
steps:
- name: dyn-cfg-metadata
id: dyn-cfg-metadata
shell: bash
run: |
# We're using a weekly cache key here so that we only regenerate the configuration on a
# weekly basis. If/when Github decides to purge our tiny config file cache we'll also
# recreate it as necessary.
#
# Uses GITHUB_ENV instead of GITHUB_OUTPUT because composite actions are broken,
# see: https://github.com/actions/cache/issues/803#issuecomment-1793565071
{
echo "DYNAMIC_CONFIG_KEY=$(date +%Y-%m-%U)"
echo "DYNAMIC_CONFIG_PATH=enos/enos-dynamic-config.hcl"
} | tee -a "$GITHUB_ENV"
- name: Try to restore dynamic config from cache
id: dyn-cfg-cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
path: ${{ env.DYNAMIC_CONFIG_PATH }}
key: dyn-cfg-${{ env.DYNAMIC_CONFIG_KEY }}
- if: steps.dyn-cfg-cache.outputs.cache-hit != 'true'
id: dyn-cfg-set-up-pipeline
# If we can't restore it from config then set up pipeline and generate it
name: Set up the pipeline tool
uses: ./.github/actions/set-up-pipeline
with:
github-token: ${{ inputs.github-token }}
- if: steps.dyn-cfg-cache.outputs.cache-hit != 'true'
id: dyn-cfg-generate
name: Create dynamic configuration
shell: bash
run: |
# Make sure that any branch specific dynamic config has been generated
pipeline generate enos-dynamic-config -d ./enos -f enos-dynamic-config.hcl -v ${{ inputs.vault-version }} -e ${{ inputs.vault-edition }} -n 3 --log debug
5 changes: 1 addition & 4 deletions .github/actions/set-up-go/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@ description: Set up Go with a shared module cache.
inputs:
github-token:
description: An elevated Github token to access private modules if necessary.
type: string
no-restore:
description: Whether or not to restore the Go module cache on a cache hit
type: boolean
default: false
default: "false"
go-version:
description: "Override .go-version"
type: string
default: ""

outputs:
Expand Down
47 changes: 47 additions & 0 deletions .github/actions/set-up-pipeline/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

---
name: Install the pipeline tool
description: Install the pipeline tool

inputs:
github-token:
description: An elevated Github token to access private HashiCorp modules.

runs:
using: composite
steps:
- uses: ./.github/actions/set-up-go
with:
github-token: ${{ inputs.github-token }}
no-restore: true # Don't download vault's modules for pipeline
- name: pipeline-metadata
id: pipeline-metadata
shell: bash
# Uses GITHUB_ENV instead of GITHUB_OUTPUT because composite actions are broken,
# see: https://github.com/actions/cache/issues/803#issuecomment-1793565071
run: |
gobin=$(go env GOBIN)
if [[ -z "$gobin" ]]; then
gobin="$(go env GOPATH)/bin"
fi
{
echo "PIPELINE_HASH=$(git ls-tree HEAD tools/pipeline --object-only)"
echo "PIPELINE_PATH=$gobin/pipeline"
} | tee -a "$GITHUB_ENV"
- name: Try to restore pipeline from cache
id: pipeline-cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
path: ${{ env.PIPELINE_PATH }}
key: pipeline-${{ env.PIPELINE_HASH }}
- if: steps.pipeline-cache.outputs.cache-hit != 'true'
id: pipeline-build
name: Build pipeline
shell: bash
env:
GOPRIVATE: github.com/hashicorp/*
run: |
git config --global url."https://${{ inputs.github-token }}@github.com".insteadOf https://github.com
make tools-pipeline
2 changes: 1 addition & 1 deletion .github/workflows/code-checker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ jobs:
needs: setup
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: ./.github/actions/install-external-tools # for buf and gofumpt
- uses: ./.github/actions/set-up-go
with:
github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
- uses: ./.github/actions/install-external-tools # for buf and gofumpt
- name: Go format
run: make prep check-go-fmt
- name: Protobuf format
Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/test-run-enos-scenario-matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ jobs:
- uses: hashicorp/action-setup-enos@v1
with:
github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
- uses: ./.github/actions/create-dynamic-config
with:
github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
vault-version: ${{ inputs.vault-version }}
vault-edition: ${{ inputs.vault-edition }}
- id: metadata
run: |
build_date=$(make ci-get-date)
Expand All @@ -69,6 +74,8 @@ jobs:
# shellcheck disable=2001
vault_version="$(sed 's/+ent/+${{ inputs.vault-edition }}/g' <<< '${{ inputs.vault-version }}')"
fi
sample_seed=$(date +%s)
sample=$(enos scenario sample observe "${{ inputs.sample-name }}" --chdir ./enos --min 1 --max "${{ inputs.sample-max }}" --seed "${sample_seed}" --format json | jq -c ".observation.elements")
{
echo "build-date=${build_date}"
echo "vault-version=${vault_version}"
Expand Down Expand Up @@ -99,6 +106,7 @@ jobs:
ENOS_VAR_vault_build_date: ${{ needs.metadata.outputs.build-date }}
ENOS_VAR_vault_product_version: ${{ needs.metadata.outputs.vault-version }}
ENOS_VAR_vault_revision: ${{ inputs.vault-revision }}
ENOS_VAR_vault_upgrade_initial_version: ${{ matrix.attributes.upgrade_initial_version }}
ENOS_VAR_consul_license_path: ./support/consul.hclic
ENOS_VAR_vault_license_path: ./support/vault.hclic
ENOS_VAR_distro_version_amzz: ${{ matrix.attributes.distro_version_amzn }}
Expand Down Expand Up @@ -127,6 +135,11 @@ jobs:
- uses: hashicorp/action-setup-enos@v1
with:
github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
- uses: ./.github/actions/create-dynamic-config
with:
github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
vault-version: ${{ inputs.vault-version }}
vault-edition: ${{ inputs.vault-edition }}
- name: Prepare scenario dependencies
id: prepare_scenario
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,4 @@ website/components/node_modules
tools/godoctests/.bin
tools/gonilnilfunctions/.bin
tools/codechecker/.bin
.ci-bootstrap
.ci-bootstrap
16 changes: 8 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,10 @@ check-tools-external:
check-tools-internal:
@$(CURDIR)/tools/tools.sh check-internal

.PHONY: check-tools-pipeline
check-tools-pipeline:
@$(CURDIR)/tools/tools.sh check-pipeline

check-vault-in-path:
@VAULT_BIN=$$(command -v vault) || { echo "vault command not found"; exit 1; }; \
[ -x "$$VAULT_BIN" ] || { echo "$$VAULT_BIN not executable"; exit 1; }; \
Expand All @@ -314,6 +318,10 @@ tools-external:
tools-internal:
@$(CURDIR)/tools/tools.sh install-internal

.PHONY: tools-pipeline
tools-pipeline:
@$(CURDIR)/tools/tools.sh install-pipeline

mysql-database-plugin:
@CGO_ENABLED=0 $(GO_CMD) build -o bin/mysql-database-plugin ./plugins/database/mysql/mysql-database-plugin

Expand Down Expand Up @@ -368,10 +376,6 @@ ci-get-revision:
ci-get-version-package:
@$(CURDIR)/scripts/ci-helper.sh version-package

.PHONY: ci-install-external-tools
ci-install-external-tools:
@$(CURDIR)/scripts/ci-helper.sh install-external-tools

.PHONY: ci-prepare-ent-legal
ci-prepare-ent-legal:
@$(CURDIR)/scripts/ci-helper.sh prepare-ent-legal
Expand All @@ -380,10 +384,6 @@ ci-prepare-ent-legal:
ci-prepare-ce-legal:
@$(CURDIR)/scripts/ci-helper.sh prepare-ce-legal

.PHONY: ci-update-external-tool-modules
ci-update-external-tool-modules:
@$(CURDIR)/scripts/ci-helper.sh update-external-tool-modules

.PHONY: ci-copywriteheaders
ci-copywriteheaders:
copywrite headers --plan
Expand Down
9 changes: 9 additions & 0 deletions enos/Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
VAULT_VERSION=$$(cat $(CURDIR)/../version/VERSION)

.PHONY: default
default: check-fmt shellcheck

Expand All @@ -10,10 +12,16 @@ fmt: fmt-enos fmt-modules shfmt
.PHONY: check-fmt-enos
check-fmt-enos:
enos fmt --check --diff .
enos fmt --check --diff ./k8s

.PHONY: fmt-enos
fmt-enos:
enos fmt .
enos fmt ./k8s

.PHONY: gen-enos
gen-enos:
pushd ../tools/pipeline &> /dev/null && go run ./... generate enos-dynamic-config -d ../../enos -f enos-dynamic-config.hcl -e ce -v $(VAULT_VERSION) -n 3 --log info && popd &> /dev/null

.PHONY: check-fmt-modules
check-fmt-modules:
Expand All @@ -25,6 +33,7 @@ fmt-modules:

.PHONY: validate-enos
validate-enos:
enos scenario validate --timeout 30m0s --chdir ./k8s
enos scenario validate --timeout 30m0s

.PHONY: lint
Expand Down
60 changes: 21 additions & 39 deletions enos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,27 @@ Variables that are required:
See [enos.vars.hcl](./enos.vars.hcl) or [enos-variables.hcl](./enos-variables.hcl)
for further descriptions of the variables.

Additional variable information can also be found in the [Scenario Outlines](#scenario_outlines)

## Scenario Outlines
Enos is capable of producing an outline of each scenario that is defined in a given directory. These
scenarios often include a description of what behavior the scenario performs, which variants are
available, and which variables are required. They also provide a step by step breakdown including
which quality requirments are verifiend by a given step.

You can generate outlines of all scenarios or specify one via it's name.

From the `enos` directory:
```bash
enos scenario outline smoke
```

There are also HTML versions available for an improved reading experience:
```bash
enos scenario outline --format html > index.html
open index.html
```

## Executing Scenarios
From the `enos` directory:

Expand Down Expand Up @@ -96,45 +117,6 @@ enos scenario destroy smoke artifact_source:local
Refer to the [Enos documentation](https://github.com/hashicorp/Enos-Docs)
for further information regarding installation, execution or composing scenarios.

# Scenarios
There are current two scenarios: `smoke` and `upgrade`. Both begin by building Vault
as specified by the selected `artifact_source` variant (see Variants section below for more
information).

## Smoke
The [`smoke` scenario](./enos-scenario-smoke.hcl) creates a Vault cluster using
the version from the current branch (either in CI or locally), with the backend
specified by the `backend` variant (`raft` or `consul`). Next, it unseals with the
appropriate method (`awskms` or `shamir`) and performs different verifications
depending on the backend and seal type.

## Upgrade
The [`upgrade` scenario](./enos-scenario-upgrade.hcl) creates a Vault cluster using
the version specified in `vault_upgrade_initial_release`, with the backend specified
by the `backend` variant (`raft` or `consul`). Next, it upgrades the Vault binary
that is determined by the `artifact_source` variant. After the upgrade, it verifies that
cluster is at the desired version, along with additional verifications.


## Autopilot
The [`autopilot` scenario](./enos-scenario-autopilot.hcl) creates a Vault cluster using
the version specified in `vault_upgrade_initial_release`. It writes test data to the Vault cluster. Next, it creates additional nodes with the candidate version of Vault as determined by the `vault_product_version` variable set.
The module uses AWS auto-join to handle discovery and unseals with auto-unseal
or Shamir depending on the `seal` variant. After the new nodes have joined and been
unsealed, it verifies reading stored data on the new nodes. Autopilot upgrade verification checks the upgrade status is "await-server-removal" and the target version is set to the version of upgraded nodes. This test also verifies the undo_logs status for Vault versions 1.13.x

## Replication
The [`replication` scenario](./enos-scenario-replication.hcl) creates two 3-node Vault clusters and runs following verification steps:

1. Writes data on the primary cluster
1. Enables performance replication
1. Verifies reading stored data from secondary cluster
1. Verifies initial replication status between both clusters
1. Replaces the leader node and one standby node on the primary Vault cluster
1. Verifies updated replication status between both clusters

This scenario verifies the performance replication status on both clusters to have their connection_status as "connected" and that the secondary cluster has known_primaries cluster addresses updated to the active nodes IP addresses of the primary Vault cluster. This scenario currently works around issues VAULT-12311 and VAULT-12309. The scenario fails when the primary storage backend is Consul due to issue VAULT-12332

## UI Tests
The [`ui` scenario](./enos-scenario-ui.hcl) creates a Vault cluster (deployed to AWS) using a version
built from the current checkout of the project. Once the cluster is available the UI acceptance tests
Expand Down
20 changes: 20 additions & 0 deletions enos/enos-dynamic-config.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

# Code generated by pipeline generate enos-dynamic-config DO NOT EDIT.

# This file is overwritten in CI as it contains branch specific and sometimes ever-changing values.
# It's checked in here so that enos samples and scenarios can be performed, just be aware that this
# might change out from under you.

globals {
sample_attributes = {
aws_region = ["us-east-1", "us-west-2"]
distro_version_amzn = ["2023"]
distro_version_leap = ["15.6"]
distro_version_rhel = ["8.10", "9.4"]
distro_version_sles = ["15.6"]
distro_version_ubuntu = ["20.04", "24.04"]
upgrade_initial_version = ["1.16.1", "1.16.2", "1.16.3", "1.17.0-rc1", "1.17.0", "1.17.1", "1.17.2", "1.17.3", "1.17.4", "1.17.5", "1.17.6", "1.18.0-rc1", "1.18.0"]
}
}
20 changes: 0 additions & 20 deletions enos/enos-globals.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -146,32 +146,12 @@ globals {
protocol = "udp"
},
}
sample_attributes = {
aws_region = ["us-east-1", "us-west-2"]
distro_version_amzn = ["2023"]
distro_version_leap = ["15.6"]
distro_version_rhel = ["8.10", "9.4"]
distro_version_sles = ["15.6"]
distro_version_ubuntu = ["20.04", "24.04"]
}
seals = ["awskms", "pkcs11", "shamir"]
tags = merge({
"Project Name" : var.project_name
"Project" : "Enos",
"Environment" : "ci"
}, var.tags)
// This reads the VERSION file, strips any pre-release metadata, and selects only initial
// versions that are less than our current version. E.g. A VERSION file containing 1.17.0-beta2
// would render: semverconstraint(v, "<1.17.0-0")
upgrade_version_stripped = join("-", [split("-", chomp(file("../version/VERSION")))[0], "0"])
// NOTE: when backporting, make sure that our initial versions are less than that
// release branch's version. Also beware if adding versions below 1.11.x. Some scenarios
// that use this global might not work as expected with earlier versions. Below 1.8.x is
// not supported in any way.
upgrade_all_initial_versions_ce = ["1.8.12", "1.9.10", "1.10.11", "1.11.12", "1.12.11", "1.13.13", "1.14.10", "1.15.6", "1.16.3", "1.17.0"]
upgrade_all_initial_versions_ent = ["1.8.12", "1.9.10", "1.10.11", "1.11.12", "1.12.11", "1.13.13", "1.14.13", "1.15.10", "1.16.4", "1.17.0"]
upgrade_initial_versions_ce = [for v in global.upgrade_all_initial_versions_ce : v if semverconstraint(v, "<${global.upgrade_version_stripped}")]
upgrade_initial_versions_ent = [for v in global.upgrade_all_initial_versions_ent : v if semverconstraint(v, "<${global.upgrade_version_stripped}")]
vault_install_dir = {
bundle = "/opt/vault/bin"
package = "/usr/bin"
Expand Down
Loading

0 comments on commit ce58852

Please sign in to comment.