Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(controller): add functions to EL for finding artifacts in promo's freight collection #2925

Merged
merged 11 commits into from
Nov 15, 2024
292 changes: 124 additions & 168 deletions docs/docs/35-references/10-promotion-steps.md

Large diffs are not rendered by default.

99 changes: 99 additions & 0 deletions docs/docs/35-references/20-expression-language.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,102 @@ At present, such re-use can be achieved only through manual copy/paste, but
support for a new, top-level `PromotionTemplate` resource type is planned for an
upcoming release.
:::

## Functions

Several functions are built-in to Kargo's expression language. This section
describes each of them.

### `quote()`

The `quote()` function takes a single argument of any type and returns a string
representation. This is useful for scenarios where an expression evaluates to a
non-`string` JSON type, but you wish to treat it as a `string` regardless.

Example:

```yaml
config:
numField: ${{ 40 + 2 }} # Will be treated as a number
strField: ${{ quote(40 + 2) }} # Will be treated as a string
```

### `warehouse()`

The `warehouse()` function takes a single argument of type `string`, which is the
name of a `Warehouse` resource in the same `Project` as the `Promotion` being
executed. It returns a `FreightOrigin` object representing that `Warehouse`.

The `FreightOrigin` object can be used as an optional argument to the
`commitFrom()`, `imageFrom()`, or `chartFrom()` functions to disambiguate the
desired source of an artifact when necessary.

See the next sections for examples.

### `commitFrom()`

The `commitFrom()` function takes the URL of a Git repository as its first
argument and returns a corresponding `GitCommit` object from the `Promotion`'s
`FreightCollection`.

In the event that a `Stage` requests `Freight` from multiple origins
(`Warehouse`s) and more than one of those can provide a `GitCommit` object from
the specified repository, a `FreightOrigin` may be used as a second argument to
disambiguate the desired source.

Example:

```yaml
config:
commitID: ${{ commitFrom("https://github.com/example/repo.git", warehouse("my-warehouse")).ID }}
```

### `imageFrom()`

The `imageFrom()` function takes the URL of a container image repository as its
first argument and returns a corresponding `Image` object from the `Promotion`'s
`FreightCollection`.

In the event that a `Stage` requests `Freight` from multiple origins
(`Warehouse`s) and more than one of those can provide an `Image` object from the
specified repository, a `FreightOrigin` may be used as a second argument to
disambiguate the desired source.

Example:

```yaml
config:
imageTag: ${{ imageFrom("public.ecr.aws/nginx/nginx", warehouse("my-warehouse")).Tag }}
```

### `chartFrom()`

The `chartFrom()` function takes the URL of a Helm chart repository as its first
argument and returns a corresponding `Chart` object from the `Promotion`'s
`FreightCollection`.

For Helm charts stored in OCI registries, the URL should be the full path to the
repository within that registry.

For Helm charts stored in classic (http/s) repositories, which can store
multiple different charts within a single repository, a second argument should
be used to specify the name of the chart within the repository.

In the event that a `Stage` requests `Freight` from multiple origins
(`Warehouse`s) and more than one of those can provide a `Chart` object from the
specified repository, a `FreightOrigin` may be used as a final argument to
disambiguate the desired source.

OCI registry example:

```yaml
config:
chartVersion: ${{ chartFrom("oci://example.com/my-chart", warehouse("my-warehouse")).Version }}
```

Classic repository example:

```yaml
config:
chartVersion: ${{ chartFrom("https://example.com/charts", "my-chart", warehouse("my-warehouse")).Version }}
```
2 changes: 1 addition & 1 deletion internal/directives/argocd_revisions.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (a *argocdUpdater) getDesiredRevisions(
// specific about a previous step whose output should be used as the desired
// revision.
if sourceUpdate != nil {
revisions[i] = sourceUpdate.DesiredCommit
revisions[i] = sourceUpdate.DesiredRevision
if revisions[i] == "" {
var err error
if revisions[i], err = getCommitFromStep(
Expand Down
97 changes: 55 additions & 42 deletions internal/directives/argocd_updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -815,28 +815,41 @@
kustomizeImages := make(argocd.KustomizeImages, 0, len(update.Images))
for i := range update.Images {
imageUpdate := &update.Images[i]
desiredOrigin := getDesiredOrigin(stepCfg, imageUpdate)
image, err := freight.FindImage(
ctx,
stepCtx.KargoClient,
stepCtx.Project,
stepCtx.FreightRequests,
desiredOrigin,
stepCtx.Freight.References(),
imageUpdate.RepoURL,
)
if err != nil {
return nil,
fmt.Errorf("error finding image from repo %q: %w", imageUpdate.RepoURL, err)
var digest, tag string
switch {
case imageUpdate.Digest != "":
digest = imageUpdate.Digest
case imageUpdate.Tag != "":
tag = imageUpdate.Tag
default:
desiredOrigin := getDesiredOrigin(stepCfg, imageUpdate)
image, err := freight.FindImage(
ctx,
stepCtx.KargoClient,
stepCtx.Project,
stepCtx.FreightRequests,
desiredOrigin,
stepCtx.Freight.References(),
imageUpdate.RepoURL,
)
if err != nil {
return nil,
fmt.Errorf("error finding image from repo %q: %w", imageUpdate.RepoURL, err)
}

Check warning on line 838 in internal/directives/argocd_updater.go

View check run for this annotation

Codecov / codecov/patch

internal/directives/argocd_updater.go#L836-L838

Added lines #L836 - L838 were not covered by tests
if imageUpdate.UseDigest {
digest = image.Digest
} else {
tag = image.Tag
}
}
kustomizeImageStr := imageUpdate.RepoURL
if imageUpdate.NewName != "" {
kustomizeImageStr = fmt.Sprintf("%s=%s", kustomizeImageStr, imageUpdate.NewName)
}
if imageUpdate.UseDigest {
kustomizeImageStr = fmt.Sprintf("%s@%s", kustomizeImageStr, image.Digest)
if digest != "" {
kustomizeImageStr = fmt.Sprintf("%s@%s", kustomizeImageStr, digest)
} else {
kustomizeImageStr = fmt.Sprintf("%s:%s", kustomizeImageStr, image.Tag)
kustomizeImageStr = fmt.Sprintf("%s:%s", kustomizeImageStr, tag)
}
kustomizeImages = append(
kustomizeImages,
Expand All @@ -857,33 +870,33 @@
imageUpdate := &update.Images[i]
switch imageUpdate.Value {
case ImageAndTag, Tag, ImageAndDigest, Digest:
// TODO(krancour): Remove this for v1.2.0
desiredOrigin := getDesiredOrigin(stepCfg, imageUpdate)
image, err := freight.FindImage(
ctx,
stepCtx.KargoClient,
stepCtx.Project,
stepCtx.FreightRequests,
desiredOrigin,
stepCtx.Freight.References(),
imageUpdate.RepoURL,
)
if err != nil {
return nil,
fmt.Errorf("error finding image from repo %q: %w", imageUpdate.RepoURL, err)
}

Check warning on line 887 in internal/directives/argocd_updater.go

View check run for this annotation

Codecov / codecov/patch

internal/directives/argocd_updater.go#L885-L887

Added lines #L885 - L887 were not covered by tests
switch imageUpdate.Value {
case ImageAndTag:
changes[imageUpdate.Key] = fmt.Sprintf("%s:%s", imageUpdate.RepoURL, image.Tag)
case Tag:
changes[imageUpdate.Key] = image.Tag
case ImageAndDigest:
changes[imageUpdate.Key] = fmt.Sprintf("%s@%s", imageUpdate.RepoURL, image.Digest)
case Digest:
changes[imageUpdate.Key] = image.Digest
}
default:
// This really shouldn't happen, so we'll ignore it.
continue
}
desiredOrigin := getDesiredOrigin(stepCfg, imageUpdate)
image, err := freight.FindImage(
ctx,
stepCtx.KargoClient,
stepCtx.Project,
stepCtx.FreightRequests,
desiredOrigin,
stepCtx.Freight.References(),
imageUpdate.RepoURL,
)
if err != nil {
return nil,
fmt.Errorf("error finding image from repo %q: %w", imageUpdate.RepoURL, err)
}
switch imageUpdate.Value {
case ImageAndTag:
changes[imageUpdate.Key] = fmt.Sprintf("%s:%s", imageUpdate.RepoURL, image.Tag)
case Tag:
changes[imageUpdate.Key] = image.Tag
case ImageAndDigest:
changes[imageUpdate.Key] = fmt.Sprintf("%s@%s", imageUpdate.RepoURL, image.Digest)
case Digest:
changes[imageUpdate.Key] = image.Digest
changes[imageUpdate.Key] = imageUpdate.Value
}
}
return changes, nil
Expand Down
Loading