diff --git a/cmd/identify_imagebuild.go b/cmd/identify_imagebuild.go index 23495f84..071e594a 100644 --- a/cmd/identify_imagebuild.go +++ b/cmd/identify_imagebuild.go @@ -35,6 +35,7 @@ type imageBuild struct { Images []imageBuilds `json:"images"` BuildArguments map[string]string `json:"buildArguments"` ContainerRegistries []generator.ContainerRegistry `json:"containerRegistries,omitempty"` + ForcePullImages []string `json:"forcePullImages"` } type imageBuilds struct { @@ -69,6 +70,7 @@ func ImageBuildConfigurationIdentification(g generator.GeneratorInput) (imageBui }) } } + lServices.ForcePullImages = lagoonBuild.BuildValues.ForcePullImages lServices.ContainerRegistries = lagoonBuild.BuildValues.ContainerRegistry return lServices, nil } diff --git a/cmd/identify_imagebuild_test.go b/cmd/identify_imagebuild_test.go index 6f3aec44..5e55a1ee 100644 --- a/cmd/identify_imagebuild_test.go +++ b/cmd/identify_imagebuild_test.go @@ -711,6 +711,47 @@ func TestImageBuildConfigurationIdentification(t *testing.T) { }, }, }, + { + name: "test11 Force Pull Base Images", + args: testdata.GetSeedData( + testdata.TestData{ + Namespace: "example-project-main", + ProjectName: "example-project", + EnvironmentName: "main", + Branch: "main", + LagoonYAML: "internal/testdata/basic/lagoon.forcebaseimagepull.yml", + }, true), + want: imageBuild{ + BuildKit: false, + BuildArguments: map[string]string{ + "LAGOON_BUILD_NAME": "lagoon-build-abcdefg", + "LAGOON_PROJECT": "example-project", + "LAGOON_ENVIRONMENT": "main", + "LAGOON_ENVIRONMENT_TYPE": "production", + "LAGOON_BUILD_TYPE": "branch", + "LAGOON_GIT_SOURCE_REPOSITORY": "ssh://git@example.com/lagoon-demo.git", + "LAGOON_KUBERNETES": "remote-cluster1", + "LAGOON_GIT_SHA": "abcdefg123456", + "LAGOON_GIT_BRANCH": "main", + "NODE_IMAGE": "example-project-main-node", + "LAGOON_SSH_PRIVATE_KEY": "-----BEGIN OPENSSH PRIVATE KEY-----\nthisisafakekey\n-----END OPENSSH PRIVATE KEY-----", + }, + ForcePullImages: []string{ + "registry.com/namespace/imagename:latest", + }, + Images: []imageBuilds{ + { + Name: "node", + ImageBuild: generator.ImageBuild{ + BuildImage: "harbor.example/example-project/main/node:latest", + Context: "internal/testdata/basic/docker", + DockerFile: "basic.dockerfile", + TemporaryImage: "example-project-main-node", + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/go.mod b/go.mod index cf905d0e..12fdf24e 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e // indirect + github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect diff --git a/go.sum b/go.sum index 1eb2a909..63ffabd3 100644 --- a/go.sum +++ b/go.sum @@ -149,6 +149,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e h1:n81KvOMrLZa+VWHwST7dun9f0G98X3zREHS1ztYzZKU= github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e/go.mod h1:xpWTC2KnJMiDLkoawhsPQcXjvwATEBcbq0xevG2YR9M= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= diff --git a/internal/generator/buildvalues.go b/internal/generator/buildvalues.go index ce6e805c..9ba9c12f 100644 --- a/internal/generator/buildvalues.go +++ b/internal/generator/buildvalues.go @@ -80,6 +80,7 @@ type BuildValues struct { ImageCacheBuildArguments []ImageCacheBuildArguments `json:"imageCacheBuildArgs"` IgnoreImageCache bool `json:"ignoreImageCache"` SSHPrivateKey string `json:"sshPrivateKey"` + ForcePullImages []string `json:"forcePullImages"` } type Resources struct { diff --git a/internal/generator/services.go b/internal/generator/services.go index c0675a05..59f37ebe 100644 --- a/internal/generator/services.go +++ b/internal/generator/services.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/distribution/reference" "os" "reflect" "regexp" @@ -244,6 +245,15 @@ func composeToServiceValues( } } + baseimage := lagoon.CheckServiceLagoonLabel(composeServiceValues.Labels, "lagoon.base.image") + if baseimage != "" { + // First, let's ensure that the structure of the base image is valid + if !reference.ReferenceRegexp.MatchString(baseimage) { + return ServiceValues{}, fmt.Errorf("the 'lagoon.base.image' label defined on service %s in the docker-compose file is invalid ('%s') - please ensure it conforms to the structure `[REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG|@DIGEST]`", composeService, baseimage) + } + buildValues.ForcePullImages = append(buildValues.ForcePullImages, baseimage) + } + // if there are overrides defined in the lagoon API `LAGOON_SERVICE_TYPES` // handle those here if buildValues.ServiceTypeOverrides != nil { diff --git a/internal/generator/services_test.go b/internal/generator/services_test.go index d3c5bbf9..048c557c 100644 --- a/internal/generator/services_test.go +++ b/internal/generator/services_test.go @@ -2,14 +2,13 @@ package generator import ( "encoding/json" - "reflect" - "testing" - "time" - composetypes "github.com/compose-spec/compose-go/types" "github.com/uselagoon/build-deploy-tool/internal/dbaasclient" "github.com/uselagoon/build-deploy-tool/internal/helpers" "github.com/uselagoon/build-deploy-tool/internal/lagoon" + "reflect" + "testing" + "time" ) func Test_composeToServiceValues(t *testing.T) { @@ -1163,6 +1162,41 @@ func Test_composeToServiceValues(t *testing.T) { }, }, }, + { + name: "test23 - failure on invalid baseimage label reference structure", + args: args{ + buildValues: &BuildValues{ + Namespace: "example-project-main", + Project: "example-project", + ImageRegistry: "harbor.example", + Environment: "main", + Branch: "main", + BuildType: "branch", + ServiceTypeOverrides: &lagoon.EnvironmentVariable{}, + LagoonYAML: lagoon.YAML{ + Environments: lagoon.Environments{ + "main": lagoon.Environment{ + Overrides: map[string]lagoon.Override{ + "redis": { + Image: "uselagoon/fake-redis:7", + }, + }, + }, + }, + }, + }, + composeService: "redis", + composeServiceValues: composetypes.ServiceConfig{ + Labels: composetypes.Labels{ + "lagoon.type": "redis", + "lagoon.base.image": "this-is-an-invalid-reference!", + }, + Image: "uselagoon/fake-redis:7", + }, + }, + want: ServiceValues{}, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/testdata/basic/docker-compose.forcebaseimagepull.yml b/internal/testdata/basic/docker-compose.forcebaseimagepull.yml new file mode 100644 index 00000000..a3937b94 --- /dev/null +++ b/internal/testdata/basic/docker-compose.forcebaseimagepull.yml @@ -0,0 +1,23 @@ +version: '2' +services: + node: + networks: + - amazeeio-network + - default + build: + context: internal/testdata/basic/docker + dockerfile: basic.dockerfile + labels: + lagoon.type: basic + lagoon.service.usecomposeports: true + lagoon.base.image: registry.com/namespace/imagename:latest + volumes: + - .:/app:delegated + ports: + - '1234' + - '8191' + - '9001/udp' + +networks: + amazeeio-network: + external: true \ No newline at end of file diff --git a/internal/testdata/basic/lagoon.forcebaseimagepull.yml b/internal/testdata/basic/lagoon.forcebaseimagepull.yml new file mode 100644 index 00000000..92091d65 --- /dev/null +++ b/internal/testdata/basic/lagoon.forcebaseimagepull.yml @@ -0,0 +1,10 @@ +docker-compose-yaml: internal/testdata/basic/docker-compose.forcebaseimagepull.yml + +environment_variables: + git_sha: "true" + +environments: + main: + routes: + - node: + - example.com diff --git a/legacy/build-deploy-docker-compose.sh b/legacy/build-deploy-docker-compose.sh index 5c808974..8b416f1a 100755 --- a/legacy/build-deploy-docker-compose.sh +++ b/legacy/build-deploy-docker-compose.sh @@ -615,6 +615,14 @@ if [[ "$BUILD_TYPE" == "pullrequest" || "$BUILD_TYPE" == "branch" ]]; then BUILD_ARGS+=(--build-arg ${BUILD_ARG_NAME}="${BUILD_ARG_VALUE}") done + # Here we iterate over any lagoon.base.image data that has been passed to us + # in order to explicitly pull the images to ensure they are current + for FPI in $(echo "$ENVIRONMENT_IMAGE_BUILD_DATA" | jq -rc '.forcePullImages[]?') + do + echo "Pulling Image: ${FPI}" + docker pull "${FPI}" + done + # now we loop through the images in the build data and determine if they need to be pulled or build for IMAGE_BUILD_DATA in $(echo "$ENVIRONMENT_IMAGE_BUILD_DATA" | jq -c '.images[]') do