internal/helm/upgrade.go
Rama main · réplica de lectura del repositorio original.
package helm
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"strings"
"sigs.k8s.io/yaml"
"github.com/sophotechlabs/spinoza/internal/api"
"github.com/sophotechlabs/spinoza/internal/charts"
)
const ActionUpgrade = "upgrade"
var ErrFluxManaged = errors.New("this release is managed by flux")
type UpgradeRequest struct {
Namespace string
Name string
Chart string
Version string
RepoURL string
OCI bool
Values string
DryRun bool
}
func (s *Service) Upgrade(ctx context.Context, req UpgradeRequest) (api.HelmActionResult, error) {
err := s.admitsUpgrade(req)
if err != nil {
return api.HelmActionResult{}, err
}
driver, driverErr := s.driverFor(ctx, req.Namespace, req.Name)
if driverErr != nil {
return api.HelmActionResult{}, driverErr
}
valuesFile, valuesErr := writeValues(req.Values)
if valuesErr != nil {
return api.HelmActionResult{}, valuesErr
}
defer func() {
_ = os.Remove(valuesFile)
}()
args := s.args(ctx, upgradeArgs(req, valuesFile)...)
out, runErr := s.run(ctx, args, driver)
if runErr != nil {
return api.HelmActionResult{}, runErr
}
if req.DryRun {
return renderResult(ActionUpgrade, req.Chart, req.Version, true, out)
}
return api.HelmActionResult{
Action: ActionUpgrade,
Message: orDefault(out, fmt.Sprintf("upgraded %s to %s %s", req.Name, req.Chart, req.Version)),
}, nil
}
func (s *Service) admitsUpgrade(req UpgradeRequest) error {
err := s.admits(req.Namespace, req.Name)
if err != nil {
return err
}
if !nameFormat.MatchString(req.Chart) {
return fmt.Errorf("%q is not a chart name", req.Chart)
}
if !charts.ValidVersion(req.Version) {
return fmt.Errorf("version %q is not a semantic version", req.Version)
}
repoErr := s.admitsRepository(req.RepoURL, req.OCI)
if repoErr != nil {
return repoErr
}
return checkValues(req.Values)
}
func checkValues(values string) error {
if values == "" {
return nil
}
var doc map[string]any
err := yaml.Unmarshal([]byte(values), &doc)
if err != nil {
return fmt.Errorf("values must be a yaml mapping: %w", err)
}
return nil
}
func writeValues(values string) (string, error) {
create := func() (temporaryValues, error) {
return os.CreateTemp("", "spinoza-values-*.yaml")
}
return writeValuesWith(values, create, os.Remove)
}
type temporaryValues interface {
Name() string
WriteString(values string) (int, error)
Close() error
}
func writeValuesWith(
values string,
create func() (temporaryValues, error),
remove func(string) error,
) (string, error) {
file, err := create()
if err != nil {
return "", err
}
name := file.Name()
written, writeErr := file.WriteString(values)
closeErr := file.Close()
if writeErr != nil {
_ = remove(name)
return "", writeErr
}
if written != len(values) {
_ = remove(name)
return "", io.ErrShortWrite
}
if closeErr != nil {
_ = remove(name)
return "", closeErr
}
return name, nil
}
func upgradeArgs(req UpgradeRequest, valuesFile string) []string {
args := []string{
"upgrade",
req.Name,
chartRef(req.Chart, req.RepoURL, req.OCI),
"--namespace", req.Namespace,
"--version", req.Version,
}
if !req.OCI {
args = append(args, "--repo", req.RepoURL)
}
args = append(args, "--values", valuesFile)
if req.DryRun {
args = append(args, "--dry-run=server", "--output", "json")
}
return args
}
func chartRef(chart, repoURL string, oci bool) string {
if oci {
return strings.TrimSuffix(repoURL, "/") + "/" + chart
}
return chart
}
func renderResult(action, chart, version string, onCluster bool, out string) (api.HelmActionResult, error) {
var rendered payload
err := json.Unmarshal([]byte(afterThePull(out)), &rendered)
if err != nil {
return api.HelmActionResult{}, fmt.Errorf("could not read helm's dry-run output: %w", err)
}
return api.HelmActionResult{
Action: action,
DryRun: true,
Manifest: rendered.Manifest,
Message: fmt.Sprintf("%s render of %s %s", renderedBy(onCluster), chart, version),
}, nil
}
func afterThePull(out string) string {
start := strings.Index(out, "{")
if start < 0 {
return out
}
return out[start:]
}
func renderedBy(onCluster bool) string {
if onCluster {
return "server"
}
return "local"
}