commands/cluster/agent/bootstrap/flux.go (154 lines of code) (raw):

package bootstrap import ( "bytes" "errors" "fmt" "os" "path" "time" "github.com/avast/retry-go/v4" "gopkg.in/yaml.v3" ) var _ FluxWrapper = (*localFluxWrapper)(nil) func NewLocalFluxWrapper( cmd Cmd, binary string, manifestPath string, helmRepositoryName string, helmRepositoryNamespace string, helmRepositoryFilepath string, helmReleaseName string, helmReleaseNamespace string, helmReleaseFilepath string, helmReleaseTargetNamespace string, helmReleaseValues []string, helmReleaseValuesFrom []string, fluxSourceType string, fluxSourceNamespace string, fluxSourceName string, ) FluxWrapper { return &localFluxWrapper{ cmd: cmd, binary: binary, manifestPath: manifestPath, helmRepositoryName: helmRepositoryName, helmRepositoryNamespace: helmRepositoryNamespace, helmRepositoryFilepath: helmRepositoryFilepath, helmReleaseName: helmReleaseName, helmReleaseNamespace: helmReleaseNamespace, helmReleaseFilepath: helmReleaseFilepath, helmReleaseTargetNamespace: helmReleaseTargetNamespace, helmReleaseValues: helmReleaseValues, helmReleaseValuesFrom: helmReleaseValuesFrom, fluxSourceType: fluxSourceType, fluxSourceNamespace: fluxSourceNamespace, fluxSourceName: fluxSourceName, reconcileRetryDelay: 10 * time.Second, } } type localFluxWrapper struct { cmd Cmd binary string manifestPath string helmRepositoryName string helmRepositoryNamespace string helmRepositoryFilepath string helmReleaseName string helmReleaseNamespace string helmReleaseFilepath string helmReleaseTargetNamespace string helmReleaseValues []string helmReleaseValuesFrom []string fluxSourceType string fluxSourceNamespace string fluxSourceName string reconcileRetryDelay time.Duration } func (f *localFluxWrapper) createHelmRepositoryManifest() (file, error) { helmRepositoryYAML, err := f.cmd.RunWithOutput( f.binary, "create", "source", "helm", f.helmRepositoryName, "--export", fmt.Sprintf("-n=%s", f.helmRepositoryNamespace), "--url=https://charts.gitlab.io", ) if err != nil { return file{}, err } return file{path: path.Join(f.manifestPath, f.helmRepositoryFilepath), content: helmRepositoryYAML}, nil } type agentHelmChartValues struct { Config *agentHelmChartValuesConfig `yaml:"config"` } type agentHelmChartValuesConfig struct { KASAddress string `yaml:"kasAddress"` SecretName string `yaml:"secretName"` } func (f *localFluxWrapper) createHelmReleaseManifest(kasAddress string) (file, error) { // create temporary file for Flux CLI to read values from. // The Flux CLI does not yet support reading values from literal flags. valuesFile, err := os.CreateTemp("", "glab-bootstrap-helmrelease-values") if err != nil { return file{}, err } defer os.Remove(valuesFile.Name()) defer valuesFile.Close() cfg := &agentHelmChartValues{ Config: &agentHelmChartValuesConfig{ KASAddress: kasAddress, SecretName: "gitlab-agent-token", }, } enc := yaml.NewEncoder(valuesFile) if err = enc.Encode(cfg); err != nil { return file{}, err } args := []string{ "create", "helmrelease", f.helmReleaseName, "--export", fmt.Sprintf("-n=%s", f.helmReleaseNamespace), fmt.Sprintf("--target-namespace=%s", f.helmReleaseTargetNamespace), "--create-target-namespace=true", fmt.Sprintf("--source=HelmRepository/%s.%s", f.helmRepositoryName, f.helmRepositoryNamespace), "--chart=gitlab-agent", fmt.Sprintf("--release-name=%s", f.helmReleaseName), fmt.Sprintf("--values=%s", valuesFile.Name()), } for _, v := range f.helmReleaseValues { args = append(args, fmt.Sprintf("--values=%s", v)) } for _, v := range f.helmReleaseValuesFrom { args = append(args, fmt.Sprintf("--values-from=%s", v)) } helmReleaseYAML, err := f.cmd.RunWithOutput( f.binary, args..., ) if err != nil { return file{}, err } return file{path: path.Join(f.manifestPath, f.helmReleaseFilepath), content: helmReleaseYAML}, nil } func (f *localFluxWrapper) reconcile() error { // reconcile flux source to pull new HelmRepository source err := f.cmd.Run(f.binary, "reconcile", "source", f.fluxSourceType, f.fluxSourceName, fmt.Sprintf("-n=%s", f.fluxSourceNamespace)) if err != nil { return err } // just reconciling doesn't mean that the HelmRelease now exists ... (bug in flux? At least very unfortunate behavior) err = retry.Do(func() error { output, err := f.cmd.RunWithOutput(f.binary, "get", "helmreleases", f.helmReleaseName, fmt.Sprintf("-n=%s", f.helmReleaseNamespace)) if err != nil { // flux always returns with exit code 0, even when the helmrelease does not exist (yet) return retry.Unrecoverable(err) } if bytes.Contains(output, fmt.Appendf(nil, `HelmRelease object '%s' not found in "%s" namespace`, f.helmReleaseName, f.helmReleaseNamespace)) { return errors.New(string(output)) } return nil }, retry.Attempts(6), retry.Delay(f.reconcileRetryDelay)) if err != nil { return err } return f.cmd.Run(f.binary, "reconcile", "helmrelease", f.helmReleaseName, fmt.Sprintf("-n=%s", f.helmReleaseNamespace), "--with-source") }