Skip to content

Commit

Permalink
add image tag replacement functionality (#29)
Browse files Browse the repository at this point in the history
* add image tag replacement functionality

* update readme

* bump golang to 1.19.2 in Dockerfile

* update golang to 1.19.2

* update golang.org/x/net
  • Loading branch information
simongottschlag authored Oct 13, 2022
1 parent b67817c commit 1eb9510
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 12 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/pr-validation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- name: Setup go
uses: actions/setup-go@v3
with:
go-version: "^1.19"
go-version: "^1.19.2"
- name: Install libgit2
run: |
cd /tmp
Expand All @@ -38,7 +38,7 @@ jobs:
- name: Setup go
uses: actions/setup-go@v3
with:
go-version: "^1.19"
go-version: "^1.19.2"
- name: Run fmt
run: |
make fmt
Expand All @@ -57,7 +57,7 @@ jobs:
- name: Setup go
uses: actions/setup-go@v3
with:
go-version: "^1.19"
go-version: "^1.19.2"
- name: Install libgit2
run: |
cd /tmp
Expand All @@ -80,7 +80,7 @@ jobs:
- name: Setup go
uses: actions/setup-go@v3
with:
go-version: "^1.19"
go-version: "^1.19.2"
- name: Install libgit2
run: |
cd /tmp
Expand All @@ -103,7 +103,7 @@ jobs:
- name: Setup go
uses: actions/setup-go@v3
with:
go-version: "^1.19"
go-version: "^1.19.2"
- name: Install libgit2
run: |
cd /tmp
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-trigger-client.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: "^1.19"
go-version: "^1.19.2"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3.0.0
env:
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.19.0-alpine3.15 as builder
FROM golang:1.19.2-alpine3.15 as builder
RUN apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/v3.15/main/ gcc=~10.3 pkgconfig=~1.7 musl-dev=~1.2 libgit2-dev=~1.3 binutils-gold=~2.37
WORKDIR /workspace
COPY go.mod go.mod
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ spec:
remoteSecrets:
- appSecretName: connection-string
remoteSecretName: mssql-connection-string
replacements:
images:
- imageName: "mcr.microsoft.com/azuredocs/containerapps-helloworld"
newImageTag: "v0.1"
app:
properties:
configuration:
Expand Down Expand Up @@ -67,6 +71,7 @@ YAML-files can contain one or more documents (with `---` as a document separator
- Send notifications to the git commits
- Filter locations, making it possible to specify in the manifest what regions can run the app
- Push custom metrics to Azure monitor
- Functionality to replace the image tag using `spec.replacements.images`

## Frequently Asked Questions

Expand Down Expand Up @@ -142,6 +147,10 @@ If you open the `azcagit` container app (in the platform resource group) and go

![custom-metrics](docs/custom-metrics.png "Example custom metrics in Azure")

> How does the image tag replacement work?

If an image replacement is configured, it will match for the image name and if found it will apply the newImageTag.

## Things TODO in the future

- [x] Append secrets to Container Apps from KeyVault
Expand Down
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ require (
github.com/hashicorp/go-multierror v1.1.1
github.com/libgit2/git2go/v33 v33.0.9
github.com/microsoft/azure-devops-go-api/azuredevops/v6 v6.0.1
github.com/onsi/gomega v1.20.0
github.com/stretchr/testify v1.8.0
github.com/whilp/git-urls v1.0.0
go.uber.org/zap v1.23.0
Expand Down Expand Up @@ -94,7 +93,7 @@ require (
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503 // indirect
golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c // indirect
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458 // indirect
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 // indirect
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
golang.org/x/text v0.3.7 // indirect
Expand Down
5 changes: 2 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,6 @@ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGV
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q=
github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
Expand Down Expand Up @@ -755,8 +754,8 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c h1:JVAXQ10yGGVbSyoer5VILysz6YKjdNT2bsvlayjqhes=
golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458 h1:MgJ6t2zo8v0tbmLCueaCbF1RM+TtB0rs3Lv8DGtOIpY=
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand Down
35 changes: 35 additions & 0 deletions src/source/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,19 @@ func (r *RemoteSecretSpecification) Valid() bool {
}

type LocationFilterSpecification string
type ImageReplacementSpecification struct {
ImageName *string `json:"imageName,omitempty" yaml:"imageName,omitempty"`
NewImageTag *string `json:"newImageTag,omitempty" yaml:"newImageTag,omitempty"`
}
type ReplacementsSpecification struct {
Images []ImageReplacementSpecification `json:"images,omitempty" yaml:"image,omitempty"`
}

type SourceAppSpecification struct {
App *armappcontainers.ContainerApp `json:"app,omitempty" yaml:"app,omitempty"`
RemoteSecrets []RemoteSecretSpecification `json:"remoteSecrets,omitempty" yaml:"remoteSecrets,omitempty"`
LocationFilter []LocationFilterSpecification `json:"locationFilter,omitempty" yaml:"locationFilter,omitempty"`
Replacements *ReplacementsSpecification `json:"replacements,omitempty" yaml:"replacements,omitempty"`
}

type SourceApp struct {
Expand Down Expand Up @@ -253,10 +261,37 @@ func (app *SourceApp) Unmarshal(y []byte, cfg config.Config) error {

newapp.Specification.App.Tags["aca.xenit.io"] = toPtr("true")

err = newapp.applyReplacements()
if err != nil {
return err
}

*app = newapp
return nil
}

func (app *SourceApp) applyReplacements() error {
if app.Specification.Replacements != nil && app.Specification.Replacements.Images != nil && len(app.Specification.Replacements.Images) != 0 {
if app.Specification.App.Properties.Template == nil || app.Specification.App.Properties.Template.Containers == nil || len(app.Specification.App.Properties.Template.Containers) == 0 {
return fmt.Errorf("no containers found")
}
for i, container := range app.Specification.App.Properties.Template.Containers {
if container.Image == nil || *container.Image == "" {
return fmt.Errorf("no image found for container %d", i)
}
oldImage := *container.Image
imageParts := strings.Split(oldImage, ":")
imageName := imageParts[0]
for _, replacementImage := range app.Specification.Replacements.Images {
if imageName == *replacementImage.ImageName {
*app.Specification.App.Properties.Template.Containers[i].Image = fmt.Sprintf("%s:%s", imageName, *replacementImage.NewImageTag)
}
}
}
}
return nil
}

func (app *SourceApp) ShoudRunInLocation(currentLocation string) bool {
if app == nil || app.Specification == nil || len(app.Specification.LocationFilter) == 0 {
return true
Expand Down
75 changes: 75 additions & 0 deletions src/source/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,81 @@ spec:
},
expectedError: "",
},
{
testDescription: "validate that image replacement works",
rawYaml: `
kind: AzureContainerApp
apiVersion: aca.xenit.io/v1alpha1
metadata:
name: foo
spec:
replacements:
images:
- imageName: "mcr.microsoft.com/azuredocs/containerapps-helloworld"
newImageTag: "v0.1"
app:
properties:
configuration:
activeRevisionsMode: Single
template:
containers:
- name: simple-hello-world-container
image: mcr.microsoft.com/azuredocs/containerapps-helloworld:latest
resources:
cpu: 0.25
memory: .5Gi
scale:
minReplicas: 1
maxReplicas: 1
`,
expectedResult: SourceApp{
Kind: "AzureContainerApp",
APIVersion: "aca.xenit.io/v1alpha1",
Metadata: map[string]string{
"name": "foo",
},
Specification: &SourceAppSpecification{
Replacements: &ReplacementsSpecification{
Images: []ImageReplacementSpecification{
{
ImageName: toPtr("mcr.microsoft.com/azuredocs/containerapps-helloworld"),
NewImageTag: toPtr("v0.1"),
},
},
},
App: &armappcontainers.ContainerApp{
Location: toPtr("ze-location"),
Tags: map[string]*string{
"aca.xenit.io": toPtr("true"),
},
Identity: nil,
Properties: &armappcontainers.ContainerAppProperties{
ManagedEnvironmentID: toPtr("ze-managedEnvironmentID"),
Configuration: &armappcontainers.Configuration{
ActiveRevisionsMode: toPtr(armappcontainers.ActiveRevisionsModeSingle),
},
Template: &armappcontainers.Template{
Containers: []*armappcontainers.Container{
{
Name: toPtr("simple-hello-world-container"),
Image: toPtr("mcr.microsoft.com/azuredocs/containerapps-helloworld:v0.1"),
Resources: &armappcontainers.ContainerResources{
CPU: toPtr(float64(0.25)),
Memory: toPtr(".5Gi"),
},
},
},
Scale: &armappcontainers.Scale{
MaxReplicas: toPtr(int32(1)),
MinReplicas: toPtr(int32(1)),
},
},
},
},
},
},
expectedError: "",
},
}

for i, c := range cases {
Expand Down

0 comments on commit 1eb9510

Please sign in to comment.