vault/tools/pipeline/internal/pkg/generate/enos_dynamic_config.go
Ryan Cragun f61bd3230c
enos(artifactory): unify dev and test scenario artifactory metadata into new module (#29891)
* enos(artifactory): unify dev and test scenario artifactory metadata into new module

There was previously a lot of shared logic between
`build_artifactory_artifact` and `build_artifactory_package` as it
regards to building an artifact name. When it comes down to it, both
modules are very similar and their only major difference is searching
for any artifact (released or not) by either a combination of
`revision`, `edition`, `version`, and `type` vs. searching for a
released artifact with a combination of `version`, `edition`, and
`type`.

Rather than bolt on new `s390x` and `fips1403` artifact metadata to
both, I factored their metadata for package names and such into a
unified and shared `artifact/metadata` module that is now called by
both.

This was tricky as dev and test scenarios currently differ in what
we pass in as the `vault_version`, but we hope to remove that
difference soon. We also add metadata support for the forthcoming
FIPS 140-3.

This commit was tested extensively, along with other test scenarios
in support for `s390x but will be useful immediately for FIPS 140-3
so I've extracted it out.

Signed-off-by: Ryan Cragun <me@ryan.ec>

* Fix artifactory metadata before merge

The initial pass of the artifactory metadata was largely untested and
extracted from a different branch. After testing, this commit fixes a
few issues with the metadata module.

In order to test this I also had to fix an issue where AWS secrets
engine testing became a requirement but is impossible unless you exectue
against a blessed AWS account that has required roles. Instead, we now
make those verification opt-in via a new variable.

We also make some improvements to the pki-verify-certificates script so
that it works reliably against all our supported distros.

We also update our dynamic configuration to use the updated versions in
samples.

Signed-off-by: Ryan Cragun <me@ryan.ec>
2025-04-25 14:55:26 -06:00

209 lines
6.3 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package generate
import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"path/filepath"
"slices"
"github.com/Masterminds/semver"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/hashicorp/vault/tools/pipeline/internal/pkg/metadata"
"github.com/hashicorp/vault/tools/pipeline/internal/pkg/releases"
slogctx "github.com/veqryn/slog-context"
)
// EnosDynamicConfigReq is a request to generate dynamic enos configuration
type EnosDynamicConfigReq struct {
VaultEdition string
VaultVersion string
EnosDir string
FileName string
Skip []string
NMinus uint
releases.VersionLister
}
// EnosDynamicConfigRes is a response from a request to generate dynamic enos configuration
type EnosDynamicConfigRes struct {
Globals *Globals `json:"globals,omitempty" hcl:"globals,block" cty:"globals"`
}
// Globals are our dynamic globals
type Globals struct {
SampleAttributes *SampleAttrs `json:"sample_attributes,omitempty" hcl:"sample_attributes" cty:"sample_attributes"`
}
// SampleAttrs are the dynamic sample attributes that we'll write as globals
type SampleAttrs struct {
AWSRegion []string `json:"aws_region,omitempty" hcl:"aws_region" cty:"aws_region"`
DistroVersionAmzn []string `json:"distro_version_amzn,omitempty" hcl:"distro_version_amzn" cty:"distro_version_amzn"`
DistroVersionLeap []string `json:"distro_version_leap,omitempty" hcl:"distro_version_leap" cty:"distro_version_leap"`
DistroVersionRhel []string `json:"distro_version_rhel,omitempty" hcl:"distro_version_rhel" cty:"distro_version_rhel"`
DistroVersionSles []string `json:"distro_version_sles,omitempty" hcl:"distro_version_sles" cty:"distro_version_sles"`
DistroVersionUbuntu []string `json:"distro_version_ubuntu,omitempty" hcl:"distro_version_ubuntu" cty:"distro_version_ubuntu"`
UpgradeInitialVersion []string `json:"upgrade_initial_version,omitempty" hcl:"upgrade_initial_version" cty:"upgrade_initial_version"`
}
// Validate validates the request parameters
func (e *EnosDynamicConfigReq) Validate(ctx context.Context) error {
if e == nil {
return errors.New("enos dynamic config req: validate: uninitialized")
}
slog.Default().DebugContext(ctx, "validating enos dynamic config request")
if e.FileName == "" {
return errors.New("no destination file name set")
}
if e.VersionLister == nil {
return errors.New("no version lister set")
}
if !slices.Contains(metadata.Editions, e.VaultEdition) {
return fmt.Errorf("unknown edition: %s", e.VaultEdition)
}
_, err := semver.NewVersion(e.VaultVersion)
if err != nil {
return fmt.Errorf("invalid version: %s: %w", e.VaultVersion, err)
}
s, err := os.Stat(e.EnosDir)
if err != nil {
return fmt.Errorf("invalid enos dir: %s: %w", e.EnosDir, err)
}
if !s.IsDir() {
return fmt.Errorf("invalid enos dir: %s is not a directory", e.EnosDir)
}
return nil
}
// Run runs the dynamic configuration request
func (e *EnosDynamicConfigReq) Run(ctx context.Context) (*EnosDynamicConfigRes, error) {
if e == nil {
return nil, fmt.Errorf("enos-dynamic-config-req uninitialized")
}
ctx = slogctx.Append(ctx,
slog.String("vault-edition", e.VaultEdition),
slog.String("vault-version", e.VaultVersion),
slog.String("file-name", e.FileName),
slog.String("dir", e.EnosDir),
slog.Uint64("n-minnux", uint64(e.NMinus)),
"skip", e.Skip,
)
slog.Default().DebugContext(ctx, "running enos dynamic config request")
err := e.Validate(ctx)
if err != nil {
return nil, err
}
res := &EnosDynamicConfigRes{}
res.Globals, err = e.getGlobals(ctx)
if err != nil {
return nil, err
}
return res, e.writeFile(ctx, res)
}
func (e *EnosDynamicConfigReq) getGlobals(ctx context.Context) (*Globals, error) {
var err error
res := &Globals{}
res.SampleAttributes, err = e.getSampleAttrs(ctx)
return res, err
}
func (e *EnosDynamicConfigReq) getSampleAttrs(ctx context.Context) (*SampleAttrs, error) {
// Create our HCL body
attrs := &SampleAttrs{
// Use the cheapest regions
AWSRegion: []string{"us-east-1", "us-west-2"},
// Current distro defaults
DistroVersionAmzn: []string{"2023"},
DistroVersionLeap: []string{"15.6"},
DistroVersionRhel: []string{"8.10", "9.5"},
DistroVersionSles: []string{"15.6"},
DistroVersionUbuntu: []string{"20.04", "24.04"},
}
// Create our initial upgrade version list. We'll find all released versions between N-3 -> Current
// version, minus any explicitly skipped versions that have been set. Since CE and Ent do not share
// the same version lineage now we'll also have to figure that in as well.
versionReq := &releases.ListVersionsReq{
VersionLister: e.VersionLister,
LicenseClass: e.VaultEdition,
UpperBound: e.VaultVersion,
NMinus: e.NMinus,
Skip: e.Skip,
}
versionRes, err := versionReq.Run(ctx)
if err != nil {
return nil, err
}
attrs.UpgradeInitialVersion = versionRes.Versions
return attrs, nil
}
// writeFile creates the dynamic config file and writes the dynamic data into it
func (e *EnosDynamicConfigReq) writeFile(ctx context.Context, res *EnosDynamicConfigRes) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
slog.Default().DebugContext(ctx, "writing enos dynamic config request")
// Make sure our path is valid
path, err := filepath.Abs(filepath.Join(e.EnosDir, e.FileName))
if err != nil {
return fmt.Errorf("expanding path dynamic config request path: %w", err)
}
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return fmt.Errorf("opening dynamic config request destination file: %w", err)
}
defer f.Close()
if _, err = f.WriteString(`# 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.
`); err != nil {
return err
}
hf := hclwrite.NewEmptyFile()
gohcl.EncodeIntoBody(res, hf.Body())
bytes := hclwrite.Format(hf.Bytes())
slog.Default().InfoContext(ctx, "writing enos dynamic config request",
"path", path,
"hcl", string(bytes),
)
_, err = f.Write(bytes)
return err
}