Skip to content

Commit

Permalink
refactor: replace helm-update-image with yaml-update (akuity#2941)
Browse files Browse the repository at this point in the history
Signed-off-by: Kent Rancourt <kent.rancourt@gmail.com>
  • Loading branch information
krancour authored Nov 16, 2024
1 parent b75c455 commit c09fec4
Show file tree
Hide file tree
Showing 10 changed files with 702 additions and 149 deletions.
60 changes: 56 additions & 4 deletions docs/docs/35-references/10-promotion-steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,16 +394,18 @@ file with new version information which is referenced by the Freight being
promoted. This step is commonly followed by a [`helm-template`](#helm-template)
step.

__Deprecated: Use the generic `yaml-update` step instead. Will be removed in v1.2.0.__

### `helm-update-image` Configuration

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `path` | `string` | Y | Path to Helm values file (e.g. `values.yaml`). This path is relative to the temporary workspace that Kargo provisions for use by the promotion process. |
| `images` | `[]object` | Y | The details of changes to be applied to the values file. At least one must be specified. |
| `images[].image` | `string` | N | Name/URL of the image being updated. <br/><br/>__Deprecated: Use `value` with an expression instead. Will be removed in v1.2.0.__ |
| `images[].fromOrigin` | `object` | N | See [specifying origins](#specifying-origins). <br/><br/>__Deprecated: Use `value` with an expression instead. Will be removed in v1.2.0.__ |
| `images[].image` | `string` | Y | Name/URL of the image being updated. The Freight being promoted presumably contains a reference to a revision of this image. |
| `images[].fromOrigin` | `object` | N | See [specifying origins](#specifying-origins) |
| `images[].key` | `string` | Y | The key to update within the values file. See Helm documentation on the [format and limitations](https://helm.sh/docs/intro/using_helm/#the-format-and-limitations-of---set) of the notation used in this field. |
| `images[].value` | `string` | Y | Specifies how the value of `key` is to be updated. When `image` is non-empty, possible values for this field are limited to:<ul><li>`ImageAndTag`: Replaces the value of `key` with a string in form `<image url>:<tag>`</li><li>`Tag`: Replaces the value of `key` with the image's tag</li><li>`ImageAndDigest`: Replaces the value of `key` with a string in form `<image url>@<digest>`</li><li>`Digest`: Replaces the value of `key` with the image's digest</li></ul> When `image` is empty, use an expression in this field to describe the new value. |
| `images[].value` | `string` | Y | Specifies how the value of `key` is to be updated. Possible values for this field are limited to:<ul><li>`ImageAndTag`: Replaces the value of `key` with a string in form `<image url>:<tag>`</li><li>`Tag`: Replaces the value of `key` with the image's tag</li><li>`ImageAndDigest`: Replaces the value of `key` with a string in form `<image url>@<digest>`</li><li>`Digest`: Replaces the value of `key` with the image's digest</li></ul> |

### `helm-update-image` Example

Expand All @@ -428,12 +430,62 @@ steps:
config:
path: ./src/charts/my-chart/values.yaml
images:
- image: my/image
key: image.tag
value: Tag
# Render manifests to ./out, commit, push, etc...
```

### `helm-update-image` Output

| Name | Type | Description |
|------|------|-------------|
| `commitMessage` | `string` | A description of the change(s) applied by this step. Typically, a subsequent [`git-commit`](#git-commit) step will reference this output and aggregate this commit message fragment with other like it to build a comprehensive commit message that describes all changes. |

## `yaml-update`

`yaml-update` updates the values of specified keys in any YAML file. This step
most often used to update image tags or digests in a Helm values and is commonly
followed by a [`helm-template`](#helm-template) step.

### `yaml-update` Configuration

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `path` | `string` | Y | Path to a YAML file. This path is relative to the temporary workspace that Kargo provisions for use by the promotion process. |
| `updates` | `[]object` | Y | The details of changes to be applied to the file. At least one must be specified. |
| `updates[].key` | `string` | Y | The key to update within the file. For nested values, use a YAML dot notation path. |
| `updates[].value` | `string` | Y | The new value for the key. Typically specified using an expression. |

### `yaml-update` Example

```yaml
vars:
- name: gitRepo
value: https://github.com/example/repo.git
steps:
- uses: git-clone
config:
repoURL: ${{ vars.gitRepo }}
checkout:
- commit: ${{ commitFrom(vars.gitRepo).ID }}
path: ./src
- branch: stage/${{ ctx.stage }}
create: true
path: ./out
- uses: git-clear
config:
path: ./out
- uses: yaml-update
config:
path: ./src/charts/my-chart/values.yaml
updates:
- key: image.tag
value: ${{ imageFrom("my/image").tag }}
# Render manifests to ./out, commit, push, etc...
```

### `helm-update-image` Output
### `yaml-update` Output

| Name | Type | Description |
|------|------|-------------|
Expand Down
86 changes: 45 additions & 41 deletions internal/directives/helm_image_updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package directives
import (
"context"
"fmt"
"slices"
"strings"

securejoin "github.com/cyphar/filepath-securejoin"
Expand Down Expand Up @@ -72,7 +71,7 @@ func (h *helmImageUpdater) runPromotionStep(
stepCtx *PromotionStepContext,
cfg HelmUpdateImageConfig,
) (PromotionStepResult, error) {
updates, err := h.generateImageUpdates(ctx, stepCtx, cfg)
updates, fullImageRefs, err := h.generateImageUpdates(ctx, stepCtx, cfg)
if err != nil {
return PromotionStepResult{Status: kargoapi.PromotionPhaseErrored},
fmt.Errorf("failed to generate image updates: %w", err)
Expand All @@ -85,7 +84,7 @@ func (h *helmImageUpdater) runPromotionStep(
fmt.Errorf("values file update failed: %w", err)
}

if commitMsg := h.generateCommitMessage(cfg.Path, updates); commitMsg != "" {
if commitMsg := h.generateCommitMessage(cfg.Path, fullImageRefs); commitMsg != "" {
result.Output = map[string]any{
"commitMessage": commitMsg,
}
Expand All @@ -98,31 +97,35 @@ func (h *helmImageUpdater) generateImageUpdates(
ctx context.Context,
stepCtx *PromotionStepContext,
cfg HelmUpdateImageConfig,
) (map[string]string, error) {
) (map[string]string, []string, error) {
updates := make(map[string]string, len(cfg.Images))
fullImageRefs := make([]string, 0, len(cfg.Images))

for _, image := range cfg.Images {
switch image.Value {
case ImageAndTag, Tag, ImageAndDigest, Digest:
// TODO(krancour): Remove this for v1.2.0
desiredOrigin := h.getDesiredOrigin(image.FromOrigin)
targetImage, err := freight.FindImage(
ctx,
stepCtx.KargoClient,
stepCtx.Project,
stepCtx.FreightRequests,
desiredOrigin,
stepCtx.Freight.References(),
image.Image,
)
if err != nil {
return nil, fmt.Errorf("failed to find image %s: %w", image.Image, err)
}
updates[image.Key] = h.getValue(targetImage, image.Value)
default:
updates[image.Key] = image.Value
desiredOrigin := h.getDesiredOrigin(image.FromOrigin)

targetImage, err := freight.FindImage(
ctx,
stepCtx.KargoClient,
stepCtx.Project,
stepCtx.FreightRequests,
desiredOrigin,
stepCtx.Freight.References(),
image.Image,
)
if err != nil {
return nil, nil, fmt.Errorf("failed to find image %s: %w", image.Image, err)
}

value, imageRef, err := h.getImageValues(targetImage, image.Value)
if err != nil {
return nil, nil, err
}

updates[image.Key] = value
fullImageRefs = append(fullImageRefs, imageRef)
}
return updates, nil
return updates, fullImageRefs, nil
}

func (h *helmImageUpdater) getDesiredOrigin(fromOrigin *ChartFromOrigin) *kargoapi.FreightOrigin {
Expand All @@ -135,19 +138,20 @@ func (h *helmImageUpdater) getDesiredOrigin(fromOrigin *ChartFromOrigin) *kargoa
}
}

// TODO(krancour): Remove this for v1.2.0
func (h *helmImageUpdater) getValue(image *kargoapi.Image, value string) string {
switch value {
func (h *helmImageUpdater) getImageValues(image *kargoapi.Image, valueType string) (string, string, error) {
switch valueType {
case ImageAndTag:
return fmt.Sprintf("%s:%s", image.RepoURL, image.Tag)
imageRef := fmt.Sprintf("%s:%s", image.RepoURL, image.Tag)
return imageRef, imageRef, nil
case Tag:
return image.Tag
return image.Tag, fmt.Sprintf("%s:%s", image.RepoURL, image.Tag), nil
case ImageAndDigest:
return fmt.Sprintf("%s@%s", image.RepoURL, image.Digest)
imageRef := fmt.Sprintf("%s@%s", image.RepoURL, image.Digest)
return imageRef, imageRef, nil
case Digest:
return image.Digest
return image.Digest, fmt.Sprintf("%s@%s", image.RepoURL, image.Digest), nil
default:
return value
return "", "", fmt.Errorf("unknown image value type %q", valueType)
}
}

Expand All @@ -162,20 +166,20 @@ func (h *helmImageUpdater) updateValuesFile(workDir string, path string, changes
return nil
}

func (h *helmImageUpdater) generateCommitMessage(path string, updates map[string]string) string {
if len(updates) == 0 {
func (h *helmImageUpdater) generateCommitMessage(path string, fullImageRefs []string) string {
if len(fullImageRefs) == 0 {
return ""
}

var commitMsg strings.Builder
_, _ = commitMsg.WriteString(fmt.Sprintf("Updated %s\n", path))
keys := make([]string, 0, len(updates))
for key := range updates {
keys = append(keys, key)
_, _ = commitMsg.WriteString(fmt.Sprintf("Updated %s to use new image", path))
if len(fullImageRefs) > 1 {
_, _ = commitMsg.WriteString("s")
}
slices.Sort(keys)
for _, key := range keys {
_, _ = commitMsg.WriteString(fmt.Sprintf("\n- %s: %q", key, updates[key]))
_, _ = commitMsg.WriteString("\n")

for _, s := range fullImageRefs {
_, _ = commitMsg.WriteString(fmt.Sprintf("\n- %s", s))
}

return commitMsg.String()
Expand Down
Loading

0 comments on commit c09fec4

Please sign in to comment.