Skip to content

Commit

Permalink
Use new docker build provider (#1278)
Browse files Browse the repository at this point in the history
This replaces the old Docker provider with the new Docker Build
provider.

User-facing AWSX types are unchanged.

Behavior is unchanged, except we will now automatically store and use an
inline cache if the user hasn't configured anything explicitly.

Fixes #1349
Fixes #1072

---------

Co-authored-by: Florian Stadler <[email protected]>
  • Loading branch information
blampe and flostadler authored Jan 6, 2025
1 parent 5e85be9 commit fe08e89
Show file tree
Hide file tree
Showing 23 changed files with 293 additions and 103 deletions.
57 changes: 36 additions & 21 deletions awsx/ecr/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// limitations under the License.

import * as aws from "@pulumi/aws";
import * as docker from "@pulumi/docker";
import * as docker from "@pulumi/docker-build";
import * as pulumi from "@pulumi/pulumi";
import * as schema from "../schema-types";
import * as utils from "../utils";
Expand Down Expand Up @@ -62,37 +62,52 @@ export function computeImageFromAsset(
throw new Error("Invalid credentials");
}
return {
registry: ecrCredentials.proxyEndpoint,
address: ecrCredentials.proxyEndpoint,
username: username,
password: password,
};
});

let cacheFrom: docker.types.input.CacheFromArgs[] = [];
if (dockerInputs.cacheFrom !== undefined) {
cacheFrom = dockerInputs.cacheFrom.map((c) => {
return {
registry: {
ref: c,
},
};
});
}
// Use an inline cache by default.
if (cacheFrom.length === 0) {
cacheFrom.push({ registry: { ref: canonicalImageName } });
}

let context = ".";
if (dockerInputs.context !== undefined) {
context = dockerInputs.context;
}

const dockerImageArgs: docker.ImageArgs = {
imageName: canonicalImageName,
build: {
args: dockerInputs.args,
builderVersion: dockerInputs.builderVersion,
cacheFrom: dockerInputs.cacheFrom
? {
images: dockerInputs.cacheFrom,
}
: undefined,
context: dockerInputs.context,
dockerfile: dockerInputs.dockerfile,
platform: dockerInputs.platform,
target: dockerInputs.target,
},
registry: registryCredentials,
tags: [canonicalImageName],
buildArgs: dockerInputs.args,
cacheFrom: cacheFrom,
cacheTo: [{ inline: {} }],
context: { location: context },
dockerfile: { location: dockerInputs.dockerfile },
platforms: dockerInputs.platform ? [dockerInputs.platform as docker.Platform] : [],
target: dockerInputs.target,
push: true,
registries: [registryCredentials],
};

const image = new docker.Image(imageName, dockerImageArgs, { parent });

image.repoDigest.apply((d: any) =>
pulumi.log.debug(` build complete: ${imageName} (${d})`, parent),
);
image.ref.apply((ref) => {
pulumi.log.debug(` build complete: ${ref}`, parent);
});

return image.repoDigest;
return image.ref;
}

function createUniqueImageName(inputs: pulumi.Unwrap<schema.DockerBuildInputs>): string {
Expand Down
2 changes: 1 addition & 1 deletion awsx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"//": "Pulumi sub-provider dependencies must be pinned at an exact version because we extract this value to generate the correct dependency in the schema",
"dependencies": {
"@pulumi/aws": "6.65.0",
"@pulumi/docker": "4.5.1",
"@pulumi/docker-build": "0.0.8",
"@pulumi/pulumi": "3.144.1",
"@types/aws-lambda": "^8.10.23",
"docker-classic": "npm:@pulumi/[email protected]",
Expand Down
1 change: 0 additions & 1 deletion awsx/schema-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export type Functions = {
"awsx:ec2:getDefaultVpc": (inputs: getDefaultVpcInputs) => Promise<getDefaultVpcOutputs>;
};
import * as aws from "@pulumi/aws";
import * as docker from "@pulumi/docker";
export abstract class Trail<TData = any> extends (pulumi.ComponentResource)<TData> {
public bucket?: aws.s3.Bucket | pulumi.Output<aws.s3.Bucket>;
public logGroup?: aws.cloudwatch.LogGroup | pulumi.Output<aws.cloudwatch.LogGroup>;
Expand Down
1 change: 0 additions & 1 deletion awsx/scripts/generate-provider-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ const externalRefs = (() => {
};
};
addRef("aws");
addRef("docker");
return externalRefs;
})();

Expand Down
44 changes: 34 additions & 10 deletions awsx/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1671,15 +1671,14 @@
mime "^2.0.0"
resolve "^1.7.1"

"@pulumi/docker@4.5.1":
version "4.5.1"
resolved "https://registry.yarnpkg.com/@pulumi/docker/-/docker-4.5.1.tgz#9058851bebbb358a1081c765d928fd6791c6c7ba"
integrity sha512-2BTFycFLwSpHGQ4IFTsUHl8H5w81AgkrMHSLUQ8Zu6HBDgGhB5up6YsxVqLeaUeWAedEUrrSCY3xTCNbP4a0ag==
"@pulumi/docker[email protected]":
version "0.0.8"
resolved "https://registry.yarnpkg.com/@pulumi/docker-build/-/docker-build-0.0.8.tgz#203bdd9eed062ed0d3ddd2ef275ffcc45a87b3f4"
integrity sha512-tS6UUgmDjQ+LVekMXGC/6ND7riY75h3oO9fLiVOrvNafCBumxK+Cjm6ZZ9tUChLYvw6H+rZRExZEhfC8F/SQgQ==
dependencies:
"@pulumi/pulumi" "^3.0.0"
semver "^5.4.0"
"@pulumi/pulumi" "^3.136.0"

"@pulumi/[email protected]", "@pulumi/pulumi@^3.0.0", "@pulumi/pulumi@^3.136.0", "@pulumi/pulumi@^3.142.0":
"@pulumi/[email protected]", "@pulumi/pulumi@^3.136.0", "@pulumi/pulumi@^3.142.0":
version "3.144.1"
resolved "https://registry.yarnpkg.com/@pulumi/pulumi/-/pulumi-3.144.1.tgz#96b3c54879f7bc5857ba38ac389e2b7262e44f9e"
integrity sha512-C3kdP/uYnZygxfK18pFzs0P6IYng/rgf/NW5LXUjHMKcCSwyHbsk6l2wpuuYQiWaJFWyqLdNwDFRVf07ZcIEsA==
Expand Down Expand Up @@ -5274,7 +5273,16 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"

"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"

string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
Expand Down Expand Up @@ -5306,7 +5314,14 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"

"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"

strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
Expand Down Expand Up @@ -5749,7 +5764,16 @@ which@^4.0.0:
dependencies:
isexe "^3.1.1"

"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
Expand Down
89 changes: 89 additions & 0 deletions examples/examples_nodejs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,22 @@
package examples

import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ecr"
"github.com/pulumi/providertest/pulumitest"
"github.com/pulumi/providertest/pulumitest/optnewstack"
"github.com/pulumi/providertest/pulumitest/opttest"
"github.com/pulumi/pulumi/pkg/v3/testing/integration"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestAccTrailTs(t *testing.T) {
Expand Down Expand Up @@ -360,6 +368,87 @@ func TestAccEcsParallel(t *testing.T) {
})
}

func TestDockerUpgrade(t *testing.T) {
providerName := "awsx"
baselineVersion := "2.19.0"
t.Parallel()
cwd := getCwd(t)
options := []opttest.Option{
opttest.DownloadProviderVersion(providerName, baselineVersion),
opttest.NewStackOptions(optnewstack.DisableAutoDestroy()),
}
pt := pulumitest.NewPulumiTest(t, filepath.Join(cwd, "ts-ecr-simple"), options...)

t.Logf("Running `pulumi up` with v%s of the awsx provider", baselineVersion)
result := pt.Up(t)
require.Contains(t, result.Outputs, "image", "image should be in the outputs")
originalImageUri := result.Outputs["image"].Value.(string)
require.NotEmpty(t, originalImageUri, "imageUri should not be empty")

state := pt.ExportStack(t)
pt = pt.CopyToTempDir(t,
opttest.Defaults(),
opttest.NewStackOptions(optnewstack.EnableAutoDestroy()),
opttest.LocalProviderPath("awsx", filepath.Join(cwd, "..", "bin")),
opttest.YarnLink("@pulumi/awsx"),
)
pt.ImportStack(t, state)

t.Log("Running `pulumi up` with the latest version of the awsx provider")
result = pt.Up(t)
require.Contains(t, result.Outputs, "image", "image should be in the outputs")
updatedImageUri := result.Outputs["image"].Value.(string)
require.NotEmpty(t, updatedImageUri, "imageUri should not be empty")

require.Contains(t, result.Outputs, "repositoryName", "repositoryName should be in the outputs")
repoName := result.Outputs["repositoryName"].Value.(string)
require.NotEmpty(t, repoName, "repositoryName should not be empty")

t.Logf("Verifying images in ECR repository %q", repoName)
client := createEcrClient(t)
describeImagesInput := &ecr.DescribeImagesInput{
RepositoryName: aws.String(repoName),
}

var describeImagesOutput *ecr.DescribeImagesOutput
var err error
for retries := 0; retries < 10; retries++ {
describeImagesOutput, err = client.DescribeImages(context.TODO(), describeImagesInput)
require.NoError(t, err, "failed to describe images")
if len(describeImagesOutput.ImageDetails) >= 2 {
break
}
time.Sleep(5 * time.Second)
}
require.NotEmpty(t, describeImagesOutput.ImageDetails, "image details should not be empty")
require.Len(t, describeImagesOutput.ImageDetails, 2, "should have 2 images")

// Extract digests from URIs
getDigest := func(uri string) string {
parts := strings.Split(uri, "@sha256:")
require.Len(t, parts, 2, "URI should contain sha256 digest")
return parts[1]
}
originalDigest := getDigest(originalImageUri)
updatedDigest := getDigest(updatedImageUri)

// Find matching digests in ECR response
foundOriginal := false
foundUpdated := false
for _, img := range describeImagesOutput.ImageDetails {
digest := strings.TrimPrefix(*img.ImageDigest, "sha256:")
if digest == originalDigest {
foundOriginal = true
}
if digest == updatedDigest {
foundUpdated = true
}
}

assert.Truef(t, foundOriginal, "original image digest %q should exist in ECR", originalDigest)
assert.Truef(t, foundUpdated, "updated image digest %q should exist in ECR", updatedDigest)
}

func getNodeJSBaseOptions(t *testing.T) integration.ProgramTestOptions {
base := getBaseOptions(t)
nodeBase := base.With(integration.ProgramTestOptions{
Expand Down
25 changes: 25 additions & 0 deletions examples/examples_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package examples

import (
"context"
"fmt"
"os"
"testing"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ecr"
"github.com/pulumi/pulumi/pkg/v3/testing/integration"
"github.com/stretchr/testify/require"
)

func getEnvRegion(t *testing.T) string {
Expand Down Expand Up @@ -56,3 +61,23 @@ func maxDuration(dur time.Duration, t *testing.T, test func(t *testing.T)) {
case <-done:
}
}

func loadAwsDefaultConfig(t *testing.T) aws.Config {
loadOpts := []func(*config.LoadOptions) error{}
if p, ok := os.LookupEnv("AWS_PROFILE"); ok {
loadOpts = append(loadOpts, config.WithSharedConfigProfile(p))
}
if r, ok := os.LookupEnv("AWS_REGION"); ok {
loadOpts = append(loadOpts, config.WithRegion(r))
}
cfg, err := config.LoadDefaultConfig(context.TODO(), loadOpts...)
require.NoError(t, err, "failed to load AWS config")

return cfg
}

func createEcrClient(t *testing.T) *ecr.Client {
client := ecr.NewFromConfig(loadAwsDefaultConfig(t))
require.NotNil(t, client, "failed to create ECR client")
return client
}
Loading

0 comments on commit fe08e89

Please sign in to comment.