vault/tools/pipeline/internal/pkg/generate/enos_dynamic_config.go
2024-11-28 15:27:17 +00: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.4"},
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
}