From e1539f56480f0822841a8b7e0d99fb1f73fce724 Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Mon, 6 Jan 2025 19:36:05 +0100 Subject: [PATCH 01/17] Add schema for ecr.RegistryImage resource --- awsx/ecr/auth.ts | 54 +++++++++ awsx/package.json | 1 + awsx/yarn.lock | 8 ++ schemagen/pkg/gen/docs/ecr/registry-image.md | 117 +++++++++++++++++++ schemagen/pkg/gen/ecr.go | 79 ++++++++++--- schemagen/pkg/gen/schema.go | 36 ++++-- 6 files changed, 269 insertions(+), 26 deletions(-) create mode 100644 awsx/ecr/auth.ts create mode 100644 schemagen/pkg/gen/docs/ecr/registry-image.md diff --git a/awsx/ecr/auth.ts b/awsx/ecr/auth.ts new file mode 100644 index 000000000..941261f00 --- /dev/null +++ b/awsx/ecr/auth.ts @@ -0,0 +1,54 @@ +// Copyright 2016-2022, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as aws from "@pulumi/aws"; +import * as pulumi from "@pulumi/pulumi"; + +export interface DockerCredentials { + address: string; + username: string; + password: string; +} + +export function getAuthToken(registryUrl: string, opts: pulumi.InvokeOutputOptions): pulumi.Output { + // add protocol to help parse the url + if (!registryUrl?.startsWith("https://")) { + registryUrl = "https://" + registryUrl; + } + if (!URL.canParse(registryUrl)) { + throw new Error(`Cannot parse invalid registry URL: '${registryUrl}'`); + } + + // the registry id is the AWS account id. It's the first part of the hostname + const parsedUrl = new URL(registryUrl); + const registryId = parsedUrl.hostname.split(".")[0]; + + const ecrCredentials = aws.ecr.getCredentialsOutput( + { registryId: registryId }, + opts, + ); + + return ecrCredentials.apply((creds) => { + const decodedCredentials = Buffer.from(creds.authorizationToken, "base64").toString(); + const [username, password] = decodedCredentials.split(":"); + if (!password || !username) { + throw new Error("Invalid credentials"); + } + return { + address: creds.proxyEndpoint, + username: username, + password: password, + }; + }); +} diff --git a/awsx/package.json b/awsx/package.json index f9328939a..a6b676cbb 100644 --- a/awsx/package.json +++ b/awsx/package.json @@ -25,6 +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.8", "@pulumi/docker-build": "0.0.8", "@pulumi/pulumi": "3.144.1", "@types/aws-lambda": "^8.10.23", diff --git a/awsx/yarn.lock b/awsx/yarn.lock index ca88f640d..82118b799 100644 --- a/awsx/yarn.lock +++ b/awsx/yarn.lock @@ -1678,6 +1678,14 @@ dependencies: "@pulumi/pulumi" "^3.136.0" +"@pulumi/docker@4.5.8": + version "4.5.8" + resolved "https://registry.yarnpkg.com/@pulumi/docker/-/docker-4.5.8.tgz#55cefdebcee55eedf0674352ab7f6b8101420c30" + integrity sha512-h5ZfsXTt5GaqenOmleNAJT/zXLErYXYMftgFNbTS4Z1n1gQXwBewxZ/p7nEqKZkh0JjZZuoDlRN1+lkosM5W6w== + dependencies: + "@pulumi/pulumi" "^3.142.0" + semver "^5.4.0" + "@pulumi/pulumi@3.144.1", "@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" diff --git a/schemagen/pkg/gen/docs/ecr/registry-image.md b/schemagen/pkg/gen/docs/ecr/registry-image.md new file mode 100644 index 000000000..c1819c2ea --- /dev/null +++ b/schemagen/pkg/gen/docs/ecr/registry-image.md @@ -0,0 +1,117 @@ +Manages the lifecycle of a docker image in a registry. You can upload images to a registry (= `docker push`) and also delete them again. In contrast to [`awsx.ecr.Image`](/registry/packages/awsx/api-docs/ecr/image/), this resource does not require to build the image, but can be used to push an existing image to an ECR repository. The image will be pushed whenever the source image changes or is updated. + +{{% examples %}} +## Example Usage +{{% example %}} +### Pushing an image to an ECR repository + +```typescript +import * as pulumi from "@pulumi/pulumi"; +import * as awsx from "@pulumi/awsx"; + +const repository = new awsx.ecr.Repository("repository", { forceDelete: true }); + +const preTaggedImage = new awsx.ecr.RegistryImage("registry-image", { + repositoryUrl: repository.url, + sourceImage: "my-awesome-image:v1.0.0", +}); +``` +```python +import pulumi +import pulumi_awsx as awsx + +repository = awsx.ecr.Repository("repository", force_delete=True) + +registry_image = awsx.ecr.RegistryImage("registry_image", + repository_url=repository.url, + source_image="my-awesome-image:v1.0.0") +``` +```go +package main + +import ( + "github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ecr" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +func main() { + pulumi.Run(func(ctx *pulumi.Context) error { + repository, err := ecr.NewRepository(ctx, "repository", &ecr.RepositoryArgs{ + ForceDelete: pulumi.Bool(true), + }) + if err != nil { + return err + } + + registryImage, err := ecr.NewRegistryImage(ctx, "registryImage", &ecr.RegistryImageArgs{ + RepositoryUrl: repository.Url, + SourceImage: pulumi.String("my-awesome-image:v1.0.0"), + }) + if err != nil { + return err + } + + return nil + }) +} +``` +```csharp +using Pulumi; +using Pulumi.Awsx.Ecr; + +return await Pulumi.Deployment.RunAsync(() => +{ + var repository = new Repository("repository", new RepositoryArgs + { + ForceDelete = true, + }); + + var registryImage = new RegistryImage("registryImage", new RegistryImageArgs + { + RepositoryUrl = repository.Url, + SourceImage = "my-awesome-image:v1.0.0", + }); + + return new Dictionary{}; +}); +``` +```yaml +name: example +runtime: yaml +resources: + repository: + type: awsx:ecr:Repository + properties: + forceDelete: true + registryImage: + type: awsx:ecr:RegistryImage + properties: + repositoryUrl: ${repository.url} + sourceImage: "my-awesome-image:v1.0.0" +``` +```java +import com.pulumi.Pulumi; +import com.pulumi.awsx.ecr.Repository; +import com.pulumi.awsx.ecr.RepositoryArgs; +import com.pulumi.awsx.ecr.RegistryImage; +import com.pulumi.awsx.ecr.RegistryImageArgs; + +public class Main { + public static void main(String[] args) { + Pulumi.run(ctx -> { + // Create an ECR repository with force delete enabled + var repository = new Repository("repository", RepositoryArgs.builder() + .forceDelete(true) + .build()); + + // Create a RegistryImage based on the ECR repository URL and source image + var registryImage = new RegistryImage("registryImage", RegistryImageArgs.builder() + .repositoryUrl(repository.url()) + .sourceImage("my-awesome-image:v1.0.0") + .build()); + }); + } +} +``` +{{% /example %}} +{{% /examples %}} diff --git a/schemagen/pkg/gen/ecr.go b/schemagen/pkg/gen/ecr.go index c28fdddd1..8f7461f1a 100644 --- a/schemagen/pkg/gen/ecr.go +++ b/schemagen/pkg/gen/ecr.go @@ -15,21 +15,26 @@ package gen import ( + _ "embed" "github.com/pulumi/pulumi/pkg/v3/codegen/schema" ) +//go:embed docs/ecr/registry-image.md +var registryImageDocs string + func generateEcr(awsSpec, dockerSpec schema.PackageSpec) schema.PackageSpec { return schema.PackageSpec{ Resources: map[string]schema.ResourceSpec{ - "awsx:ecr:Repository": repository(awsSpec), - "awsx:ecr:Image": ecrImage(awsSpec, dockerSpec), + "awsx:ecr:Repository": repository(awsSpec), + "awsx:ecr:Image": ecrImage(), + "awsx:ecr:RegistryImage": registryImage(dockerSpec), }, Types: map[string]schema.ComplexTypeSpec{ - "awsx:ecr:DockerBuild": dockerBuild(dockerSpec), + "awsx:ecr:DockerBuild": dockerBuild(), "awsx:ecr:BuilderVersion": builderVersion(), - "awsx:ecr:lifecyclePolicy": lifecyclePolicy(awsSpec), - "awsx:ecr:lifecyclePolicyRule": lifecyclePolicyRule(awsSpec), - "awsx:ecr:lifecycleTagStatus": lifecycleTagStatus(awsSpec), + "awsx:ecr:lifecyclePolicy": lifecyclePolicy(), + "awsx:ecr:lifecyclePolicyRule": lifecyclePolicyRule(), + "awsx:ecr:lifecycleTagStatus": lifecycleTagStatus(), }, } } @@ -81,7 +86,7 @@ func repository(awsSpec schema.PackageSpec) schema.ResourceSpec { } } -func lifecyclePolicy(awsSpec schema.PackageSpec) schema.ComplexTypeSpec { +func lifecyclePolicy() schema.ComplexTypeSpec { return schema.ComplexTypeSpec{ ObjectTypeSpec: schema.ObjectTypeSpec{ Type: "object", @@ -108,7 +113,7 @@ func lifecyclePolicy(awsSpec schema.PackageSpec) schema.ComplexTypeSpec { } } -func lifecyclePolicyRule(awsSpec schema.PackageSpec) schema.ComplexTypeSpec { +func lifecyclePolicyRule() schema.ComplexTypeSpec { return schema.ComplexTypeSpec{ ObjectTypeSpec: schema.ObjectTypeSpec{ Type: "object", @@ -153,7 +158,7 @@ func lifecyclePolicyRule(awsSpec schema.PackageSpec) schema.ComplexTypeSpec { } } -func lifecycleTagStatus(awsSpec schema.PackageSpec) schema.ComplexTypeSpec { +func lifecycleTagStatus() schema.ComplexTypeSpec { return schema.ComplexTypeSpec{ ObjectTypeSpec: schema.ObjectTypeSpec{ Type: "string", @@ -178,8 +183,8 @@ func lifecycleTagStatus(awsSpec schema.PackageSpec) schema.ComplexTypeSpec { } } -func ecrImage(awsSpec schema.PackageSpec, dockerSpec schema.PackageSpec) schema.ResourceSpec { - inputs := dockerBuildProperties(dockerSpec) +func ecrImage() schema.ResourceSpec { + inputs := dockerBuildProperties() inputs["repositoryUrl"] = schema.PropertySpec{ Description: "Url of the repository", TypeSpec: schema.TypeSpec{ @@ -212,12 +217,12 @@ func ecrImage(awsSpec schema.PackageSpec, dockerSpec schema.PackageSpec) schema. } } -func dockerBuild(dockerSpec schema.PackageSpec) schema.ComplexTypeSpec { +func dockerBuild() schema.ComplexTypeSpec { return schema.ComplexTypeSpec{ ObjectTypeSpec: schema.ObjectTypeSpec{ Type: "object", Description: "Arguments for building a docker image", - Properties: dockerBuildProperties(dockerSpec), + Properties: dockerBuildProperties(), }, } } @@ -241,7 +246,7 @@ func builderVersion() schema.ComplexTypeSpec { } } -func dockerBuildProperties(dockerSpec schema.PackageSpec) map[string]schema.PropertySpec { +func dockerBuildProperties() map[string]schema.PropertySpec { return map[string]schema.PropertySpec{ "args": { Description: "An optional map of named build-time argument variables to set during the Docker build. This flag allows you to pass built-time variables that can be accessed like environment variables inside the `RUN` instruction.", @@ -306,3 +311,49 @@ func dockerBuildProperties(dockerSpec schema.PackageSpec) map[string]schema.Prop }, } } + +func registryImage(dockerSpec schema.PackageSpec) schema.ResourceSpec { + originalSpec := dockerSpec.Resources["docker:index/registryImage:RegistryImage"] + inputProperties := renameDockerPropertiesRefs(dockerSpec, originalSpec.InputProperties) + inputProperties["repositoryUrl"] = schema.PropertySpec{ + Description: "Url of the ECR repository.", + TypeSpec: schema.TypeSpec{ + Type: "string", + }, + } + + delete(inputProperties, "name") + inputProperties["sourceImage"] = schema.PropertySpec{ + Description: "The source image to push to the registry. The image is pushed with its existing tag by default. " + + "If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default.\n" + + "You can override the tag by using the `tag` input property.", + TypeSpec: schema.TypeSpec{ + Type: "string", + }, + } + inputProperties["tag"] = schema.PropertySpec{ + Description: "The tag to use for the pushed image. If not provided, the tag of the source image is used.", + TypeSpec: schema.TypeSpec{ + Type: "string", + }, + } + + return schema.ResourceSpec{ + IsComponent: true, + InputProperties: inputProperties, + RequiredInputs: []string{"repositoryUrl", "sourceImage"}, + ObjectTypeSpec: schema.ObjectTypeSpec{ + Type: "object", + Description: registryImageDocs, + Properties: map[string]schema.PropertySpec{ + "image": { + Description: "The underlying RegistryImage resource.", + TypeSpec: schema.TypeSpec{ + Ref: packageRef(dockerSpec, "/resources/docker:index%2fregistryImage:RegistryImage"), + }, + }, + }, + Required: []string{"image"}, + }, + } +} diff --git a/schemagen/pkg/gen/schema.go b/schemagen/pkg/gen/schema.go index 3af92c27c..a3dd6c5cf 100644 --- a/schemagen/pkg/gen/schema.go +++ b/schemagen/pkg/gen/schema.go @@ -37,7 +37,7 @@ func GenerateSchema(packageDir string) schema.PackageSpec { dependencies := readPackageDependencies(packageDir) awsSpec := getPackageSpec("aws", dependencies.Aws) awsNativeSpec := getPackageSpec("aws-native", awsNativeTypesVersion) - dockerSpec := getPackageSpec("docker-build", dependencies.Docker) + dockerSpec := getPackageSpec("docker", dependencies.Docker) packageSpec := schema.PackageSpec{ Name: "awsx", @@ -56,9 +56,10 @@ func GenerateSchema(packageDir string) schema.PackageSpec { "csharp": rawMessage(map[string]interface{}{ "packageReferences": map[string]string{ // We use .* format rather than [x,y) because then it prefers the maximum satisfiable version - "Pulumi": "3.*", - "Pulumi.Aws": "6.*", - "Pulumi.Docker": "4.*", + "Pulumi": "3.*", + "Pulumi.Aws": "6.*", + "Pulumi.Docker": "4.*", + "Pulumi.DockerBuild": "0.*", }, "liftSingleValueMethodReturns": true, "respectSchemaVersion": true, @@ -67,20 +68,25 @@ func GenerateSchema(packageDir string) schema.PackageSpec { "generateResourceContainerTypes": true, "importBasePath": "github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx", "liftSingleValueMethodReturns": true, - "internalDependencies": []string{"github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild"}, - "respectSchemaVersion": true, + "internalDependencies": []string{ + "github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild", + "github.com/pulumi/pulumi-docker/sdk/v4/go/docker", + }, + "respectSchemaVersion": true, }), "java": rawMessage(map[string]interface{}{ "dependencies": map[string]string{ "com.pulumi:aws": dependencies.Aws, - "com.pulumi:docker-build": dependencies.Docker, + "com.pulumi:docker": dependencies.Docker, + "com.pulumi:docker-build": dependencies.DockerBuild, }, }), "nodejs": rawMessage(map[string]interface{}{ "dependencies": map[string]string{ "@aws-sdk/client-ecs": "^3.405.0", "@pulumi/aws": "^" + dependencies.Aws, - "@pulumi/docker-build": "^" + dependencies.Docker, + "@pulumi/docker": "^" + dependencies.Docker, + "@pulumi/docker-build": "^" + dependencies.DockerBuild, "docker-classic": "npm:@pulumi/docker@3.6.1", "@types/aws-lambda": "^8.10.23", "mime": "^2.0.0", @@ -95,7 +101,8 @@ func GenerateSchema(packageDir string) schema.PackageSpec { "python": rawMessage(map[string]interface{}{ "requires": map[string]string{ "pulumi-aws": ">=6.0.4,<7.0.0", - "pulumi-docker-build": fmt.Sprintf(">=%s,<1.0.0", dependencies.Docker), + "pulumi-docker": fmt.Sprintf(">=%s,<5.0.0", dependencies.Docker), + "pulumi-docker-build": fmt.Sprintf(">=%s,<1.0.0", dependencies.DockerBuild), }, "usesIOClasses": true, "readme": "Pulumi Amazon Web Services (AWS) AWSX Components.", @@ -170,6 +177,10 @@ func renameAwsPropertiesRefs(spec schema.PackageSpec, propertySpec map[string]sc return renamePropertiesRefs(propertySpec, "#/types/aws:", packageRef(spec, "/types/aws:")) } +func renameDockerPropertiesRefs(spec schema.PackageSpec, propertySpec map[string]schema.PropertySpec) map[string]schema.PropertySpec { + return renamePropertiesRefs(propertySpec, "#/types/docker:", packageRef(spec, "/types/docker:")) +} + func renamePropertiesRefs(propertySpec map[string]schema.PropertySpec, old, new string) map[string]schema.PropertySpec { properties := map[string]schema.PropertySpec{} for k, v := range propertySpec { @@ -252,9 +263,10 @@ func rawMessage(v interface{}) schema.RawMessage { } type Dependencies struct { - Aws string `json:"@pulumi/aws"` - Docker string `json:"@pulumi/docker-build"` - Pulumi string `json:"@pulumi/pulumi"` + Aws string `json:"@pulumi/aws"` + Docker string `json:"@pulumi/docker"` + DockerBuild string `json:"@pulumi/docker-build"` + Pulumi string `json:"@pulumi/pulumi"` } type PackageJson struct { From b0b438958fcbbc9b25f522c8d397f168938e0936 Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Mon, 6 Jan 2025 19:36:53 +0100 Subject: [PATCH 02/17] Implement RegistryImage resource --- awsx/ecr/auth.ts | 59 ++++++++++++------------ awsx/ecr/image.ts | 19 +------- awsx/ecr/index.ts | 1 + awsx/ecr/registryImage.test.ts | 34 ++++++++++++++ awsx/ecr/registryImage.ts | 83 ++++++++++++++++++++++++++++++++++ awsx/resources.ts | 4 +- awsx/schema-types.ts | 15 ++++++ 7 files changed, 165 insertions(+), 50 deletions(-) create mode 100644 awsx/ecr/registryImage.test.ts create mode 100644 awsx/ecr/registryImage.ts diff --git a/awsx/ecr/auth.ts b/awsx/ecr/auth.ts index 941261f00..28ba9fee6 100644 --- a/awsx/ecr/auth.ts +++ b/awsx/ecr/auth.ts @@ -1,4 +1,4 @@ -// Copyright 2016-2022, Pulumi Corporation. +// Copyright 2016-2024, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,39 +16,36 @@ import * as aws from "@pulumi/aws"; import * as pulumi from "@pulumi/pulumi"; export interface DockerCredentials { - address: string; - username: string; - password: string; + address: string; + username: string; + password: string; } -export function getAuthToken(registryUrl: string, opts: pulumi.InvokeOutputOptions): pulumi.Output { - // add protocol to help parse the url - if (!registryUrl?.startsWith("https://")) { - registryUrl = "https://" + registryUrl; - } - if (!URL.canParse(registryUrl)) { - throw new Error(`Cannot parse invalid registry URL: '${registryUrl}'`); - } +export function getDockerCredentials( + registryUrl: string, + opts: pulumi.InvokeOutputOptions, +): pulumi.Output { + // add protocol to help parse the url + if (!registryUrl?.startsWith("https://")) { + registryUrl = "https://" + registryUrl; + } - // the registry id is the AWS account id. It's the first part of the hostname - const parsedUrl = new URL(registryUrl); - const registryId = parsedUrl.hostname.split(".")[0]; + // the registry id is the AWS account id. It's the first part of the hostname + const parsedUrl = new URL(registryUrl); + const registryId = parsedUrl.hostname.split(".")[0]; - const ecrCredentials = aws.ecr.getCredentialsOutput( - { registryId: registryId }, - opts, - ); + const ecrCredentials = aws.ecr.getCredentialsOutput({ registryId: registryId }, opts); - return ecrCredentials.apply((creds) => { - const decodedCredentials = Buffer.from(creds.authorizationToken, "base64").toString(); - const [username, password] = decodedCredentials.split(":"); - if (!password || !username) { - throw new Error("Invalid credentials"); - } - return { - address: creds.proxyEndpoint, - username: username, - password: password, - }; - }); + return ecrCredentials.apply((creds) => { + const decodedCredentials = Buffer.from(creds.authorizationToken, "base64").toString(); + const [username, password] = decodedCredentials.split(":"); + if (!password || !username) { + throw new Error("Invalid credentials"); + } + return { + address: creds.proxyEndpoint, + username: username, + password: password, + }; + }); } diff --git a/awsx/ecr/image.ts b/awsx/ecr/image.ts index b9eaf4de5..746659f0e 100644 --- a/awsx/ecr/image.ts +++ b/awsx/ecr/image.ts @@ -17,6 +17,7 @@ import * as docker from "@pulumi/docker-build"; import * as pulumi from "@pulumi/pulumi"; import * as schema from "../schema-types"; import * as utils from "../utils"; +import { getDockerCredentials } from "./auth"; export class Image extends schema.Image { constructor(name: string, args: schema.ImageArgs, opts: pulumi.ComponentResourceOptions = {}) { @@ -50,23 +51,7 @@ export function computeImageFromAsset( // the unique image name we pushed to. The name will change if the image changes ensuring // the TaskDefinition get's replaced IFF the built image changes. - const ecrCredentials = aws.ecr.getCredentialsOutput( - { registryId: registryId }, - { parent, async: true }, - ); - - const registryCredentials = ecrCredentials.authorizationToken.apply((authorizationToken) => { - const decodedCredentials = Buffer.from(authorizationToken, "base64").toString(); - const [username, password] = decodedCredentials.split(":"); - if (!password || !username) { - throw new Error("Invalid credentials"); - } - return { - address: ecrCredentials.proxyEndpoint, - username: username, - password: password, - }; - }); + const registryCredentials = getDockerCredentials(repositoryUrl, { parent }); let cacheFrom: docker.types.input.CacheFromArgs[] = []; if (dockerInputs.cacheFrom !== undefined) { diff --git a/awsx/ecr/index.ts b/awsx/ecr/index.ts index 5941ba8a3..681e2ea8a 100644 --- a/awsx/ecr/index.ts +++ b/awsx/ecr/index.ts @@ -14,3 +14,4 @@ export * from "./repository"; export * from "./image"; +export * from "./registryImage"; diff --git a/awsx/ecr/registryImage.test.ts b/awsx/ecr/registryImage.test.ts new file mode 100644 index 000000000..f5c194394 --- /dev/null +++ b/awsx/ecr/registryImage.test.ts @@ -0,0 +1,34 @@ +// Copyright 2016-2024, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { parseImageTag } from "./registryImage"; + +describe("parseImageTag", () => { + it("should return 'latest' for sha256 digest", () => { + const result = parseImageTag( + "sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + ); + expect(result).toEqual("latest"); + }); + + it("should return 'latest' if no tag is present", () => { + const result = parseImageTag("repository/image"); + expect(result).toEqual("latest"); + }); + + it("should return the tag if present", () => { + const result = parseImageTag("repository/image:tag"); + expect(result).toEqual("tag"); + }); +}); diff --git a/awsx/ecr/registryImage.ts b/awsx/ecr/registryImage.ts new file mode 100644 index 000000000..88fe733df --- /dev/null +++ b/awsx/ecr/registryImage.ts @@ -0,0 +1,83 @@ +// Copyright 2016-2024, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as docker from "@pulumi/docker"; +import * as pulumi from "@pulumi/pulumi"; +import * as schema from "../schema-types"; +import { getDockerCredentials } from "./auth"; + +export class RegistryImage extends schema.RegistryImage { + constructor( + name: string, + args: schema.RegistryImageArgs, + opts?: pulumi.ComponentResourceOptions, + ) { + super(name, args, opts); + + const creds = pulumi + .output(args.repositoryUrl) + .apply((url) => getDockerCredentials(url, { parent: this })); + const provider = new docker.Provider(name, { + registryAuth: [creds], + }); + + // tag the source image in the form of : + // we explicitly look up the source image in order to trigger a push whenever the source image changes + const sourceImage = docker.getRemoteImageOutput( + { name: args.sourceImage }, + { parent: this, provider }, + ); + const tagName = args.tag + ? pulumi.output(args.tag) + : pulumi.output(args.sourceImage).apply((image) => parseImageTag(image)); + const tag = new docker.Tag( + name, + { + sourceImage: sourceImage.id, + targetImage: pulumi.interpolate`${args.repositoryUrl}:${tagName}`, + }, + { parent: this, provider }, + ); + + this.image = new docker.RegistryImage( + name, + { + ...args, + name: tag.targetImage, + triggers: { + ...args.triggers, + // trigger a push whenever the source image changes + "@pulumi/awsx/internal/sourceImage": sourceImage.id, + }, + }, + { parent: this, provider }, + ); + } +} + +export function parseImageTag(str: string): string { + // Check if the string is a sha256 digest (starts with "sha256:" followed by 64 hex characters) + if (/^sha256:[a-f0-9]{64}$/i.test(str)) { + return "latest"; + } + + const parts = str.split(":"); + if (parts.length < 2) { + // if there is no tag, use the latest tag + return "latest"; + } + + // the tag is the last part of the image, tags are not allowed to contain colons so we can just take the last part + return parts[parts.length - 1]; +} diff --git a/awsx/resources.ts b/awsx/resources.ts index fdac2ac98..a6c1e0125 100644 --- a/awsx/resources.ts +++ b/awsx/resources.ts @@ -15,8 +15,7 @@ import * as pulumi from "@pulumi/pulumi"; import { Trail } from "./cloudtrail"; import * as ec2 from "./ec2"; -import { Repository } from "./ecr"; -import { Image } from "./ecr/image"; +import { Image, RegistryImage, Repository } from "./ecr"; import * as ecs from "./ecs"; import * as lb from "./lb"; import * as schemaTypes from "./schema-types"; @@ -34,6 +33,7 @@ const resources: schemaTypes.ResourceConstructor = { "awsx:ec2:DefaultVpc": (...args) => new ec2.DefaultVpc(...args), "awsx:ecr:Repository": (...args) => new Repository(...args), "awsx:ecr:Image": (...args) => new Image(...args), + "awsx:ecr:RegistryImage": (...args) => new RegistryImage(...args), }; export function construct( diff --git a/awsx/schema-types.ts b/awsx/schema-types.ts index e76c4e7ff..7fac52b71 100644 --- a/awsx/schema-types.ts +++ b/awsx/schema-types.ts @@ -10,6 +10,7 @@ export type ResourceConstructor = { readonly "awsx:ec2:DefaultVpc": ConstructComponent; readonly "awsx:ec2:Vpc": ConstructComponent; readonly "awsx:ecr:Image": ConstructComponent; + readonly "awsx:ecr:RegistryImage": ConstructComponent; readonly "awsx:ecr:Repository": ConstructComponent; readonly "awsx:ecs:EC2Service": ConstructComponent; readonly "awsx:ecs:EC2TaskDefinition": ConstructComponent; @@ -118,6 +119,20 @@ export interface ImageArgs { readonly repositoryUrl: pulumi.Input; readonly target?: pulumi.Input; } +export abstract class RegistryImage extends (pulumi.ComponentResource) { + public image!: unknown | pulumi.Output; + constructor(name: string, args: pulumi.Inputs, opts: pulumi.ComponentResourceOptions = {}) { + super("awsx:ecr:RegistryImage", name, opts.urn ? { image: undefined } : { name, args, opts }, opts); + } +} +export interface RegistryImageArgs { + readonly insecureSkipVerify?: pulumi.Input; + readonly keepRemotely?: pulumi.Input; + readonly repositoryUrl: pulumi.Input; + readonly sourceImage: pulumi.Input; + readonly tag?: pulumi.Input; + readonly triggers?: pulumi.Input>>; +} export abstract class Repository extends (pulumi.ComponentResource) { public lifecyclePolicy?: aws.ecr.LifecyclePolicy | pulumi.Output; public repository!: aws.ecr.Repository | pulumi.Output; From 8669c8de1fb0e33fac3703ff1612bd25f78216f7 Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Mon, 6 Jan 2025 19:37:04 +0100 Subject: [PATCH 03/17] regen schema --- schema.json | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/schema.json b/schema.json index ae2458f77..128ae5315 100644 --- a/schema.json +++ b/schema.json @@ -19,7 +19,8 @@ "packageReferences": { "Pulumi": "3.*", "Pulumi.Aws": "6.*", - "Pulumi.Docker": "4.*" + "Pulumi.Docker": "4.*", + "Pulumi.DockerBuild": "0.*" }, "respectSchemaVersion": true }, @@ -27,7 +28,8 @@ "generateResourceContainerTypes": true, "importBasePath": "github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx", "internalDependencies": [ - "github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild" + "github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild", + "github.com/pulumi/pulumi-docker/sdk/v4/go/docker" ], "liftSingleValueMethodReturns": true, "respectSchemaVersion": true @@ -35,6 +37,7 @@ "java": { "dependencies": { "com.pulumi:aws": "6.65.0", + "com.pulumi:docker": "4.5.8", "com.pulumi:docker-build": "0.0.8" } }, @@ -42,6 +45,7 @@ "dependencies": { "@aws-sdk/client-ecs": "^3.405.0", "@pulumi/aws": "^6.65.0", + "@pulumi/docker": "^4.5.8", "@pulumi/docker-build": "^0.0.8", "@types/aws-lambda": "^8.10.23", "docker-classic": "npm:@pulumi/docker@3.6.1", @@ -62,6 +66,7 @@ "readme": "Pulumi Amazon Web Services (AWS) AWSX Components.", "requires": { "pulumi-aws": "\u003e=6.0.4,\u003c7.0.0", + "pulumi-docker": "\u003e=4.5.8,\u003c5.0.0", "pulumi-docker-build": "\u003e=0.0.8,\u003c1.0.0" }, "respectSchemaVersion": true, @@ -2220,6 +2225,54 @@ ], "isComponent": true }, + "awsx:ecr:RegistryImage": { + "description": "Manages the lifecycle of a docker image in a registry. You can upload images to a registry (= `docker push`) and also delete them again. In contrast to [`awsx.ecr.Image`](/registry/packages/awsx/api-docs/ecr/image/), this resource does not require to build the image, but can be used to push an existing image to an ECR repository. The image will be pushed whenever the source image changes or is updated.\n\n{{% examples %}}\n## Example Usage\n{{% example %}}\n### Pushing an image to an ECR repository\n\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as awsx from \"@pulumi/awsx\";\n\nconst repository = new awsx.ecr.Repository(\"repository\", { forceDelete: true });\n\nconst preTaggedImage = new awsx.ecr.RegistryImage(\"registry-image\", {\n repositoryUrl: repository.url,\n sourceImage: \"my-awesome-image:v1.0.0\",\n});\n```\n```python\nimport pulumi\nimport pulumi_awsx as awsx\n\nrepository = awsx.ecr.Repository(\"repository\", force_delete=True)\n\nregistry_image = awsx.ecr.RegistryImage(\"registry_image\",\n repository_url=repository.url,\n source_image=\"my-awesome-image:v1.0.0\")\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ecr\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\trepository, err := ecr.NewRepository(ctx, \"repository\", \u0026ecr.RepositoryArgs{\n\t\t\tForceDelete: pulumi.Bool(true),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tregistryImage, err := ecr.NewRegistryImage(ctx, \"registryImage\", \u0026ecr.RegistryImageArgs{\n\t\t\tRepositoryUrl: repository.Url,\n\t\t\tSourceImage: pulumi.String(\"my-awesome-image:v1.0.0\"),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n}\n```\n```csharp\nusing Pulumi;\nusing Pulumi.Awsx.Ecr;\n\nreturn await Pulumi.Deployment.RunAsync(() =\u003e\n{\n var repository = new Repository(\"repository\", new RepositoryArgs\n {\n ForceDelete = true,\n });\n\n var registryImage = new RegistryImage(\"registryImage\", new RegistryImageArgs\n {\n RepositoryUrl = repository.Url,\n SourceImage = \"my-awesome-image:v1.0.0\",\n });\n\n return new Dictionary\u003cstring, object?\u003e{};\n});\n```\n```yaml\nname: example\nruntime: yaml\nresources:\n repository:\n type: awsx:ecr:Repository\n properties:\n forceDelete: true\n registryImage:\n type: awsx:ecr:RegistryImage\n properties:\n repositoryUrl: ${repository.url}\n sourceImage: \"my-awesome-image:v1.0.0\"\n```\n```java\nimport com.pulumi.Pulumi;\nimport com.pulumi.awsx.ecr.Repository;\nimport com.pulumi.awsx.ecr.RepositoryArgs;\nimport com.pulumi.awsx.ecr.RegistryImage;\nimport com.pulumi.awsx.ecr.RegistryImageArgs;\n\npublic class Main {\n public static void main(String[] args) {\n Pulumi.run(ctx -\u003e {\n // Create an ECR repository with force delete enabled\n var repository = new Repository(\"repository\", RepositoryArgs.builder()\n .forceDelete(true)\n .build());\n\n // Create a RegistryImage based on the ECR repository URL and source image\n var registryImage = new RegistryImage(\"registryImage\", RegistryImageArgs.builder()\n .repositoryUrl(repository.url())\n .sourceImage(\"my-awesome-image:v1.0.0\")\n .build());\n });\n }\n}\n```\n{{% /example %}}\n{{% /examples %}}\n", + "properties": { + "image": { + "$ref": "/docker/v4.5.8/schema.json#/resources/docker:index%2fregistryImage:RegistryImage", + "description": "The underlying RegistryImage resource." + } + }, + "type": "object", + "required": [ + "image" + ], + "inputProperties": { + "insecureSkipVerify": { + "type": "boolean", + "description": "If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false`\n" + }, + "keepRemotely": { + "type": "boolean", + "description": "If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false`\n" + }, + "repositoryUrl": { + "type": "string", + "description": "Url of the ECR repository." + }, + "sourceImage": { + "type": "string", + "description": "The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default.\nYou can override the tag by using the `tag` input property." + }, + "tag": { + "type": "string", + "description": "The tag to use for the pushed image. If not provided, the tag of the source image is used." + }, + "triggers": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image\n", + "willReplaceOnChanges": true + } + }, + "requiredInputs": [ + "repositoryUrl", + "sourceImage" + ], + "isComponent": true + }, "awsx:ecr:Repository": { "description": "A [Repository] represents an [aws.ecr.Repository] along with an associated [LifecyclePolicy] controlling how images are retained in the repo. \n\nDocker images can be built and pushed to the repo using the [buildAndPushImage] method. This will call into the `@pulumi/docker/buildAndPushImage` function using this repo as the appropriate destination registry.", "properties": { From 693545dba76ed3a9293d43f0a109a4e2f7457e83 Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Mon, 6 Jan 2025 19:37:12 +0100 Subject: [PATCH 04/17] Regen SDK --- sdk/dotnet/Ecr/RegistryImage.cs | 123 ++++++++ sdk/dotnet/Pulumi.Awsx.csproj | 1 + sdk/go.mod | 1 + sdk/go.sum | 4 +- sdk/go/awsx/cloudtrail/init.go | 1 + sdk/go/awsx/ec2/init.go | 1 + sdk/go/awsx/ecr/init.go | 3 + sdk/go/awsx/ecr/registryImage.go | 253 ++++++++++++++++ sdk/go/awsx/ecs/init.go | 1 + sdk/go/awsx/init.go | 1 + sdk/go/awsx/lb/init.go | 1 + sdk/java/build.gradle | 1 + .../com/pulumi/awsx/ecr/RegistryImage.java | 94 ++++++ .../pulumi/awsx/ecr/RegistryImageArgs.java | 276 ++++++++++++++++++ sdk/nodejs/ecr/index.ts | 7 + sdk/nodejs/ecr/registryImage.ts | 108 +++++++ sdk/nodejs/package.json | 1 + sdk/nodejs/tsconfig.json | 1 + sdk/python/pulumi_awsx/__init__.py | 1 + sdk/python/pulumi_awsx/ecr/__init__.py | 1 + sdk/python/pulumi_awsx/ecr/registry_image.py | 241 +++++++++++++++ sdk/python/pyproject.toml | 2 +- 22 files changed, 1120 insertions(+), 3 deletions(-) create mode 100644 sdk/dotnet/Ecr/RegistryImage.cs create mode 100644 sdk/go/awsx/ecr/registryImage.go create mode 100644 sdk/java/src/main/java/com/pulumi/awsx/ecr/RegistryImage.java create mode 100644 sdk/java/src/main/java/com/pulumi/awsx/ecr/RegistryImageArgs.java create mode 100644 sdk/nodejs/ecr/registryImage.ts create mode 100644 sdk/python/pulumi_awsx/ecr/registry_image.py diff --git a/sdk/dotnet/Ecr/RegistryImage.cs b/sdk/dotnet/Ecr/RegistryImage.cs new file mode 100644 index 000000000..ce0e19065 --- /dev/null +++ b/sdk/dotnet/Ecr/RegistryImage.cs @@ -0,0 +1,123 @@ +// *** WARNING: this file was generated by pulumi-gen-awsx. *** +// *** Do not edit by hand unless you're certain you know what you are doing! *** + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Pulumi.Serialization; + +namespace Pulumi.Awsx.Ecr +{ + /// + /// Manages the lifecycle of a docker image in a registry. You can upload images to a registry (= `docker push`) and also delete them again. In contrast to [`awsx.ecr.Image`](/registry/packages/awsx/api-docs/ecr/image/), this resource does not require to build the image, but can be used to push an existing image to an ECR repository. The image will be pushed whenever the source image changes or is updated. + /// + /// ## Example Usage + /// ### Pushing an image to an ECR repository + /// ```csharp + /// using Pulumi; + /// using Pulumi.Awsx.Ecr; + /// + /// return await Pulumi.Deployment.RunAsync(() => + /// { + /// var repository = new Repository("repository", new RepositoryArgs + /// { + /// ForceDelete = true, + /// }); + /// + /// var registryImage = new RegistryImage("registryImage", new RegistryImageArgs + /// { + /// RepositoryUrl = repository.Url, + /// SourceImage = "my-awesome-image:v1.0.0", + /// }); + /// + /// return new Dictionary<string, object?>{}; + /// }); + /// ``` + /// + [AwsxResourceType("awsx:ecr:RegistryImage")] + public partial class RegistryImage : global::Pulumi.ComponentResource + { + /// + /// The underlying RegistryImage resource. + /// + [Output("image")] + public Output Image { get; private set; } = null!; + + + /// + /// Create a RegistryImage resource with the given unique name, arguments, and options. + /// + /// + /// The unique name of the resource + /// The arguments used to populate this resource's properties + /// A bag of options that control this resource's behavior + public RegistryImage(string name, RegistryImageArgs args, ComponentResourceOptions? options = null) + : base("awsx:ecr:RegistryImage", name, args ?? new RegistryImageArgs(), MakeResourceOptions(options, ""), remote: true) + { + } + + private static ComponentResourceOptions MakeResourceOptions(ComponentResourceOptions? options, Input? id) + { + var defaultOptions = new ComponentResourceOptions + { + Version = Utilities.Version, + }; + var merged = ComponentResourceOptions.Merge(defaultOptions, options); + // Override the ID if one was specified for consistency with other language SDKs. + merged.Id = id ?? merged.Id; + return merged; + } + } + + public sealed class RegistryImageArgs : global::Pulumi.ResourceArgs + { + /// + /// If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false` + /// + [Input("insecureSkipVerify")] + public Input? InsecureSkipVerify { get; set; } + + /// + /// If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` + /// + [Input("keepRemotely")] + public Input? KeepRemotely { get; set; } + + /// + /// Url of the ECR repository. + /// + [Input("repositoryUrl", required: true)] + public Input RepositoryUrl { get; set; } = null!; + + /// + /// The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. + /// You can override the tag by using the `tag` input property. + /// + [Input("sourceImage", required: true)] + public Input SourceImage { get; set; } = null!; + + /// + /// The tag to use for the pushed image. If not provided, the tag of the source image is used. + /// + [Input("tag")] + public Input? Tag { get; set; } + + [Input("triggers")] + private InputMap? _triggers; + + /// + /// A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image + /// + public InputMap Triggers + { + get => _triggers ?? (_triggers = new InputMap()); + set => _triggers = value; + } + + public RegistryImageArgs() + { + } + public static new RegistryImageArgs Empty => new RegistryImageArgs(); + } +} diff --git a/sdk/dotnet/Pulumi.Awsx.csproj b/sdk/dotnet/Pulumi.Awsx.csproj index 92a766b3a..173acca16 100644 --- a/sdk/dotnet/Pulumi.Awsx.csproj +++ b/sdk/dotnet/Pulumi.Awsx.csproj @@ -48,6 +48,7 @@ + diff --git a/sdk/go.mod b/sdk/go.mod index 4ffe072cf..865de467c 100644 --- a/sdk/go.mod +++ b/sdk/go.mod @@ -8,6 +8,7 @@ require ( github.com/blang/semver v3.5.1+incompatible github.com/pulumi/pulumi-aws/sdk/v6 v6.65.0 github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild v0.0.3 + github.com/pulumi/pulumi-docker/sdk/v4 v4.5.8 github.com/pulumi/pulumi/sdk/v3 v3.144.1 ) diff --git a/sdk/go.sum b/sdk/go.sum index eaa55526f..07efd9ec7 100644 --- a/sdk/go.sum +++ b/sdk/go.sum @@ -17,8 +17,6 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= -github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= -github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= @@ -154,6 +152,8 @@ github.com/pulumi/pulumi-aws/sdk/v6 v6.65.0 h1:OvCLqUueOja9YE2WEGPYAw+lKHFRbLQ7Q github.com/pulumi/pulumi-aws/sdk/v6 v6.65.0/go.mod h1:FFzye44v9E0BgaFXVB/9X7KH0S0MapoXEy2YonrQfz4= github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild v0.0.3 h1:NxCXxRvzhUJP9dIvlpNlZKt/A3NHu3i9pC5XO+i8bR0= github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild v0.0.3/go.mod h1:jw9NcyRXjv5V2HHHJlqUBdXFCFiLfZoCChWEn38LR2A= +github.com/pulumi/pulumi-docker/sdk/v4 v4.5.8 h1:rik9L2SIpsoDenY51MkogR6GWgu/0Sy/XmyQmKWNUqU= +github.com/pulumi/pulumi-docker/sdk/v4 v4.5.8/go.mod h1:eph7BPNPkEIIK882/Ll4dbeHl5wZEc/UvTcUW0CK1UY= github.com/pulumi/pulumi/sdk/v3 v3.144.1 h1:QQtCDERihhlfvcmRzqHeBmOER1Fg1VkFj9933Lxqv00= github.com/pulumi/pulumi/sdk/v3 v3.144.1/go.mod h1:/6gxU2XirnLlImBy5OoqV6I4HcjOf+IznNIZNZExZzo= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= diff --git a/sdk/go/awsx/cloudtrail/init.go b/sdk/go/awsx/cloudtrail/init.go index 693f137cd..011874518 100644 --- a/sdk/go/awsx/cloudtrail/init.go +++ b/sdk/go/awsx/cloudtrail/init.go @@ -9,6 +9,7 @@ import ( "github.com/blang/semver" "github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/internal" _ "github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild" + _ "github.com/pulumi/pulumi-docker/sdk/v4/go/docker" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) diff --git a/sdk/go/awsx/ec2/init.go b/sdk/go/awsx/ec2/init.go index 7236b0605..6c5cc654f 100644 --- a/sdk/go/awsx/ec2/init.go +++ b/sdk/go/awsx/ec2/init.go @@ -9,6 +9,7 @@ import ( "github.com/blang/semver" "github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/internal" _ "github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild" + _ "github.com/pulumi/pulumi-docker/sdk/v4/go/docker" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) diff --git a/sdk/go/awsx/ecr/init.go b/sdk/go/awsx/ecr/init.go index ee838f76a..ba5716673 100644 --- a/sdk/go/awsx/ecr/init.go +++ b/sdk/go/awsx/ecr/init.go @@ -9,6 +9,7 @@ import ( "github.com/blang/semver" "github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/internal" _ "github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild" + _ "github.com/pulumi/pulumi-docker/sdk/v4/go/docker" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) @@ -24,6 +25,8 @@ func (m *module) Construct(ctx *pulumi.Context, name, typ, urn string) (r pulumi switch typ { case "awsx:ecr:Image": r = &Image{} + case "awsx:ecr:RegistryImage": + r = &RegistryImage{} case "awsx:ecr:Repository": r = &Repository{} default: diff --git a/sdk/go/awsx/ecr/registryImage.go b/sdk/go/awsx/ecr/registryImage.go new file mode 100644 index 000000000..cf50cfbbf --- /dev/null +++ b/sdk/go/awsx/ecr/registryImage.go @@ -0,0 +1,253 @@ +// Code generated by pulumi-gen-awsx DO NOT EDIT. +// *** WARNING: Do not edit by hand unless you're certain you know what you are doing! *** + +package ecr + +import ( + "context" + "reflect" + + "errors" + "github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/internal" + "github.com/pulumi/pulumi-docker/sdk/v4/go/docker" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +// Manages the lifecycle of a docker image in a registry. You can upload images to a registry (= `docker push`) and also delete them again. In contrast to [`awsx.ecr.Image`](/registry/packages/awsx/api-docs/ecr/image/), this resource does not require to build the image, but can be used to push an existing image to an ECR repository. The image will be pushed whenever the source image changes or is updated. +// +// ## Example Usage +// ### Pushing an image to an ECR repository +// ```go +// package main +// +// import ( +// +// "github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ecr" +// "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +// +// ) +// +// func main() { +// pulumi.Run(func(ctx *pulumi.Context) error { +// repository, err := ecr.NewRepository(ctx, "repository", &ecr.RepositoryArgs{ +// ForceDelete: pulumi.Bool(true), +// }) +// if err != nil { +// return err +// } +// +// registryImage, err := ecr.NewRegistryImage(ctx, "registryImage", &ecr.RegistryImageArgs{ +// RepositoryUrl: repository.Url, +// SourceImage: pulumi.String("my-awesome-image:v1.0.0"), +// }) +// if err != nil { +// return err +// } +// +// return nil +// }) +// } +// +// ``` +type RegistryImage struct { + pulumi.ResourceState + + // The underlying RegistryImage resource. + Image docker.RegistryImageOutput `pulumi:"image"` +} + +// NewRegistryImage registers a new resource with the given unique name, arguments, and options. +func NewRegistryImage(ctx *pulumi.Context, + name string, args *RegistryImageArgs, opts ...pulumi.ResourceOption) (*RegistryImage, error) { + if args == nil { + return nil, errors.New("missing one or more required arguments") + } + + if args.RepositoryUrl == nil { + return nil, errors.New("invalid value for required argument 'RepositoryUrl'") + } + if args.SourceImage == nil { + return nil, errors.New("invalid value for required argument 'SourceImage'") + } + opts = internal.PkgResourceDefaultOpts(opts) + var resource RegistryImage + err := ctx.RegisterRemoteComponentResource("awsx:ecr:RegistryImage", name, args, &resource, opts...) + if err != nil { + return nil, err + } + return &resource, nil +} + +type registryImageArgs struct { + // If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false` + InsecureSkipVerify *bool `pulumi:"insecureSkipVerify"` + // If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` + KeepRemotely *bool `pulumi:"keepRemotely"` + // Url of the ECR repository. + RepositoryUrl string `pulumi:"repositoryUrl"` + // The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. + // You can override the tag by using the `tag` input property. + SourceImage string `pulumi:"sourceImage"` + // The tag to use for the pushed image. If not provided, the tag of the source image is used. + Tag *string `pulumi:"tag"` + // A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image + Triggers map[string]string `pulumi:"triggers"` +} + +// The set of arguments for constructing a RegistryImage resource. +type RegistryImageArgs struct { + // If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false` + InsecureSkipVerify pulumi.BoolPtrInput + // If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` + KeepRemotely pulumi.BoolPtrInput + // Url of the ECR repository. + RepositoryUrl pulumi.StringInput + // The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. + // You can override the tag by using the `tag` input property. + SourceImage pulumi.StringInput + // The tag to use for the pushed image. If not provided, the tag of the source image is used. + Tag pulumi.StringPtrInput + // A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image + Triggers pulumi.StringMapInput +} + +func (RegistryImageArgs) ElementType() reflect.Type { + return reflect.TypeOf((*registryImageArgs)(nil)).Elem() +} + +type RegistryImageInput interface { + pulumi.Input + + ToRegistryImageOutput() RegistryImageOutput + ToRegistryImageOutputWithContext(ctx context.Context) RegistryImageOutput +} + +func (*RegistryImage) ElementType() reflect.Type { + return reflect.TypeOf((**RegistryImage)(nil)).Elem() +} + +func (i *RegistryImage) ToRegistryImageOutput() RegistryImageOutput { + return i.ToRegistryImageOutputWithContext(context.Background()) +} + +func (i *RegistryImage) ToRegistryImageOutputWithContext(ctx context.Context) RegistryImageOutput { + return pulumi.ToOutputWithContext(ctx, i).(RegistryImageOutput) +} + +// RegistryImageArrayInput is an input type that accepts RegistryImageArray and RegistryImageArrayOutput values. +// You can construct a concrete instance of `RegistryImageArrayInput` via: +// +// RegistryImageArray{ RegistryImageArgs{...} } +type RegistryImageArrayInput interface { + pulumi.Input + + ToRegistryImageArrayOutput() RegistryImageArrayOutput + ToRegistryImageArrayOutputWithContext(context.Context) RegistryImageArrayOutput +} + +type RegistryImageArray []RegistryImageInput + +func (RegistryImageArray) ElementType() reflect.Type { + return reflect.TypeOf((*[]*RegistryImage)(nil)).Elem() +} + +func (i RegistryImageArray) ToRegistryImageArrayOutput() RegistryImageArrayOutput { + return i.ToRegistryImageArrayOutputWithContext(context.Background()) +} + +func (i RegistryImageArray) ToRegistryImageArrayOutputWithContext(ctx context.Context) RegistryImageArrayOutput { + return pulumi.ToOutputWithContext(ctx, i).(RegistryImageArrayOutput) +} + +// RegistryImageMapInput is an input type that accepts RegistryImageMap and RegistryImageMapOutput values. +// You can construct a concrete instance of `RegistryImageMapInput` via: +// +// RegistryImageMap{ "key": RegistryImageArgs{...} } +type RegistryImageMapInput interface { + pulumi.Input + + ToRegistryImageMapOutput() RegistryImageMapOutput + ToRegistryImageMapOutputWithContext(context.Context) RegistryImageMapOutput +} + +type RegistryImageMap map[string]RegistryImageInput + +func (RegistryImageMap) ElementType() reflect.Type { + return reflect.TypeOf((*map[string]*RegistryImage)(nil)).Elem() +} + +func (i RegistryImageMap) ToRegistryImageMapOutput() RegistryImageMapOutput { + return i.ToRegistryImageMapOutputWithContext(context.Background()) +} + +func (i RegistryImageMap) ToRegistryImageMapOutputWithContext(ctx context.Context) RegistryImageMapOutput { + return pulumi.ToOutputWithContext(ctx, i).(RegistryImageMapOutput) +} + +type RegistryImageOutput struct{ *pulumi.OutputState } + +func (RegistryImageOutput) ElementType() reflect.Type { + return reflect.TypeOf((**RegistryImage)(nil)).Elem() +} + +func (o RegistryImageOutput) ToRegistryImageOutput() RegistryImageOutput { + return o +} + +func (o RegistryImageOutput) ToRegistryImageOutputWithContext(ctx context.Context) RegistryImageOutput { + return o +} + +// The underlying RegistryImage resource. +func (o RegistryImageOutput) Image() docker.RegistryImageOutput { + return o.ApplyT(func(v *RegistryImage) docker.RegistryImageOutput { return v.Image }).(docker.RegistryImageOutput) +} + +type RegistryImageArrayOutput struct{ *pulumi.OutputState } + +func (RegistryImageArrayOutput) ElementType() reflect.Type { + return reflect.TypeOf((*[]*RegistryImage)(nil)).Elem() +} + +func (o RegistryImageArrayOutput) ToRegistryImageArrayOutput() RegistryImageArrayOutput { + return o +} + +func (o RegistryImageArrayOutput) ToRegistryImageArrayOutputWithContext(ctx context.Context) RegistryImageArrayOutput { + return o +} + +func (o RegistryImageArrayOutput) Index(i pulumi.IntInput) RegistryImageOutput { + return pulumi.All(o, i).ApplyT(func(vs []interface{}) *RegistryImage { + return vs[0].([]*RegistryImage)[vs[1].(int)] + }).(RegistryImageOutput) +} + +type RegistryImageMapOutput struct{ *pulumi.OutputState } + +func (RegistryImageMapOutput) ElementType() reflect.Type { + return reflect.TypeOf((*map[string]*RegistryImage)(nil)).Elem() +} + +func (o RegistryImageMapOutput) ToRegistryImageMapOutput() RegistryImageMapOutput { + return o +} + +func (o RegistryImageMapOutput) ToRegistryImageMapOutputWithContext(ctx context.Context) RegistryImageMapOutput { + return o +} + +func (o RegistryImageMapOutput) MapIndex(k pulumi.StringInput) RegistryImageOutput { + return pulumi.All(o, k).ApplyT(func(vs []interface{}) *RegistryImage { + return vs[0].(map[string]*RegistryImage)[vs[1].(string)] + }).(RegistryImageOutput) +} + +func init() { + pulumi.RegisterInputType(reflect.TypeOf((*RegistryImageInput)(nil)).Elem(), &RegistryImage{}) + pulumi.RegisterInputType(reflect.TypeOf((*RegistryImageArrayInput)(nil)).Elem(), RegistryImageArray{}) + pulumi.RegisterInputType(reflect.TypeOf((*RegistryImageMapInput)(nil)).Elem(), RegistryImageMap{}) + pulumi.RegisterOutputType(RegistryImageOutput{}) + pulumi.RegisterOutputType(RegistryImageArrayOutput{}) + pulumi.RegisterOutputType(RegistryImageMapOutput{}) +} diff --git a/sdk/go/awsx/ecs/init.go b/sdk/go/awsx/ecs/init.go index 753dd55e0..9a0b3143e 100644 --- a/sdk/go/awsx/ecs/init.go +++ b/sdk/go/awsx/ecs/init.go @@ -9,6 +9,7 @@ import ( "github.com/blang/semver" "github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/internal" _ "github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild" + _ "github.com/pulumi/pulumi-docker/sdk/v4/go/docker" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) diff --git a/sdk/go/awsx/init.go b/sdk/go/awsx/init.go index 321628d3a..824ef462d 100644 --- a/sdk/go/awsx/init.go +++ b/sdk/go/awsx/init.go @@ -9,6 +9,7 @@ import ( "github.com/blang/semver" "github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/internal" _ "github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild" + _ "github.com/pulumi/pulumi-docker/sdk/v4/go/docker" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) diff --git a/sdk/go/awsx/lb/init.go b/sdk/go/awsx/lb/init.go index a33f45d18..8c8c46e8e 100644 --- a/sdk/go/awsx/lb/init.go +++ b/sdk/go/awsx/lb/init.go @@ -9,6 +9,7 @@ import ( "github.com/blang/semver" "github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/internal" _ "github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild" + _ "github.com/pulumi/pulumi-docker/sdk/v4/go/docker" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) diff --git a/sdk/java/build.gradle b/sdk/java/build.gradle index edd580abc..36e9cd65e 100644 --- a/sdk/java/build.gradle +++ b/sdk/java/build.gradle @@ -44,6 +44,7 @@ dependencies { implementation("com.google.code.findbugs:jsr305:3.0.2") implementation("com.google.code.gson:gson:2.8.9") implementation("com.pulumi:aws:6.65.0") + implementation("com.pulumi:docker:4.5.8") implementation("com.pulumi:docker-build:0.0.8") implementation("com.pulumi:pulumi:0.9.7") } diff --git a/sdk/java/src/main/java/com/pulumi/awsx/ecr/RegistryImage.java b/sdk/java/src/main/java/com/pulumi/awsx/ecr/RegistryImage.java new file mode 100644 index 000000000..d9b82a337 --- /dev/null +++ b/sdk/java/src/main/java/com/pulumi/awsx/ecr/RegistryImage.java @@ -0,0 +1,94 @@ +// *** WARNING: this file was generated by pulumi-java-gen. *** +// *** Do not edit by hand unless you're certain you know what you are doing! *** + +package com.pulumi.awsx.ecr; + +import com.pulumi.awsx.Utilities; +import com.pulumi.awsx.ecr.RegistryImageArgs; +import com.pulumi.core.Output; +import com.pulumi.core.annotations.Export; +import com.pulumi.core.annotations.ResourceType; +import com.pulumi.core.internal.Codegen; +import javax.annotation.Nullable; + +/** + * Manages the lifecycle of a docker image in a registry. You can upload images to a registry (= `docker push`) and also delete them again. In contrast to [`awsx.ecr.Image`](/registry/packages/awsx/api-docs/ecr/image/), this resource does not require to build the image, but can be used to push an existing image to an ECR repository. The image will be pushed whenever the source image changes or is updated. + * + * ## Example Usage + * ### Pushing an image to an ECR repository + * ```java + * import com.pulumi.Pulumi; + * import com.pulumi.awsx.ecr.Repository; + * import com.pulumi.awsx.ecr.RepositoryArgs; + * import com.pulumi.awsx.ecr.RegistryImage; + * import com.pulumi.awsx.ecr.RegistryImageArgs; + * + * public class Main { + * public static void main(String[] args) { + * Pulumi.run(ctx -> { + * // Create an ECR repository with force delete enabled + * var repository = new Repository("repository", RepositoryArgs.builder() + * .forceDelete(true) + * .build()); + * + * // Create a RegistryImage based on the ECR repository URL and source image + * var registryImage = new RegistryImage("registryImage", RegistryImageArgs.builder() + * .repositoryUrl(repository.url()) + * .sourceImage("my-awesome-image:v1.0.0") + * .build()); + * }); + * } + * } + * ``` + * + */ +@ResourceType(type="awsx:ecr:RegistryImage") +public class RegistryImage extends com.pulumi.resources.ComponentResource { + /** + * The underlying RegistryImage resource. + * + */ + @Export(name="image", refs={com.pulumi.docker.RegistryImage.class}, tree="[0]") + private Output image; + + /** + * @return The underlying RegistryImage resource. + * + */ + public Output image() { + return this.image; + } + + /** + * + * @param name The _unique_ name of the resulting resource. + */ + public RegistryImage(String name) { + this(name, RegistryImageArgs.Empty); + } + /** + * + * @param name The _unique_ name of the resulting resource. + * @param args The arguments to use to populate this resource's properties. + */ + public RegistryImage(String name, RegistryImageArgs args) { + this(name, args, null); + } + /** + * + * @param name The _unique_ name of the resulting resource. + * @param args The arguments to use to populate this resource's properties. + * @param options A bag of options that control this resource's behavior. + */ + public RegistryImage(String name, RegistryImageArgs args, @Nullable com.pulumi.resources.ComponentResourceOptions options) { + super("awsx:ecr:RegistryImage", name, args == null ? RegistryImageArgs.Empty : args, makeResourceOptions(options, Codegen.empty()), true); + } + + private static com.pulumi.resources.ComponentResourceOptions makeResourceOptions(@Nullable com.pulumi.resources.ComponentResourceOptions options, @Nullable Output id) { + var defaultOptions = com.pulumi.resources.ComponentResourceOptions.builder() + .version(Utilities.getVersion()) + .build(); + return com.pulumi.resources.ComponentResourceOptions.merge(defaultOptions, options, id); + } + +} diff --git a/sdk/java/src/main/java/com/pulumi/awsx/ecr/RegistryImageArgs.java b/sdk/java/src/main/java/com/pulumi/awsx/ecr/RegistryImageArgs.java new file mode 100644 index 000000000..6a57d9804 --- /dev/null +++ b/sdk/java/src/main/java/com/pulumi/awsx/ecr/RegistryImageArgs.java @@ -0,0 +1,276 @@ +// *** WARNING: this file was generated by pulumi-java-gen. *** +// *** Do not edit by hand unless you're certain you know what you are doing! *** + +package com.pulumi.awsx.ecr; + +import com.pulumi.core.Output; +import com.pulumi.core.annotations.Import; +import java.lang.Boolean; +import java.lang.String; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import javax.annotation.Nullable; + + +public final class RegistryImageArgs extends com.pulumi.resources.ResourceArgs { + + public static final RegistryImageArgs Empty = new RegistryImageArgs(); + + /** + * If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false` + * + */ + @Import(name="insecureSkipVerify") + private @Nullable Output insecureSkipVerify; + + /** + * @return If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false` + * + */ + public Optional> insecureSkipVerify() { + return Optional.ofNullable(this.insecureSkipVerify); + } + + /** + * If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` + * + */ + @Import(name="keepRemotely") + private @Nullable Output keepRemotely; + + /** + * @return If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` + * + */ + public Optional> keepRemotely() { + return Optional.ofNullable(this.keepRemotely); + } + + /** + * Url of the ECR repository. + * + */ + @Import(name="repositoryUrl", required=true) + private Output repositoryUrl; + + /** + * @return Url of the ECR repository. + * + */ + public Output repositoryUrl() { + return this.repositoryUrl; + } + + /** + * The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. + * You can override the tag by using the `tag` input property. + * + */ + @Import(name="sourceImage", required=true) + private Output sourceImage; + + /** + * @return The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. + * You can override the tag by using the `tag` input property. + * + */ + public Output sourceImage() { + return this.sourceImage; + } + + /** + * The tag to use for the pushed image. If not provided, the tag of the source image is used. + * + */ + @Import(name="tag") + private @Nullable Output tag; + + /** + * @return The tag to use for the pushed image. If not provided, the tag of the source image is used. + * + */ + public Optional> tag() { + return Optional.ofNullable(this.tag); + } + + /** + * A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image + * + */ + @Import(name="triggers") + private @Nullable Output> triggers; + + /** + * @return A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image + * + */ + public Optional>> triggers() { + return Optional.ofNullable(this.triggers); + } + + private RegistryImageArgs() {} + + private RegistryImageArgs(RegistryImageArgs $) { + this.insecureSkipVerify = $.insecureSkipVerify; + this.keepRemotely = $.keepRemotely; + this.repositoryUrl = $.repositoryUrl; + this.sourceImage = $.sourceImage; + this.tag = $.tag; + this.triggers = $.triggers; + } + + public static Builder builder() { + return new Builder(); + } + public static Builder builder(RegistryImageArgs defaults) { + return new Builder(defaults); + } + + public static final class Builder { + private RegistryImageArgs $; + + public Builder() { + $ = new RegistryImageArgs(); + } + + public Builder(RegistryImageArgs defaults) { + $ = new RegistryImageArgs(Objects.requireNonNull(defaults)); + } + + /** + * @param insecureSkipVerify If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false` + * + * @return builder + * + */ + public Builder insecureSkipVerify(@Nullable Output insecureSkipVerify) { + $.insecureSkipVerify = insecureSkipVerify; + return this; + } + + /** + * @param insecureSkipVerify If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false` + * + * @return builder + * + */ + public Builder insecureSkipVerify(Boolean insecureSkipVerify) { + return insecureSkipVerify(Output.of(insecureSkipVerify)); + } + + /** + * @param keepRemotely If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` + * + * @return builder + * + */ + public Builder keepRemotely(@Nullable Output keepRemotely) { + $.keepRemotely = keepRemotely; + return this; + } + + /** + * @param keepRemotely If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` + * + * @return builder + * + */ + public Builder keepRemotely(Boolean keepRemotely) { + return keepRemotely(Output.of(keepRemotely)); + } + + /** + * @param repositoryUrl Url of the ECR repository. + * + * @return builder + * + */ + public Builder repositoryUrl(Output repositoryUrl) { + $.repositoryUrl = repositoryUrl; + return this; + } + + /** + * @param repositoryUrl Url of the ECR repository. + * + * @return builder + * + */ + public Builder repositoryUrl(String repositoryUrl) { + return repositoryUrl(Output.of(repositoryUrl)); + } + + /** + * @param sourceImage The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. + * You can override the tag by using the `tag` input property. + * + * @return builder + * + */ + public Builder sourceImage(Output sourceImage) { + $.sourceImage = sourceImage; + return this; + } + + /** + * @param sourceImage The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. + * You can override the tag by using the `tag` input property. + * + * @return builder + * + */ + public Builder sourceImage(String sourceImage) { + return sourceImage(Output.of(sourceImage)); + } + + /** + * @param tag The tag to use for the pushed image. If not provided, the tag of the source image is used. + * + * @return builder + * + */ + public Builder tag(@Nullable Output tag) { + $.tag = tag; + return this; + } + + /** + * @param tag The tag to use for the pushed image. If not provided, the tag of the source image is used. + * + * @return builder + * + */ + public Builder tag(String tag) { + return tag(Output.of(tag)); + } + + /** + * @param triggers A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image + * + * @return builder + * + */ + public Builder triggers(@Nullable Output> triggers) { + $.triggers = triggers; + return this; + } + + /** + * @param triggers A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image + * + * @return builder + * + */ + public Builder triggers(Map triggers) { + return triggers(Output.of(triggers)); + } + + public RegistryImageArgs build() { + $.repositoryUrl = Objects.requireNonNull($.repositoryUrl, "expected parameter 'repositoryUrl' to be non-null"); + $.sourceImage = Objects.requireNonNull($.sourceImage, "expected parameter 'sourceImage' to be non-null"); + return $; + } + } + +} diff --git a/sdk/nodejs/ecr/index.ts b/sdk/nodejs/ecr/index.ts index 6e2559a82..ed30bdf02 100644 --- a/sdk/nodejs/ecr/index.ts +++ b/sdk/nodejs/ecr/index.ts @@ -10,6 +10,11 @@ export type Image = import("./image").Image; export const Image: typeof import("./image").Image = null as any; utilities.lazyLoad(exports, ["Image"], () => require("./image")); +export { RegistryImageArgs } from "./registryImage"; +export type RegistryImage = import("./registryImage").RegistryImage; +export const RegistryImage: typeof import("./registryImage").RegistryImage = null as any; +utilities.lazyLoad(exports, ["RegistryImage"], () => require("./registryImage")); + export { RepositoryArgs } from "./repository"; export type Repository = import("./repository").Repository; export const Repository: typeof import("./repository").Repository = null as any; @@ -25,6 +30,8 @@ const _module = { switch (type) { case "awsx:ecr:Image": return new Image(name, undefined, { urn }) + case "awsx:ecr:RegistryImage": + return new RegistryImage(name, undefined, { urn }) case "awsx:ecr:Repository": return new Repository(name, undefined, { urn }) default: diff --git a/sdk/nodejs/ecr/registryImage.ts b/sdk/nodejs/ecr/registryImage.ts new file mode 100644 index 000000000..0a32ffd94 --- /dev/null +++ b/sdk/nodejs/ecr/registryImage.ts @@ -0,0 +1,108 @@ +// *** WARNING: this file was generated by pulumi-gen-awsx. *** +// *** Do not edit by hand unless you're certain you know what you are doing! *** + +import * as pulumi from "@pulumi/pulumi"; +import * as utilities from "../utilities"; + +import * as pulumiDocker from "@pulumi/docker"; + +/** + * Manages the lifecycle of a docker image in a registry. You can upload images to a registry (= `docker push`) and also delete them again. In contrast to [`awsx.ecr.Image`](/registry/packages/awsx/api-docs/ecr/image/), this resource does not require to build the image, but can be used to push an existing image to an ECR repository. The image will be pushed whenever the source image changes or is updated. + * + * ## Example Usage + * ### Pushing an image to an ECR repository + * + * ```typescript + * import * as pulumi from "@pulumi/pulumi"; + * import * as awsx from "@pulumi/awsx"; + * + * const repository = new awsx.ecr.Repository("repository", { forceDelete: true }); + * + * const preTaggedImage = new awsx.ecr.RegistryImage("registry-image", { + * repositoryUrl: repository.url, + * sourceImage: "my-awesome-image:v1.0.0", + * }); + * ``` + */ +export class RegistryImage extends pulumi.ComponentResource { + /** @internal */ + public static readonly __pulumiType = 'awsx:ecr:RegistryImage'; + + /** + * Returns true if the given object is an instance of RegistryImage. This is designed to work even + * when multiple copies of the Pulumi SDK have been loaded into the same process. + */ + public static isInstance(obj: any): obj is RegistryImage { + if (obj === undefined || obj === null) { + return false; + } + return obj['__pulumiType'] === RegistryImage.__pulumiType; + } + + /** + * The underlying RegistryImage resource. + */ + public /*out*/ readonly image!: pulumi.Output; + + /** + * Create a RegistryImage resource with the given unique name, arguments, and options. + * + * @param name The _unique_ name of the resource. + * @param args The arguments to use to populate this resource's properties. + * @param opts A bag of options that control this resource's behavior. + */ + constructor(name: string, args: RegistryImageArgs, opts?: pulumi.ComponentResourceOptions) { + let resourceInputs: pulumi.Inputs = {}; + opts = opts || {}; + if (!opts.id) { + if ((!args || args.repositoryUrl === undefined) && !opts.urn) { + throw new Error("Missing required property 'repositoryUrl'"); + } + if ((!args || args.sourceImage === undefined) && !opts.urn) { + throw new Error("Missing required property 'sourceImage'"); + } + resourceInputs["insecureSkipVerify"] = args ? args.insecureSkipVerify : undefined; + resourceInputs["keepRemotely"] = args ? args.keepRemotely : undefined; + resourceInputs["repositoryUrl"] = args ? args.repositoryUrl : undefined; + resourceInputs["sourceImage"] = args ? args.sourceImage : undefined; + resourceInputs["tag"] = args ? args.tag : undefined; + resourceInputs["triggers"] = args ? args.triggers : undefined; + resourceInputs["image"] = undefined /*out*/; + } else { + resourceInputs["image"] = undefined /*out*/; + } + opts = pulumi.mergeOptions(utilities.resourceOptsDefaults(), opts); + super(RegistryImage.__pulumiType, name, resourceInputs, opts, true /*remote*/); + } +} + +/** + * The set of arguments for constructing a RegistryImage resource. + */ +export interface RegistryImageArgs { + /** + * If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false` + */ + insecureSkipVerify?: pulumi.Input; + /** + * If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` + */ + keepRemotely?: pulumi.Input; + /** + * Url of the ECR repository. + */ + repositoryUrl: pulumi.Input; + /** + * The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. + * You can override the tag by using the `tag` input property. + */ + sourceImage: pulumi.Input; + /** + * The tag to use for the pushed image. If not provided, the tag of the source image is used. + */ + tag?: pulumi.Input; + /** + * A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image + */ + triggers?: pulumi.Input<{[key: string]: pulumi.Input}>; +} diff --git a/sdk/nodejs/package.json b/sdk/nodejs/package.json index 898aa301f..8ceb3cf46 100644 --- a/sdk/nodejs/package.json +++ b/sdk/nodejs/package.json @@ -17,6 +17,7 @@ "dependencies": { "@aws-sdk/client-ecs": "^3.405.0", "@pulumi/aws": "^6.65.0", + "@pulumi/docker": "^4.5.8", "@pulumi/docker-build": "^0.0.8", "@pulumi/pulumi": "^3.142.0", "@types/aws-lambda": "^8.10.23", diff --git a/sdk/nodejs/tsconfig.json b/sdk/nodejs/tsconfig.json index 213e9bce6..f56c903a2 100644 --- a/sdk/nodejs/tsconfig.json +++ b/sdk/nodejs/tsconfig.json @@ -111,6 +111,7 @@ "ec2/vpc.ts", "ecr/image.ts", "ecr/index.ts", + "ecr/registryImage.ts", "ecr/repository.ts", "ecs/ec2service.ts", "ecs/ec2taskDefinition.ts", diff --git a/sdk/python/pulumi_awsx/__init__.py b/sdk/python/pulumi_awsx/__init__.py index 6032a297f..5e177c092 100644 --- a/sdk/python/pulumi_awsx/__init__.py +++ b/sdk/python/pulumi_awsx/__init__.py @@ -55,6 +55,7 @@ "fqn": "pulumi_awsx.ecr", "classes": { "awsx:ecr:Image": "Image", + "awsx:ecr:RegistryImage": "RegistryImage", "awsx:ecr:Repository": "Repository" } }, diff --git a/sdk/python/pulumi_awsx/ecr/__init__.py b/sdk/python/pulumi_awsx/ecr/__init__.py index 9137853aa..38a3d52e4 100644 --- a/sdk/python/pulumi_awsx/ecr/__init__.py +++ b/sdk/python/pulumi_awsx/ecr/__init__.py @@ -7,5 +7,6 @@ # Export this package's modules as members: from ._enums import * from .image import * +from .registry_image import * from .repository import * from ._inputs import * diff --git a/sdk/python/pulumi_awsx/ecr/registry_image.py b/sdk/python/pulumi_awsx/ecr/registry_image.py new file mode 100644 index 000000000..372dcc1c2 --- /dev/null +++ b/sdk/python/pulumi_awsx/ecr/registry_image.py @@ -0,0 +1,241 @@ +# coding=utf-8 +# *** WARNING: this file was generated by pulumi-gen-awsx. *** +# *** Do not edit by hand unless you're certain you know what you are doing! *** + +import copy +import warnings +import sys +import pulumi +import pulumi.runtime +from typing import Any, Mapping, Optional, Sequence, Union, overload +if sys.version_info >= (3, 11): + from typing import NotRequired, TypedDict, TypeAlias +else: + from typing_extensions import NotRequired, TypedDict, TypeAlias +from .. import _utilities +import pulumi_docker + +__all__ = ['RegistryImageArgs', 'RegistryImage'] + +@pulumi.input_type +class RegistryImageArgs: + def __init__(__self__, *, + repository_url: pulumi.Input[str], + source_image: pulumi.Input[str], + insecure_skip_verify: Optional[pulumi.Input[bool]] = None, + keep_remotely: Optional[pulumi.Input[bool]] = None, + tag: Optional[pulumi.Input[str]] = None, + triggers: Optional[pulumi.Input[Mapping[str, pulumi.Input[str]]]] = None): + """ + The set of arguments for constructing a RegistryImage resource. + :param pulumi.Input[str] repository_url: Url of the ECR repository. + :param pulumi.Input[str] source_image: The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. + You can override the tag by using the `tag` input property. + :param pulumi.Input[bool] insecure_skip_verify: If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false` + :param pulumi.Input[bool] keep_remotely: If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` + :param pulumi.Input[str] tag: The tag to use for the pushed image. If not provided, the tag of the source image is used. + :param pulumi.Input[Mapping[str, pulumi.Input[str]]] triggers: A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image + """ + pulumi.set(__self__, "repository_url", repository_url) + pulumi.set(__self__, "source_image", source_image) + if insecure_skip_verify is not None: + pulumi.set(__self__, "insecure_skip_verify", insecure_skip_verify) + if keep_remotely is not None: + pulumi.set(__self__, "keep_remotely", keep_remotely) + if tag is not None: + pulumi.set(__self__, "tag", tag) + if triggers is not None: + pulumi.set(__self__, "triggers", triggers) + + @property + @pulumi.getter(name="repositoryUrl") + def repository_url(self) -> pulumi.Input[str]: + """ + Url of the ECR repository. + """ + return pulumi.get(self, "repository_url") + + @repository_url.setter + def repository_url(self, value: pulumi.Input[str]): + pulumi.set(self, "repository_url", value) + + @property + @pulumi.getter(name="sourceImage") + def source_image(self) -> pulumi.Input[str]: + """ + The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. + You can override the tag by using the `tag` input property. + """ + return pulumi.get(self, "source_image") + + @source_image.setter + def source_image(self, value: pulumi.Input[str]): + pulumi.set(self, "source_image", value) + + @property + @pulumi.getter(name="insecureSkipVerify") + def insecure_skip_verify(self) -> Optional[pulumi.Input[bool]]: + """ + If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false` + """ + return pulumi.get(self, "insecure_skip_verify") + + @insecure_skip_verify.setter + def insecure_skip_verify(self, value: Optional[pulumi.Input[bool]]): + pulumi.set(self, "insecure_skip_verify", value) + + @property + @pulumi.getter(name="keepRemotely") + def keep_remotely(self) -> Optional[pulumi.Input[bool]]: + """ + If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` + """ + return pulumi.get(self, "keep_remotely") + + @keep_remotely.setter + def keep_remotely(self, value: Optional[pulumi.Input[bool]]): + pulumi.set(self, "keep_remotely", value) + + @property + @pulumi.getter + def tag(self) -> Optional[pulumi.Input[str]]: + """ + The tag to use for the pushed image. If not provided, the tag of the source image is used. + """ + return pulumi.get(self, "tag") + + @tag.setter + def tag(self, value: Optional[pulumi.Input[str]]): + pulumi.set(self, "tag", value) + + @property + @pulumi.getter + def triggers(self) -> Optional[pulumi.Input[Mapping[str, pulumi.Input[str]]]]: + """ + A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image + """ + return pulumi.get(self, "triggers") + + @triggers.setter + def triggers(self, value: Optional[pulumi.Input[Mapping[str, pulumi.Input[str]]]]): + pulumi.set(self, "triggers", value) + + +class RegistryImage(pulumi.ComponentResource): + @overload + def __init__(__self__, + resource_name: str, + opts: Optional[pulumi.ResourceOptions] = None, + insecure_skip_verify: Optional[pulumi.Input[bool]] = None, + keep_remotely: Optional[pulumi.Input[bool]] = None, + repository_url: Optional[pulumi.Input[str]] = None, + source_image: Optional[pulumi.Input[str]] = None, + tag: Optional[pulumi.Input[str]] = None, + triggers: Optional[pulumi.Input[Mapping[str, pulumi.Input[str]]]] = None, + __props__=None): + """ + Manages the lifecycle of a docker image in a registry. You can upload images to a registry (= `docker push`) and also delete them again. In contrast to [`awsx.ecr.Image`](/registry/packages/awsx/api-docs/ecr/image/), this resource does not require to build the image, but can be used to push an existing image to an ECR repository. The image will be pushed whenever the source image changes or is updated. + + ## Example Usage + ### Pushing an image to an ECR repository + ```python + import pulumi + import pulumi_awsx as awsx + + repository = awsx.ecr.Repository("repository", force_delete=True) + + registry_image = awsx.ecr.RegistryImage("registry_image", + repository_url=repository.url, + source_image="my-awesome-image:v1.0.0") + ``` + + :param str resource_name: The name of the resource. + :param pulumi.ResourceOptions opts: Options for the resource. + :param pulumi.Input[bool] insecure_skip_verify: If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false` + :param pulumi.Input[bool] keep_remotely: If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` + :param pulumi.Input[str] repository_url: Url of the ECR repository. + :param pulumi.Input[str] source_image: The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. + You can override the tag by using the `tag` input property. + :param pulumi.Input[str] tag: The tag to use for the pushed image. If not provided, the tag of the source image is used. + :param pulumi.Input[Mapping[str, pulumi.Input[str]]] triggers: A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image + """ + ... + @overload + def __init__(__self__, + resource_name: str, + args: RegistryImageArgs, + opts: Optional[pulumi.ResourceOptions] = None): + """ + Manages the lifecycle of a docker image in a registry. You can upload images to a registry (= `docker push`) and also delete them again. In contrast to [`awsx.ecr.Image`](/registry/packages/awsx/api-docs/ecr/image/), this resource does not require to build the image, but can be used to push an existing image to an ECR repository. The image will be pushed whenever the source image changes or is updated. + + ## Example Usage + ### Pushing an image to an ECR repository + ```python + import pulumi + import pulumi_awsx as awsx + + repository = awsx.ecr.Repository("repository", force_delete=True) + + registry_image = awsx.ecr.RegistryImage("registry_image", + repository_url=repository.url, + source_image="my-awesome-image:v1.0.0") + ``` + + :param str resource_name: The name of the resource. + :param RegistryImageArgs args: The arguments to use to populate this resource's properties. + :param pulumi.ResourceOptions opts: Options for the resource. + """ + ... + def __init__(__self__, resource_name: str, *args, **kwargs): + resource_args, opts = _utilities.get_resource_args_opts(RegistryImageArgs, pulumi.ResourceOptions, *args, **kwargs) + if resource_args is not None: + __self__._internal_init(resource_name, opts, **resource_args.__dict__) + else: + __self__._internal_init(resource_name, *args, **kwargs) + + def _internal_init(__self__, + resource_name: str, + opts: Optional[pulumi.ResourceOptions] = None, + insecure_skip_verify: Optional[pulumi.Input[bool]] = None, + keep_remotely: Optional[pulumi.Input[bool]] = None, + repository_url: Optional[pulumi.Input[str]] = None, + source_image: Optional[pulumi.Input[str]] = None, + tag: Optional[pulumi.Input[str]] = None, + triggers: Optional[pulumi.Input[Mapping[str, pulumi.Input[str]]]] = None, + __props__=None): + opts = pulumi.ResourceOptions.merge(_utilities.get_resource_opts_defaults(), opts) + if not isinstance(opts, pulumi.ResourceOptions): + raise TypeError('Expected resource options to be a ResourceOptions instance') + if opts.id is not None: + raise ValueError('ComponentResource classes do not support opts.id') + else: + if __props__ is not None: + raise TypeError('__props__ is only valid when passed in combination with a valid opts.id to get an existing resource') + __props__ = RegistryImageArgs.__new__(RegistryImageArgs) + + __props__.__dict__["insecure_skip_verify"] = insecure_skip_verify + __props__.__dict__["keep_remotely"] = keep_remotely + if repository_url is None and not opts.urn: + raise TypeError("Missing required property 'repository_url'") + __props__.__dict__["repository_url"] = repository_url + if source_image is None and not opts.urn: + raise TypeError("Missing required property 'source_image'") + __props__.__dict__["source_image"] = source_image + __props__.__dict__["tag"] = tag + __props__.__dict__["triggers"] = triggers + __props__.__dict__["image"] = None + super(RegistryImage, __self__).__init__( + 'awsx:ecr:RegistryImage', + resource_name, + __props__, + opts, + remote=True) + + @property + @pulumi.getter + def image(self) -> pulumi.Output['pulumi_docker.RegistryImage']: + """ + The underlying RegistryImage resource. + """ + return pulumi.get(self, "image") + diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index a9acb7bf5..c929f5fdc 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "pulumi_awsx" description = "Pulumi Amazon Web Services (AWS) AWSX Components." - dependencies = ["parver>=0.2.1", "pulumi>=3.142.0,<4.0.0", "pulumi-aws>=6.0.4,<7.0.0", "pulumi-docker-build>=0.0.8,<1.0.0", "semver>=2.8.1", "typing-extensions>=4.11; python_version < \"3.11\""] + dependencies = ["parver>=0.2.1", "pulumi>=3.142.0,<4.0.0", "pulumi-aws>=6.0.4,<7.0.0", "pulumi-docker>=4.5.8,<5.0.0", "pulumi-docker-build>=0.0.8,<1.0.0", "semver>=2.8.1", "typing-extensions>=4.11; python_version < \"3.11\""] keywords = ["pulumi", "aws", "awsx", "kind/component", "category/cloud"] readme = "README.md" requires-python = ">=3.9" From 73576780e766ab6d29eac433e377d3decb1f69fb Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Mon, 6 Jan 2025 19:37:31 +0100 Subject: [PATCH 05/17] Add test --- examples/examples_nodejs_test.go | 114 +++++++++++++++--- examples/ts-ecr-registry-image/Pulumi.yaml | 3 + examples/ts-ecr-registry-image/README.md | 5 + examples/ts-ecr-registry-image/app/Dockerfile | 4 + examples/ts-ecr-registry-image/index.ts | 45 +++++++ examples/ts-ecr-registry-image/package.json | 18 +++ examples/ts-ecr-registry-image/tsconfig.json | 18 +++ 7 files changed, 193 insertions(+), 14 deletions(-) create mode 100644 examples/ts-ecr-registry-image/Pulumi.yaml create mode 100644 examples/ts-ecr-registry-image/README.md create mode 100644 examples/ts-ecr-registry-image/app/Dockerfile create mode 100644 examples/ts-ecr-registry-image/index.ts create mode 100644 examples/ts-ecr-registry-image/package.json create mode 100644 examples/ts-ecr-registry-image/tsconfig.json diff --git a/examples/examples_nodejs_test.go b/examples/examples_nodejs_test.go index 2b2923a23..fb801edf0 100644 --- a/examples/examples_nodejs_test.go +++ b/examples/examples_nodejs_test.go @@ -27,6 +27,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ecr" + ecrTypes "github.com/aws/aws-sdk-go-v2/service/ecr/types" "github.com/pulumi/providertest/pulumitest" "github.com/pulumi/providertest/pulumitest/optnewstack" "github.com/pulumi/providertest/pulumitest/opttest" @@ -406,20 +407,8 @@ func TestDockerUpgrade(t *testing.T) { 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) - } + describeImagesOutput, err := getEcrImageDetails(t, client, repoName, 2) + require.NoError(t, err, "failed to describe images") require.NotEmpty(t, describeImagesOutput.ImageDetails, "image details should not be empty") require.Len(t, describeImagesOutput.ImageDetails, 2, "should have 2 images") @@ -449,6 +438,103 @@ func TestDockerUpgrade(t *testing.T) { assert.Truef(t, foundUpdated, "updated image digest %q should exist in ECR", updatedDigest) } +func TestEcrRegistryImage(t *testing.T) { + t.Parallel() + cwd := getCwd(t) + options := []opttest.Option{ + opttest.LocalProviderPath("awsx", filepath.Join(cwd, "..", "bin")), + opttest.YarnLink("@pulumi/awsx"), + } + pt := pulumitest.NewPulumiTest(t, filepath.Join(cwd, "ts-ecr-registry-image"), options...) + + pt.SetConfig(t, "message", "Hello Pulumi!") + + t.Log("Running `pulumi preview` with message=Hello Pulumi!") + previewResult := pt.Preview(t) + t.Logf("Preview:\n%s", previewResult.StdOut) + + t.Log("Running `pulumi up` with message=Hello Pulumi!") + upResult := pt.Up(t) + t.Logf("Up:\n%s", upResult.StdOut) + + require.Contains(t, upResult.Outputs, "repositoryName", "repositoryName should be in the outputs") + repoName := upResult.Outputs["repositoryName"].Value.(string) + require.NotEmpty(t, repoName, "repositoryName should not be empty") + + client := createEcrClient(t) + describeImagesOutput, err := getEcrImageDetails(t, client, repoName, 1) + require.NoError(t, err, "failed to describe images") + require.NotEmpty(t, describeImagesOutput.ImageDetails, "image details should not be empty") + require.Len(t, describeImagesOutput.ImageDetails, 1, "should have 1 image") + + firstImage := describeImagesOutput.ImageDetails[0] + tags := firstImage.ImageTags + require.Len(t, tags, 3, "image should have 3 tags") + require.Contains(t, tags, "test", "test tag should be in the image tags") + require.Contains(t, tags, "v1.0.0", "v1.0.0 tag should be in the image tags") + require.Contains(t, tags, "latest", "latest tag should be in the image tags") + + // This should produce a new image + pt.SetConfig(t, "message", "Hello Pulumi! (Again...)") + + t.Log("Running `pulumi preview` with message=Hello Pulumi! (Again...)") + previewResult = pt.Preview(t) + t.Logf("Preview:\n%s", previewResult.StdOut) + + t.Log("Running `pulumi up` with message=Hello Pulumi! (Again...)") + upResult = pt.Up(t) + t.Logf("Up:\n%s", upResult.StdOut) + + describeImagesOutput, err = getEcrImageDetails(t, client, repoName, 2) + require.NoError(t, err, "failed to describe images") + require.NotEmpty(t, describeImagesOutput.ImageDetails, "image details should not be empty") + require.Len(t, describeImagesOutput.ImageDetails, 2, "should have 2 images") + + var newImage *ecrTypes.ImageDetail + var oldImage *ecrTypes.ImageDetail + for _, img := range describeImagesOutput.ImageDetails { + if *img.ImageDigest != *firstImage.ImageDigest { + newImage = &img + } else { + oldImage = &img + } + } + + require.NotNil(t, newImage, "new image should not be nil") + require.NotNil(t, oldImage, "old image should still exist after the new image was pushed") + + // The tags should've been moved to the new image + require.Len(t, newImage.ImageTags, 3, "new image should have 3 tags") + require.Contains(t, newImage.ImageTags, "test", "the new image should have the test tag") + require.Contains(t, newImage.ImageTags, "v1.0.0", "the new image should have the v1.0.0 tag") + require.Contains(t, newImage.ImageTags, "latest", "the new image should have the latest tag") + + require.Empty(t, oldImage.ImageTags, "old image should have no tags after the new image was pushed") +} + +func getEcrImageDetails(t *testing.T, client *ecr.Client, repositoryName string, expectedImages int) (*ecr.DescribeImagesOutput, error) { + describeImagesInput := &ecr.DescribeImagesInput{ + RepositoryName: aws.String(repositoryName), + } + + var describeImagesOutput *ecr.DescribeImagesOutput + var err error + maxRetries := 4 + for retries := 0; retries < maxRetries; retries++ { + err = nil + describeImagesOutput, err = client.DescribeImages(context.TODO(), describeImagesInput) + if err != nil { + t.Logf("failed to describe images: %v", err) + continue + } + if len(describeImagesOutput.ImageDetails) >= expectedImages { + break + } + time.Sleep(5 * time.Second) + } + return describeImagesOutput, err +} + func getNodeJSBaseOptions(t *testing.T) integration.ProgramTestOptions { base := getBaseOptions(t) nodeBase := base.With(integration.ProgramTestOptions{ diff --git a/examples/ts-ecr-registry-image/Pulumi.yaml b/examples/ts-ecr-registry-image/Pulumi.yaml new file mode 100644 index 000000000..b9ee34921 --- /dev/null +++ b/examples/ts-ecr-registry-image/Pulumi.yaml @@ -0,0 +1,3 @@ +name: ecr-registry-image +runtime: nodejs +description: An example of pushing a local image to an ECR registry. diff --git a/examples/ts-ecr-registry-image/README.md b/examples/ts-ecr-registry-image/README.md new file mode 100644 index 000000000..ac60a9502 --- /dev/null +++ b/examples/ts-ecr-registry-image/README.md @@ -0,0 +1,5 @@ +# Pushing a local image to an ECR registry + +This example demonstrates how to push a local image to an ECR registry using the `awsx.ecr.RegistryImage` component. +The example builds a simple nginx image for demonstration purposes and pushes it to an ECR registry, demonstrating the +use of the `sourceImage` and `tag` properties. diff --git a/examples/ts-ecr-registry-image/app/Dockerfile b/examples/ts-ecr-registry-image/app/Dockerfile new file mode 100644 index 000000000..d8ce1aa42 --- /dev/null +++ b/examples/ts-ecr-registry-image/app/Dockerfile @@ -0,0 +1,4 @@ +FROM nginx +ARG message +RUN echo "

${message}

" > \ + /usr/share/nginx/html/index.html diff --git a/examples/ts-ecr-registry-image/index.ts b/examples/ts-ecr-registry-image/index.ts new file mode 100644 index 000000000..668433824 --- /dev/null +++ b/examples/ts-ecr-registry-image/index.ts @@ -0,0 +1,45 @@ +import * as pulumi from "@pulumi/pulumi"; +import * as awsx from "@pulumi/awsx"; +import * as docker from "@pulumi/docker"; + +const repository = new awsx.ecr.Repository("repository", { forceDelete: true }); +export const repositoryName = repository.repository.name; + +const config = new pulumi.Config(); +const message = config.require("message"); + +// build an image and keep it locally. You can replace this with any other local image you've already built or pulled. +const localImage = new docker.Image("local-image", { + build: { + context: "app", + args: { + message, + }, + }, + imageName: "my-awesome-image:test", + skipPush: true, +}); + +export const imageId = localImage.repoDigest; + +const preTaggedImage = new awsx.ecr.RegistryImage("pre-tagged-image", { + repositoryUrl: repository.url, + // if sourceImage has a tag, it will be used for pushing to the registry + sourceImage: localImage.imageName, + keepRemotely: true, +}); + +const latestImage = new awsx.ecr.RegistryImage("latest-image", { + repositoryUrl: repository.url, + // if sourceImage has no tag, it will be pushed with the latest tag + sourceImage: localImage.repoDigest, + keepRemotely: true, +}); + +const taggedImage = new awsx.ecr.RegistryImage("tagged-image", { + repositoryUrl: repository.url, + sourceImage: localImage.imageName, + // if a tag is provided, it will be used for pushing to the registry + tag: "v1.0.0", + keepRemotely: true, +}); \ No newline at end of file diff --git a/examples/ts-ecr-registry-image/package.json b/examples/ts-ecr-registry-image/package.json new file mode 100644 index 000000000..b0e401c4f --- /dev/null +++ b/examples/ts-ecr-registry-image/package.json @@ -0,0 +1,18 @@ +{ + "name": "ecr-registry-image", + "version": "0.0.1", + "license": "Apache-2.0", + "scripts": { + "build": "tsc" + }, + "dependencies": { + "@pulumi/pulumi": "^3.0.0", + "@pulumi/aws": "^6.0.0" + }, + "devDependencies": { + "@types/node": "^18.0.0" + }, + "peerDependencies": { + "@pulumi/awsx": "latest" + } +} diff --git a/examples/ts-ecr-registry-image/tsconfig.json b/examples/ts-ecr-registry-image/tsconfig.json new file mode 100644 index 000000000..ab65afa61 --- /dev/null +++ b/examples/ts-ecr-registry-image/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "strict": true, + "outDir": "bin", + "target": "es2016", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "experimentalDecorators": true, + "pretty": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.ts" + ] +} From 3ddbbdc0433c29554a2e677618e76e8de3abf953 Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Mon, 6 Jan 2025 19:52:31 +0100 Subject: [PATCH 06/17] Remove unused code --- awsx/ecr/image.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/awsx/ecr/image.ts b/awsx/ecr/image.ts index 746659f0e..a33fdd924 100644 --- a/awsx/ecr/image.ts +++ b/awsx/ecr/image.ts @@ -33,9 +33,6 @@ export function computeImageFromAsset( ) { const { repositoryUrl, registryId: inputRegistryId, imageTag, ...dockerInputs } = args ?? {}; - const url = new URL("https://" + repositoryUrl); // Add protocol to help it parse - const registryId = inputRegistryId ?? url.hostname.split(".")[0]; - pulumi.log.debug(`Building container image at '${JSON.stringify(dockerInputs)}'`, parent); const imageName = args.imageName From 5909e8521d2baa1e18baaa6ccfa861950efd3b0b Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Mon, 6 Jan 2025 19:58:57 +0100 Subject: [PATCH 07/17] Keep old behavior for ecr.Image --- awsx/ecr/auth.ts | 24 +++++++++++++++++------- awsx/ecr/image.ts | 5 ++++- awsx/ecr/registryImage.ts | 2 +- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/awsx/ecr/auth.ts b/awsx/ecr/auth.ts index 28ba9fee6..63df1d73d 100644 --- a/awsx/ecr/auth.ts +++ b/awsx/ecr/auth.ts @@ -15,6 +15,11 @@ import * as aws from "@pulumi/aws"; import * as pulumi from "@pulumi/pulumi"; +export interface CredentialArgs { + registryUrl: string; + registryId?: string; +} + export interface DockerCredentials { address: string; username: string; @@ -22,17 +27,22 @@ export interface DockerCredentials { } export function getDockerCredentials( - registryUrl: string, + args: CredentialArgs, opts: pulumi.InvokeOutputOptions, ): pulumi.Output { // add protocol to help parse the url - if (!registryUrl?.startsWith("https://")) { - registryUrl = "https://" + registryUrl; - } - // the registry id is the AWS account id. It's the first part of the hostname - const parsedUrl = new URL(registryUrl); - const registryId = parsedUrl.hostname.split(".")[0]; + let registryId: string; + if (args.registryId) { + registryId = args.registryId; + } else { + const registryUrl = args.registryUrl?.startsWith("https://") + ? args.registryUrl + : "https://" + args.registryUrl; + const parsedUrl = new URL(registryUrl); + // the registry id is the AWS account id. It's the first part of the hostname + registryId = parsedUrl.hostname.split(".")[0]; + } const ecrCredentials = aws.ecr.getCredentialsOutput({ registryId: registryId }, opts); diff --git a/awsx/ecr/image.ts b/awsx/ecr/image.ts index a33fdd924..bad88fd5d 100644 --- a/awsx/ecr/image.ts +++ b/awsx/ecr/image.ts @@ -48,7 +48,10 @@ export function computeImageFromAsset( // the unique image name we pushed to. The name will change if the image changes ensuring // the TaskDefinition get's replaced IFF the built image changes. - const registryCredentials = getDockerCredentials(repositoryUrl, { parent }); + const registryCredentials = getDockerCredentials( + { registryUrl: repositoryUrl, registryId: inputRegistryId }, + { parent }, + ); let cacheFrom: docker.types.input.CacheFromArgs[] = []; if (dockerInputs.cacheFrom !== undefined) { diff --git a/awsx/ecr/registryImage.ts b/awsx/ecr/registryImage.ts index 88fe733df..43b7d86be 100644 --- a/awsx/ecr/registryImage.ts +++ b/awsx/ecr/registryImage.ts @@ -27,7 +27,7 @@ export class RegistryImage extends schema.RegistryImage { const creds = pulumi .output(args.repositoryUrl) - .apply((url) => getDockerCredentials(url, { parent: this })); + .apply((url) => getDockerCredentials({ registryUrl: url }, { parent: this })); const provider = new docker.Provider(name, { registryAuth: [creds], }); From 7b82a054bb24adf51c506ef04941af5bf521a1af Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Tue, 7 Jan 2025 11:21:24 +0100 Subject: [PATCH 08/17] Default tag to 'latest' --- awsx/ecr/registryImage.test.ts | 34 ------------------- awsx/ecr/registryImage.ts | 20 ++--------- examples/ts-ecr-registry-image/index.ts | 21 ++++++------ schema.json | 4 +-- schemagen/pkg/gen/ecr.go | 6 ++-- sdk/dotnet/Ecr/RegistryImage.cs | 5 ++- sdk/go/awsx/ecr/registryImage.go | 10 +++--- .../pulumi/awsx/ecr/RegistryImageArgs.java | 20 +++++------ sdk/nodejs/ecr/registryImage.ts | 5 ++- sdk/python/pulumi_awsx/ecr/registry_image.py | 15 ++++---- 10 files changed, 39 insertions(+), 101 deletions(-) delete mode 100644 awsx/ecr/registryImage.test.ts diff --git a/awsx/ecr/registryImage.test.ts b/awsx/ecr/registryImage.test.ts deleted file mode 100644 index f5c194394..000000000 --- a/awsx/ecr/registryImage.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2016-2024, Pulumi Corporation. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { parseImageTag } from "./registryImage"; - -describe("parseImageTag", () => { - it("should return 'latest' for sha256 digest", () => { - const result = parseImageTag( - "sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - ); - expect(result).toEqual("latest"); - }); - - it("should return 'latest' if no tag is present", () => { - const result = parseImageTag("repository/image"); - expect(result).toEqual("latest"); - }); - - it("should return the tag if present", () => { - const result = parseImageTag("repository/image:tag"); - expect(result).toEqual("tag"); - }); -}); diff --git a/awsx/ecr/registryImage.ts b/awsx/ecr/registryImage.ts index 43b7d86be..a7a774660 100644 --- a/awsx/ecr/registryImage.ts +++ b/awsx/ecr/registryImage.ts @@ -39,8 +39,8 @@ export class RegistryImage extends schema.RegistryImage { { parent: this, provider }, ); const tagName = args.tag - ? pulumi.output(args.tag) - : pulumi.output(args.sourceImage).apply((image) => parseImageTag(image)); + ? args.tag + : "latest"; const tag = new docker.Tag( name, { @@ -65,19 +65,3 @@ export class RegistryImage extends schema.RegistryImage { ); } } - -export function parseImageTag(str: string): string { - // Check if the string is a sha256 digest (starts with "sha256:" followed by 64 hex characters) - if (/^sha256:[a-f0-9]{64}$/i.test(str)) { - return "latest"; - } - - const parts = str.split(":"); - if (parts.length < 2) { - // if there is no tag, use the latest tag - return "latest"; - } - - // the tag is the last part of the image, tags are not allowed to contain colons so we can just take the last part - return parts[parts.length - 1]; -} diff --git a/examples/ts-ecr-registry-image/index.ts b/examples/ts-ecr-registry-image/index.ts index 668433824..1e98b1415 100644 --- a/examples/ts-ecr-registry-image/index.ts +++ b/examples/ts-ecr-registry-image/index.ts @@ -22,24 +22,25 @@ const localImage = new docker.Image("local-image", { export const imageId = localImage.repoDigest; -const preTaggedImage = new awsx.ecr.RegistryImage("pre-tagged-image", { +const latestImage = new awsx.ecr.RegistryImage("image-name", { repositoryUrl: repository.url, - // if sourceImage has a tag, it will be used for pushing to the registry + // if no tag is provided, the image will be pushed with the `latest` tag sourceImage: localImage.imageName, keepRemotely: true, }); -const latestImage = new awsx.ecr.RegistryImage("latest-image", { - repositoryUrl: repository.url, - // if sourceImage has no tag, it will be pushed with the latest tag - sourceImage: localImage.repoDigest, - keepRemotely: true, -}); - const taggedImage = new awsx.ecr.RegistryImage("tagged-image", { repositoryUrl: repository.url, sourceImage: localImage.imageName, // if a tag is provided, it will be used for pushing to the registry tag: "v1.0.0", keepRemotely: true, -}); \ No newline at end of file +}); + +const digestImage = new awsx.ecr.RegistryImage("digest", { + repositoryUrl: repository.url, + // you can also specify a digest instead of an image name + sourceImage: localImage.repoDigest, + tag: "test", + keepRemotely: true, +}); diff --git a/schema.json b/schema.json index 128ae5315..fc8f09af2 100644 --- a/schema.json +++ b/schema.json @@ -2252,11 +2252,11 @@ }, "sourceImage": { "type": "string", - "description": "The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default.\nYou can override the tag by using the `tag` input property." + "description": "The source image to push to the registry." }, "tag": { "type": "string", - "description": "The tag to use for the pushed image. If not provided, the tag of the source image is used." + "description": "The tag to use for the pushed image. If not provided, it defaults to `latest`." }, "triggers": { "type": "object", diff --git a/schemagen/pkg/gen/ecr.go b/schemagen/pkg/gen/ecr.go index 8f7461f1a..b2163b61c 100644 --- a/schemagen/pkg/gen/ecr.go +++ b/schemagen/pkg/gen/ecr.go @@ -324,15 +324,13 @@ func registryImage(dockerSpec schema.PackageSpec) schema.ResourceSpec { delete(inputProperties, "name") inputProperties["sourceImage"] = schema.PropertySpec{ - Description: "The source image to push to the registry. The image is pushed with its existing tag by default. " + - "If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default.\n" + - "You can override the tag by using the `tag` input property.", + Description: "The source image to push to the registry.", TypeSpec: schema.TypeSpec{ Type: "string", }, } inputProperties["tag"] = schema.PropertySpec{ - Description: "The tag to use for the pushed image. If not provided, the tag of the source image is used.", + Description: "The tag to use for the pushed image. If not provided, it defaults to `latest`.", TypeSpec: schema.TypeSpec{ Type: "string", }, diff --git a/sdk/dotnet/Ecr/RegistryImage.cs b/sdk/dotnet/Ecr/RegistryImage.cs index ce0e19065..2fc95fe23 100644 --- a/sdk/dotnet/Ecr/RegistryImage.cs +++ b/sdk/dotnet/Ecr/RegistryImage.cs @@ -91,14 +91,13 @@ public sealed class RegistryImageArgs : global::Pulumi.ResourceArgs public Input RepositoryUrl { get; set; } = null!; /// - /// The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. - /// You can override the tag by using the `tag` input property. + /// The source image to push to the registry. /// [Input("sourceImage", required: true)] public Input SourceImage { get; set; } = null!; /// - /// The tag to use for the pushed image. If not provided, the tag of the source image is used. + /// The tag to use for the pushed image. If not provided, it defaults to `latest`. /// [Input("tag")] public Input? Tag { get; set; } diff --git a/sdk/go/awsx/ecr/registryImage.go b/sdk/go/awsx/ecr/registryImage.go index cf50cfbbf..c39d953c7 100644 --- a/sdk/go/awsx/ecr/registryImage.go +++ b/sdk/go/awsx/ecr/registryImage.go @@ -85,10 +85,9 @@ type registryImageArgs struct { KeepRemotely *bool `pulumi:"keepRemotely"` // Url of the ECR repository. RepositoryUrl string `pulumi:"repositoryUrl"` - // The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. - // You can override the tag by using the `tag` input property. + // The source image to push to the registry. SourceImage string `pulumi:"sourceImage"` - // The tag to use for the pushed image. If not provided, the tag of the source image is used. + // The tag to use for the pushed image. If not provided, it defaults to `latest`. Tag *string `pulumi:"tag"` // A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image Triggers map[string]string `pulumi:"triggers"` @@ -102,10 +101,9 @@ type RegistryImageArgs struct { KeepRemotely pulumi.BoolPtrInput // Url of the ECR repository. RepositoryUrl pulumi.StringInput - // The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. - // You can override the tag by using the `tag` input property. + // The source image to push to the registry. SourceImage pulumi.StringInput - // The tag to use for the pushed image. If not provided, the tag of the source image is used. + // The tag to use for the pushed image. If not provided, it defaults to `latest`. Tag pulumi.StringPtrInput // A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image Triggers pulumi.StringMapInput diff --git a/sdk/java/src/main/java/com/pulumi/awsx/ecr/RegistryImageArgs.java b/sdk/java/src/main/java/com/pulumi/awsx/ecr/RegistryImageArgs.java index 6a57d9804..ec5b0493e 100644 --- a/sdk/java/src/main/java/com/pulumi/awsx/ecr/RegistryImageArgs.java +++ b/sdk/java/src/main/java/com/pulumi/awsx/ecr/RegistryImageArgs.java @@ -63,16 +63,14 @@ public Output repositoryUrl() { } /** - * The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. - * You can override the tag by using the `tag` input property. + * The source image to push to the registry. * */ @Import(name="sourceImage", required=true) private Output sourceImage; /** - * @return The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. - * You can override the tag by using the `tag` input property. + * @return The source image to push to the registry. * */ public Output sourceImage() { @@ -80,14 +78,14 @@ public Output sourceImage() { } /** - * The tag to use for the pushed image. If not provided, the tag of the source image is used. + * The tag to use for the pushed image. If not provided, it defaults to `latest`. * */ @Import(name="tag") private @Nullable Output tag; /** - * @return The tag to use for the pushed image. If not provided, the tag of the source image is used. + * @return The tag to use for the pushed image. If not provided, it defaults to `latest`. * */ public Optional> tag() { @@ -202,8 +200,7 @@ public Builder repositoryUrl(String repositoryUrl) { } /** - * @param sourceImage The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. - * You can override the tag by using the `tag` input property. + * @param sourceImage The source image to push to the registry. * * @return builder * @@ -214,8 +211,7 @@ public Builder sourceImage(Output sourceImage) { } /** - * @param sourceImage The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. - * You can override the tag by using the `tag` input property. + * @param sourceImage The source image to push to the registry. * * @return builder * @@ -225,7 +221,7 @@ public Builder sourceImage(String sourceImage) { } /** - * @param tag The tag to use for the pushed image. If not provided, the tag of the source image is used. + * @param tag The tag to use for the pushed image. If not provided, it defaults to `latest`. * * @return builder * @@ -236,7 +232,7 @@ public Builder tag(@Nullable Output tag) { } /** - * @param tag The tag to use for the pushed image. If not provided, the tag of the source image is used. + * @param tag The tag to use for the pushed image. If not provided, it defaults to `latest`. * * @return builder * diff --git a/sdk/nodejs/ecr/registryImage.ts b/sdk/nodejs/ecr/registryImage.ts index 0a32ffd94..1a9b8ead5 100644 --- a/sdk/nodejs/ecr/registryImage.ts +++ b/sdk/nodejs/ecr/registryImage.ts @@ -93,12 +93,11 @@ export interface RegistryImageArgs { */ repositoryUrl: pulumi.Input; /** - * The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. - * You can override the tag by using the `tag` input property. + * The source image to push to the registry. */ sourceImage: pulumi.Input; /** - * The tag to use for the pushed image. If not provided, the tag of the source image is used. + * The tag to use for the pushed image. If not provided, it defaults to `latest`. */ tag?: pulumi.Input; /** diff --git a/sdk/python/pulumi_awsx/ecr/registry_image.py b/sdk/python/pulumi_awsx/ecr/registry_image.py index 372dcc1c2..7149cc622 100644 --- a/sdk/python/pulumi_awsx/ecr/registry_image.py +++ b/sdk/python/pulumi_awsx/ecr/registry_image.py @@ -29,11 +29,10 @@ def __init__(__self__, *, """ The set of arguments for constructing a RegistryImage resource. :param pulumi.Input[str] repository_url: Url of the ECR repository. - :param pulumi.Input[str] source_image: The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. - You can override the tag by using the `tag` input property. + :param pulumi.Input[str] source_image: The source image to push to the registry. :param pulumi.Input[bool] insecure_skip_verify: If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false` :param pulumi.Input[bool] keep_remotely: If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` - :param pulumi.Input[str] tag: The tag to use for the pushed image. If not provided, the tag of the source image is used. + :param pulumi.Input[str] tag: The tag to use for the pushed image. If not provided, it defaults to `latest`. :param pulumi.Input[Mapping[str, pulumi.Input[str]]] triggers: A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image """ pulumi.set(__self__, "repository_url", repository_url) @@ -63,8 +62,7 @@ def repository_url(self, value: pulumi.Input[str]): @pulumi.getter(name="sourceImage") def source_image(self) -> pulumi.Input[str]: """ - The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. - You can override the tag by using the `tag` input property. + The source image to push to the registry. """ return pulumi.get(self, "source_image") @@ -100,7 +98,7 @@ def keep_remotely(self, value: Optional[pulumi.Input[bool]]): @pulumi.getter def tag(self) -> Optional[pulumi.Input[str]]: """ - The tag to use for the pushed image. If not provided, the tag of the source image is used. + The tag to use for the pushed image. If not provided, it defaults to `latest`. """ return pulumi.get(self, "tag") @@ -154,9 +152,8 @@ def __init__(__self__, :param pulumi.Input[bool] insecure_skip_verify: If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false` :param pulumi.Input[bool] keep_remotely: If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` :param pulumi.Input[str] repository_url: Url of the ECR repository. - :param pulumi.Input[str] source_image: The source image to push to the registry. The image is pushed with its existing tag by default. If the source specifies an image ID without a tag, the pushed image uses the `latest` tag as the default. - You can override the tag by using the `tag` input property. - :param pulumi.Input[str] tag: The tag to use for the pushed image. If not provided, the tag of the source image is used. + :param pulumi.Input[str] source_image: The source image to push to the registry. + :param pulumi.Input[str] tag: The tag to use for the pushed image. If not provided, it defaults to `latest`. :param pulumi.Input[Mapping[str, pulumi.Input[str]]] triggers: A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image """ ... From 68f76e49ba4d2dad22bafdf502460d88d9b2ea1d Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Tue, 7 Jan 2025 14:38:14 +0100 Subject: [PATCH 09/17] fmt --- awsx/ecr/registryImage.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/awsx/ecr/registryImage.ts b/awsx/ecr/registryImage.ts index a7a774660..42db91382 100644 --- a/awsx/ecr/registryImage.ts +++ b/awsx/ecr/registryImage.ts @@ -38,9 +38,7 @@ export class RegistryImage extends schema.RegistryImage { { name: args.sourceImage }, { parent: this, provider }, ); - const tagName = args.tag - ? args.tag - : "latest"; + const tagName = args.tag ? args.tag : "latest"; const tag = new docker.Tag( name, { From c351b0ba6415f45314e6df77a0a56485c5f1eded Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Wed, 8 Jan 2025 10:30:02 +0100 Subject: [PATCH 10/17] Add docstrings --- awsx/ecr/auth.ts | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/awsx/ecr/auth.ts b/awsx/ecr/auth.ts index 63df1d73d..82bcedac0 100644 --- a/awsx/ecr/auth.ts +++ b/awsx/ecr/auth.ts @@ -15,27 +15,59 @@ import * as aws from "@pulumi/aws"; import * as pulumi from "@pulumi/pulumi"; +/** + * Arguments for fetching ECR registry credentials. + */ export interface CredentialArgs { + /** + * The URL of the ECR registry to get credentials for. + * Can be provided with or without the https:// protocol prefix. + */ registryUrl: string; + + /** + * Optional registry ID (AWS account ID) to get credentials for. + * If not provided, will be parsed from the registry URL. + */ registryId?: string; } +/** + * Docker registry credentials for authenticating with an ECR registry. + */ export interface DockerCredentials { + /** + * The address of the ECR registry. + */ address: string; + + /** + * The username to authenticate with. For ECR this is typically "AWS". + */ username: string; + + /** + * The password to authenticate with. For ECR this is a temporary token. + */ password: string; } +/** + * Fetches Docker registry credentials for authenticating with an ECR registry. + * + * @param args Arguments for fetching ECR registry credentials + * @param opts InvokeOutputOptions to use for the credential lookup + * @returns Docker registry credentials including address, username and password + */ export function getDockerCredentials( args: CredentialArgs, opts: pulumi.InvokeOutputOptions, ): pulumi.Output { - // add protocol to help parse the url - let registryId: string; if (args.registryId) { registryId = args.registryId; } else { + // add protocol to help parse the url const registryUrl = args.registryUrl?.startsWith("https://") ? args.registryUrl : "https://" + args.registryUrl; From 8e5f0e516416d0592ceec509c4387e77f355d9ad Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Mon, 13 Jan 2025 16:40:41 +0100 Subject: [PATCH 11/17] Update docker to v4.6.0 --- awsx/package.json | 4 ++-- awsx/yarn.lock | 16 ++++++++-------- examples/examples_nodejs_test.go | 5 +++++ schema.json | 8 ++++---- sdk/java/build.gradle | 2 +- sdk/nodejs/package.json | 2 +- sdk/python/pyproject.toml | 2 +- 7 files changed, 22 insertions(+), 17 deletions(-) diff --git a/awsx/package.json b/awsx/package.json index a6b676cbb..d221e9539 100644 --- a/awsx/package.json +++ b/awsx/package.json @@ -25,11 +25,11 @@ "//": "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.8", + "@pulumi/docker": "4.6.0", "@pulumi/docker-build": "0.0.8", "@pulumi/pulumi": "3.144.1", "@types/aws-lambda": "^8.10.23", - "docker-classic": "npm:@pulumi/docker@4.5.8", + "docker-classic": "npm:@pulumi/docker@4.6.0", "ip-address": "^8.1.0", "mime": "^3.0.0", "netmask": "^2.0.2" diff --git a/awsx/yarn.lock b/awsx/yarn.lock index 82118b799..61e4d73e1 100644 --- a/awsx/yarn.lock +++ b/awsx/yarn.lock @@ -1678,10 +1678,10 @@ dependencies: "@pulumi/pulumi" "^3.136.0" -"@pulumi/docker@4.5.8": - version "4.5.8" - resolved "https://registry.yarnpkg.com/@pulumi/docker/-/docker-4.5.8.tgz#55cefdebcee55eedf0674352ab7f6b8101420c30" - integrity sha512-h5ZfsXTt5GaqenOmleNAJT/zXLErYXYMftgFNbTS4Z1n1gQXwBewxZ/p7nEqKZkh0JjZZuoDlRN1+lkosM5W6w== +"@pulumi/docker@4.6.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@pulumi/docker/-/docker-4.6.0.tgz#127a0c69a06b47a9418dce12cc70a181ff680284" + integrity sha512-9XBQtKGYQykJgXS3nWXk/mp7vSZ4kpwGJcp8yPMwvlUDiIDS4lLPp7ieOJl/qVETXkW8EtQ8qxlJ/n8kTBgiBA== dependencies: "@pulumi/pulumi" "^3.142.0" semver "^5.4.0" @@ -2689,10 +2689,10 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -"docker-classic@npm:@pulumi/docker@4.5.8": - version "4.5.8" - resolved "https://registry.yarnpkg.com/@pulumi/docker/-/docker-4.5.8.tgz#55cefdebcee55eedf0674352ab7f6b8101420c30" - integrity sha512-h5ZfsXTt5GaqenOmleNAJT/zXLErYXYMftgFNbTS4Z1n1gQXwBewxZ/p7nEqKZkh0JjZZuoDlRN1+lkosM5W6w== +"docker-classic@npm:@pulumi/docker@4.6.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@pulumi/docker/-/docker-4.6.0.tgz#127a0c69a06b47a9418dce12cc70a181ff680284" + integrity sha512-9XBQtKGYQykJgXS3nWXk/mp7vSZ4kpwGJcp8yPMwvlUDiIDS4lLPp7ieOJl/qVETXkW8EtQ8qxlJ/n8kTBgiBA== dependencies: "@pulumi/pulumi" "^3.142.0" semver "^5.4.0" diff --git a/examples/examples_nodejs_test.go b/examples/examples_nodejs_test.go index fb801edf0..7d62f0b22 100644 --- a/examples/examples_nodejs_test.go +++ b/examples/examples_nodejs_test.go @@ -32,6 +32,7 @@ import ( "github.com/pulumi/providertest/pulumitest/optnewstack" "github.com/pulumi/providertest/pulumitest/opttest" "github.com/pulumi/pulumi/pkg/v3/testing/integration" + "github.com/pulumi/pulumi/sdk/v3/go/auto/optpreview" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -510,6 +511,10 @@ func TestEcrRegistryImage(t *testing.T) { require.Contains(t, newImage.ImageTags, "latest", "the new image should have the latest tag") require.Empty(t, oldImage.ImageTags, "old image should have no tags after the new image was pushed") + + t.Log("Re-running `pulumi preview` to verify that no changes are detected") + previewResult = pt.Preview(t, optpreview.ExpectNoChanges()) + t.Logf("Preview:\n%s", previewResult.StdOut) } func getEcrImageDetails(t *testing.T, client *ecr.Client, repositoryName string, expectedImages int) (*ecr.DescribeImagesOutput, error) { diff --git a/schema.json b/schema.json index fc8f09af2..6916d836f 100644 --- a/schema.json +++ b/schema.json @@ -37,7 +37,7 @@ "java": { "dependencies": { "com.pulumi:aws": "6.65.0", - "com.pulumi:docker": "4.5.8", + "com.pulumi:docker": "4.6.0", "com.pulumi:docker-build": "0.0.8" } }, @@ -45,7 +45,7 @@ "dependencies": { "@aws-sdk/client-ecs": "^3.405.0", "@pulumi/aws": "^6.65.0", - "@pulumi/docker": "^4.5.8", + "@pulumi/docker": "^4.6.0", "@pulumi/docker-build": "^0.0.8", "@types/aws-lambda": "^8.10.23", "docker-classic": "npm:@pulumi/docker@3.6.1", @@ -66,7 +66,7 @@ "readme": "Pulumi Amazon Web Services (AWS) AWSX Components.", "requires": { "pulumi-aws": "\u003e=6.0.4,\u003c7.0.0", - "pulumi-docker": "\u003e=4.5.8,\u003c5.0.0", + "pulumi-docker": "\u003e=4.6.0,\u003c5.0.0", "pulumi-docker-build": "\u003e=0.0.8,\u003c1.0.0" }, "respectSchemaVersion": true, @@ -2229,7 +2229,7 @@ "description": "Manages the lifecycle of a docker image in a registry. You can upload images to a registry (= `docker push`) and also delete them again. In contrast to [`awsx.ecr.Image`](/registry/packages/awsx/api-docs/ecr/image/), this resource does not require to build the image, but can be used to push an existing image to an ECR repository. The image will be pushed whenever the source image changes or is updated.\n\n{{% examples %}}\n## Example Usage\n{{% example %}}\n### Pushing an image to an ECR repository\n\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as awsx from \"@pulumi/awsx\";\n\nconst repository = new awsx.ecr.Repository(\"repository\", { forceDelete: true });\n\nconst preTaggedImage = new awsx.ecr.RegistryImage(\"registry-image\", {\n repositoryUrl: repository.url,\n sourceImage: \"my-awesome-image:v1.0.0\",\n});\n```\n```python\nimport pulumi\nimport pulumi_awsx as awsx\n\nrepository = awsx.ecr.Repository(\"repository\", force_delete=True)\n\nregistry_image = awsx.ecr.RegistryImage(\"registry_image\",\n repository_url=repository.url,\n source_image=\"my-awesome-image:v1.0.0\")\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ecr\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\trepository, err := ecr.NewRepository(ctx, \"repository\", \u0026ecr.RepositoryArgs{\n\t\t\tForceDelete: pulumi.Bool(true),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tregistryImage, err := ecr.NewRegistryImage(ctx, \"registryImage\", \u0026ecr.RegistryImageArgs{\n\t\t\tRepositoryUrl: repository.Url,\n\t\t\tSourceImage: pulumi.String(\"my-awesome-image:v1.0.0\"),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n}\n```\n```csharp\nusing Pulumi;\nusing Pulumi.Awsx.Ecr;\n\nreturn await Pulumi.Deployment.RunAsync(() =\u003e\n{\n var repository = new Repository(\"repository\", new RepositoryArgs\n {\n ForceDelete = true,\n });\n\n var registryImage = new RegistryImage(\"registryImage\", new RegistryImageArgs\n {\n RepositoryUrl = repository.Url,\n SourceImage = \"my-awesome-image:v1.0.0\",\n });\n\n return new Dictionary\u003cstring, object?\u003e{};\n});\n```\n```yaml\nname: example\nruntime: yaml\nresources:\n repository:\n type: awsx:ecr:Repository\n properties:\n forceDelete: true\n registryImage:\n type: awsx:ecr:RegistryImage\n properties:\n repositoryUrl: ${repository.url}\n sourceImage: \"my-awesome-image:v1.0.0\"\n```\n```java\nimport com.pulumi.Pulumi;\nimport com.pulumi.awsx.ecr.Repository;\nimport com.pulumi.awsx.ecr.RepositoryArgs;\nimport com.pulumi.awsx.ecr.RegistryImage;\nimport com.pulumi.awsx.ecr.RegistryImageArgs;\n\npublic class Main {\n public static void main(String[] args) {\n Pulumi.run(ctx -\u003e {\n // Create an ECR repository with force delete enabled\n var repository = new Repository(\"repository\", RepositoryArgs.builder()\n .forceDelete(true)\n .build());\n\n // Create a RegistryImage based on the ECR repository URL and source image\n var registryImage = new RegistryImage(\"registryImage\", RegistryImageArgs.builder()\n .repositoryUrl(repository.url())\n .sourceImage(\"my-awesome-image:v1.0.0\")\n .build());\n });\n }\n}\n```\n{{% /example %}}\n{{% /examples %}}\n", "properties": { "image": { - "$ref": "/docker/v4.5.8/schema.json#/resources/docker:index%2fregistryImage:RegistryImage", + "$ref": "/docker/v4.6.0/schema.json#/resources/docker:index%2fregistryImage:RegistryImage", "description": "The underlying RegistryImage resource." } }, diff --git a/sdk/java/build.gradle b/sdk/java/build.gradle index 36e9cd65e..65fd90023 100644 --- a/sdk/java/build.gradle +++ b/sdk/java/build.gradle @@ -44,7 +44,7 @@ dependencies { implementation("com.google.code.findbugs:jsr305:3.0.2") implementation("com.google.code.gson:gson:2.8.9") implementation("com.pulumi:aws:6.65.0") - implementation("com.pulumi:docker:4.5.8") + implementation("com.pulumi:docker:4.6.0") implementation("com.pulumi:docker-build:0.0.8") implementation("com.pulumi:pulumi:0.9.7") } diff --git a/sdk/nodejs/package.json b/sdk/nodejs/package.json index 8ceb3cf46..3db43f18e 100644 --- a/sdk/nodejs/package.json +++ b/sdk/nodejs/package.json @@ -17,7 +17,7 @@ "dependencies": { "@aws-sdk/client-ecs": "^3.405.0", "@pulumi/aws": "^6.65.0", - "@pulumi/docker": "^4.5.8", + "@pulumi/docker": "^4.6.0", "@pulumi/docker-build": "^0.0.8", "@pulumi/pulumi": "^3.142.0", "@types/aws-lambda": "^8.10.23", diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index c929f5fdc..dfa7d114f 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "pulumi_awsx" description = "Pulumi Amazon Web Services (AWS) AWSX Components." - dependencies = ["parver>=0.2.1", "pulumi>=3.142.0,<4.0.0", "pulumi-aws>=6.0.4,<7.0.0", "pulumi-docker>=4.5.8,<5.0.0", "pulumi-docker-build>=0.0.8,<1.0.0", "semver>=2.8.1", "typing-extensions>=4.11; python_version < \"3.11\""] + dependencies = ["parver>=0.2.1", "pulumi>=3.142.0,<4.0.0", "pulumi-aws>=6.0.4,<7.0.0", "pulumi-docker>=4.6.0,<5.0.0", "pulumi-docker-build>=0.0.8,<1.0.0", "semver>=2.8.1", "typing-extensions>=4.11; python_version < \"3.11\""] keywords = ["pulumi", "aws", "awsx", "kind/component", "category/cloud"] readme = "README.md" requires-python = ">=3.9" From a2f87069a3c72bc290dba246daccbac5e4a7774e Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Tue, 14 Jan 2025 13:44:48 +0100 Subject: [PATCH 12/17] Handle refs to docker in generate-provider-types.ts --- awsx/schema-types.ts | 3 ++- awsx/scripts/generate-provider-types.ts | 5 +++++ examples/examples_nodejs_test.go | 8 ++++++++ examples/ts-ecr-registry-image/index.ts | 10 ++++++---- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/awsx/schema-types.ts b/awsx/schema-types.ts index 0b331d7d3..4ee53dd10 100644 --- a/awsx/schema-types.ts +++ b/awsx/schema-types.ts @@ -24,6 +24,7 @@ export type Functions = { "awsx:ec2:getDefaultVpc": (inputs: getDefaultVpcInputs) => Promise; }; import * as aws from "@pulumi/aws"; +import * as docker from "@pulumi/docker"; export abstract class Trail extends (pulumi.ComponentResource) { public bucket?: aws.s3.Bucket | pulumi.Output; public logGroup?: aws.cloudwatch.LogGroup | pulumi.Output; @@ -120,7 +121,7 @@ export interface ImageArgs { readonly target?: pulumi.Input; } export abstract class RegistryImage extends (pulumi.ComponentResource) { - public image!: unknown | pulumi.Output; + public image!: docker.RegistryImage | pulumi.Output; constructor(name: string, args: pulumi.Inputs, opts: pulumi.ComponentResourceOptions = {}) { super("awsx:ecr:RegistryImage", name, opts.urn ? { image: undefined } : { name, args, opts }, opts); } diff --git a/awsx/scripts/generate-provider-types.ts b/awsx/scripts/generate-provider-types.ts index 8e1a66b4a..90f021a52 100644 --- a/awsx/scripts/generate-provider-types.ts +++ b/awsx/scripts/generate-provider-types.ts @@ -32,6 +32,7 @@ const externalRefs = (() => { }; }; addRef("aws"); + addRef("docker"); return externalRefs; })(); @@ -81,6 +82,10 @@ const resolveRef = (ref: unknown, direction: Direction): ts.TypeNode => { const resourceName = typeParts[2]; const path = decodeURIComponent(typeParts[1]).split("/"); path.pop(); // Last section is same as the resource name + if (path.length === 1 && path[0] === "index") { + // this is the default module name, so we don't need to include it + path.pop(); + } return ts.factory.createTypeReferenceNode( [externalRef.name, ...path, resourceName].join("."), ); diff --git a/examples/examples_nodejs_test.go b/examples/examples_nodejs_test.go index 9fe870a80..09c83e692 100644 --- a/examples/examples_nodejs_test.go +++ b/examples/examples_nodejs_test.go @@ -462,6 +462,10 @@ func TestEcrRegistryImage(t *testing.T) { upResult := pt.Up(t) t.Logf("Up:\n%s", upResult.StdOut) + require.Contains(t, upResult.Outputs, "latestImageDigest", "latestImageDigest should be in the outputs") + latestImageDigest := upResult.Outputs["latestImageDigest"].Value.(string) + require.NotEmpty(t, latestImageDigest, "latestImageDigest should not be empty") + require.Contains(t, upResult.Outputs, "repositoryName", "repositoryName should be in the outputs") repoName := upResult.Outputs["repositoryName"].Value.(string) require.NotEmpty(t, repoName, "repositoryName should not be empty") @@ -490,6 +494,10 @@ func TestEcrRegistryImage(t *testing.T) { upResult = pt.Up(t) t.Logf("Up:\n%s", upResult.StdOut) + require.Contains(t, upResult.Outputs, "latestImageDigest", "latestImageDigest should be in the outputs") + latestImageDigest = upResult.Outputs["latestImageDigest"].Value.(string) + require.NotEmpty(t, latestImageDigest, "latestImageDigest should not be empty") + describeImagesOutput, err = getEcrImageDetails(t, client, repoName, 2) require.NoError(t, err, "failed to describe images") require.NotEmpty(t, describeImagesOutput.ImageDetails, "image details should not be empty") diff --git a/examples/ts-ecr-registry-image/index.ts b/examples/ts-ecr-registry-image/index.ts index 1e98b1415..581b14916 100644 --- a/examples/ts-ecr-registry-image/index.ts +++ b/examples/ts-ecr-registry-image/index.ts @@ -11,10 +11,10 @@ const message = config.require("message"); // build an image and keep it locally. You can replace this with any other local image you've already built or pulled. const localImage = new docker.Image("local-image", { build: { - context: "app", - args: { - message, - }, + context: "app", + args: { + message, + }, }, imageName: "my-awesome-image:test", skipPush: true, @@ -29,6 +29,8 @@ const latestImage = new awsx.ecr.RegistryImage("image-name", { keepRemotely: true, }); +export const latestImageDigest = latestImage.image.sha256Digest; + const taggedImage = new awsx.ecr.RegistryImage("tagged-image", { repositoryUrl: repository.url, sourceImage: localImage.imageName, From 9a994816588a47be1872245b3434602176d1fbac Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Tue, 14 Jan 2025 15:04:47 +0100 Subject: [PATCH 13/17] Add auth error handling --- awsx/ecr/auth.test.ts | 88 +++++++++++++++++++++++++++++++++++++++ awsx/ecr/auth.ts | 32 ++++++++++---- awsx/ecr/image.ts | 2 +- awsx/ecr/registryImage.ts | 2 +- 4 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 awsx/ecr/auth.test.ts diff --git a/awsx/ecr/auth.test.ts b/awsx/ecr/auth.test.ts new file mode 100644 index 000000000..7809e2ef2 --- /dev/null +++ b/awsx/ecr/auth.test.ts @@ -0,0 +1,88 @@ +// Copyright 2016-2025, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as pulumi from "@pulumi/pulumi"; + +pulumi.runtime.setMocks( + { + newResource: function (args: pulumi.runtime.MockResourceArgs): { id: string; state: any } { + return { + id: args.inputs.name + "_id", + state: args.inputs, + }; + }, + call: function ( + args: pulumi.runtime.MockCallArgs, + ): pulumi.runtime.MockCallResult | Promise { + return { + authorizationToken: Buffer.from("AWS:password").toString("base64"), + proxyEndpoint: `https://${args.inputs.registryId}.dkr.ecr.us-west-2.amazonaws.com`, + }; + }, + }, + "project", + "stack", + false, // Sets the flag `dryRun`, which indicates if pulumi is running in preview mode. +); + +describe("getDockerCredentials", () => { + let auth: typeof import("./auth"); + + beforeAll(async function () { + auth = await import("./auth"); + }); + + it("should return Docker credentials when valid repositoryUrl is provided", async () => { + const args = { repositoryUrl: "https://123456789012.dkr.ecr.us-west-2.amazonaws.com" }; + const opts = {}; + + const result = await promisify(auth.getDockerCredentials(args, opts)); + + expect(result).toEqual({ + address: "https://123456789012.dkr.ecr.us-west-2.amazonaws.com", + username: "AWS", + password: "password", + }); + }); + + it("should use registryId if provided", async () => { + const args = { + repositoryUrl: "123456789012.dkr.ecr.us-west-2.amazonaws.com", + registryId: "987654321098", + }; + const opts = {}; + + const result = await promisify(auth.getDockerCredentials(args, opts)); + + expect(result).toEqual({ + address: `https://${args.registryId}.dkr.ecr.us-west-2.amazonaws.com`, + username: "AWS", + password: "password", + }); + }); + + it("should throw an error if the repositoryUrl is not a valid URL", async () => { + const args = { repositoryUrl: "foo:bar" }; + const opts = {}; + + expect(() => { + auth.getDockerCredentials(args, opts); + }).toThrow("Repository URL is not a valid URL."); + }); +}); + +function promisify(output: pulumi.Output | undefined): Promise { + expect(output).toBeDefined(); + return new Promise((resolve) => output!.apply(resolve)); +} diff --git a/awsx/ecr/auth.ts b/awsx/ecr/auth.ts index 82bcedac0..6096ff7fb 100644 --- a/awsx/ecr/auth.ts +++ b/awsx/ecr/auth.ts @@ -1,4 +1,4 @@ -// Copyright 2016-2024, Pulumi Corporation. +// Copyright 2016-2025, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ export interface CredentialArgs { * The URL of the ECR registry to get credentials for. * Can be provided with or without the https:// protocol prefix. */ - registryUrl: string; + repositoryUrl: string; /** * Optional registry ID (AWS account ID) to get credentials for. @@ -68,12 +68,30 @@ export function getDockerCredentials( registryId = args.registryId; } else { // add protocol to help parse the url - const registryUrl = args.registryUrl?.startsWith("https://") - ? args.registryUrl - : "https://" + args.registryUrl; - const parsedUrl = new URL(registryUrl); + const repositoryUrl = args.repositoryUrl?.startsWith("https://") + ? args.repositoryUrl + : "https://" + args.repositoryUrl; + + let parsedUrl: URL; + try { + parsedUrl = new URL(repositoryUrl); + } catch (e) { + throw new pulumi.InputPropertyError({ + reason: `Repository URL is not a valid URL.`, + propertyPath: "repositoryUrl", + }); + } + + const hostnameParts = parsedUrl.hostname.split("."); + if (hostnameParts.length < 1) { + throw new pulumi.InputPropertyError({ + reason: `Could not parse registry ID from Repository URL. It should be in the format of .dkr.ecr..amazonaws.com`, + propertyPath: "repositoryUrl", + }); + } + // the registry id is the AWS account id. It's the first part of the hostname - registryId = parsedUrl.hostname.split(".")[0]; + registryId = hostnameParts[0]; } const ecrCredentials = aws.ecr.getCredentialsOutput({ registryId: registryId }, opts); diff --git a/awsx/ecr/image.ts b/awsx/ecr/image.ts index 06a3a3e4f..4adb421b8 100644 --- a/awsx/ecr/image.ts +++ b/awsx/ecr/image.ts @@ -49,7 +49,7 @@ export function computeImageFromAsset( // the TaskDefinition get's replaced IFF the built image changes. const registryCredentials = getDockerCredentials( - { registryUrl: repositoryUrl, registryId: inputRegistryId }, + { repositoryUrl: repositoryUrl, registryId: inputRegistryId }, { parent }, ); diff --git a/awsx/ecr/registryImage.ts b/awsx/ecr/registryImage.ts index 42db91382..789235722 100644 --- a/awsx/ecr/registryImage.ts +++ b/awsx/ecr/registryImage.ts @@ -27,7 +27,7 @@ export class RegistryImage extends schema.RegistryImage { const creds = pulumi .output(args.repositoryUrl) - .apply((url) => getDockerCredentials({ registryUrl: url }, { parent: this })); + .apply((url) => getDockerCredentials({ repositoryUrl: url }, { parent: this })); const provider = new docker.Provider(name, { registryAuth: [creds], }); From 64bcff7345d21490a0aa55e5e197058a771d2c48 Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Tue, 14 Jan 2025 16:38:26 +0100 Subject: [PATCH 14/17] Add docker dep to example --- examples/ts-ecr-registry-image/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/ts-ecr-registry-image/package.json b/examples/ts-ecr-registry-image/package.json index b0e401c4f..d74cb6c2a 100644 --- a/examples/ts-ecr-registry-image/package.json +++ b/examples/ts-ecr-registry-image/package.json @@ -7,7 +7,8 @@ }, "dependencies": { "@pulumi/pulumi": "^3.0.0", - "@pulumi/aws": "^6.0.0" + "@pulumi/aws": "^6.0.0", + "@pulumi/docker": "^4.6.0" }, "devDependencies": { "@types/node": "^18.0.0" From 30b16add7c810f79479c0122b6346e02a269010e Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Wed, 15 Jan 2025 15:54:08 +0100 Subject: [PATCH 15/17] Fix merge --- provider/cmd/pulumi-resource-awsx/schema.json | 57 ++++++++++++++++++- provider/pkg/schemagen/ecr.go | 3 +- sdk/dotnet/Ecr/RegistryImage.cs | 2 +- sdk/go/awsx/ecr/registryImage.go | 4 +- .../pulumi/awsx/ecr/RegistryImageArgs.java | 8 +-- sdk/nodejs/ecr/registryImage.ts | 2 +- sdk/python/pulumi_awsx/ecr/registry_image.py | 6 +- 7 files changed, 68 insertions(+), 14 deletions(-) diff --git a/provider/cmd/pulumi-resource-awsx/schema.json b/provider/cmd/pulumi-resource-awsx/schema.json index 621a34bcf..4bfd75710 100644 --- a/provider/cmd/pulumi-resource-awsx/schema.json +++ b/provider/cmd/pulumi-resource-awsx/schema.json @@ -19,7 +19,8 @@ "packageReferences": { "Pulumi": "3.*", "Pulumi.Aws": "6.*", - "Pulumi.Docker": "4.*" + "Pulumi.Docker": "4.*", + "Pulumi.DockerBuild": "0.*" }, "respectSchemaVersion": true }, @@ -27,7 +28,8 @@ "generateResourceContainerTypes": true, "importBasePath": "github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx", "internalDependencies": [ - "github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild" + "github.com/pulumi/pulumi-docker-build/sdk/go/dockerbuild", + "github.com/pulumi/pulumi-docker/sdk/v4/go/docker" ], "liftSingleValueMethodReturns": true, "respectSchemaVersion": true @@ -35,6 +37,7 @@ "java": { "dependencies": { "com.pulumi:aws": "6.66.2", + "com.pulumi:docker": "4.6.0", "com.pulumi:docker-build": "0.0.8" } }, @@ -42,6 +45,7 @@ "dependencies": { "@aws-sdk/client-ecs": "^3.405.0", "@pulumi/aws": "^6.66.2", + "@pulumi/docker": "^4.6.0", "@pulumi/docker-build": "^0.0.8", "@types/aws-lambda": "^8.10.23", "docker-classic": "npm:@pulumi/docker@3.6.1", @@ -62,6 +66,7 @@ "readme": "Pulumi Amazon Web Services (AWS) AWSX Components.", "requires": { "pulumi-aws": "\u003e=6.0.4,\u003c7.0.0", + "pulumi-docker": "\u003e=4.6.0,\u003c5.0.0", "pulumi-docker-build": "\u003e=0.0.8,\u003c1.0.0" }, "respectSchemaVersion": true, @@ -2225,6 +2230,54 @@ ], "isComponent": true }, + "awsx:ecr:RegistryImage": { + "description": "Manages the lifecycle of a docker image in a registry. You can upload images to a registry (= `docker push`) and also delete them again. In contrast to [`awsx.ecr.Image`](/registry/packages/awsx/api-docs/ecr/image/), this resource does not require to build the image, but can be used to push an existing image to an ECR repository. The image will be pushed whenever the source image changes or is updated.\n\n{{% examples %}}\n## Example Usage\n{{% example %}}\n### Pushing an image to an ECR repository\n\n```typescript\nimport * as pulumi from \"@pulumi/pulumi\";\nimport * as awsx from \"@pulumi/awsx\";\n\nconst repository = new awsx.ecr.Repository(\"repository\", { forceDelete: true });\n\nconst preTaggedImage = new awsx.ecr.RegistryImage(\"registry-image\", {\n repositoryUrl: repository.url,\n sourceImage: \"my-awesome-image:v1.0.0\",\n});\n```\n```python\nimport pulumi\nimport pulumi_awsx as awsx\n\nrepository = awsx.ecr.Repository(\"repository\", force_delete=True)\n\nregistry_image = awsx.ecr.RegistryImage(\"registry_image\",\n repository_url=repository.url,\n source_image=\"my-awesome-image:v1.0.0\")\n```\n```go\npackage main\n\nimport (\n\t\"github.com/pulumi/pulumi-awsx/sdk/v2/go/awsx/ecr\"\n\t\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n)\n\nfunc main() {\n\tpulumi.Run(func(ctx *pulumi.Context) error {\n\t\trepository, err := ecr.NewRepository(ctx, \"repository\", \u0026ecr.RepositoryArgs{\n\t\t\tForceDelete: pulumi.Bool(true),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tregistryImage, err := ecr.NewRegistryImage(ctx, \"registryImage\", \u0026ecr.RegistryImageArgs{\n\t\t\tRepositoryUrl: repository.Url,\n\t\t\tSourceImage: pulumi.String(\"my-awesome-image:v1.0.0\"),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n}\n```\n```csharp\nusing Pulumi;\nusing Pulumi.Awsx.Ecr;\n\nreturn await Pulumi.Deployment.RunAsync(() =\u003e\n{\n var repository = new Repository(\"repository\", new RepositoryArgs\n {\n ForceDelete = true,\n });\n\n var registryImage = new RegistryImage(\"registryImage\", new RegistryImageArgs\n {\n RepositoryUrl = repository.Url,\n SourceImage = \"my-awesome-image:v1.0.0\",\n });\n\n return new Dictionary\u003cstring, object?\u003e{};\n});\n```\n```yaml\nname: example\nruntime: yaml\nresources:\n repository:\n type: awsx:ecr:Repository\n properties:\n forceDelete: true\n registryImage:\n type: awsx:ecr:RegistryImage\n properties:\n repositoryUrl: ${repository.url}\n sourceImage: \"my-awesome-image:v1.0.0\"\n```\n```java\nimport com.pulumi.Pulumi;\nimport com.pulumi.awsx.ecr.Repository;\nimport com.pulumi.awsx.ecr.RepositoryArgs;\nimport com.pulumi.awsx.ecr.RegistryImage;\nimport com.pulumi.awsx.ecr.RegistryImageArgs;\n\npublic class Main {\n public static void main(String[] args) {\n Pulumi.run(ctx -\u003e {\n // Create an ECR repository with force delete enabled\n var repository = new Repository(\"repository\", RepositoryArgs.builder()\n .forceDelete(true)\n .build());\n\n // Create a RegistryImage based on the ECR repository URL and source image\n var registryImage = new RegistryImage(\"registryImage\", RegistryImageArgs.builder()\n .repositoryUrl(repository.url())\n .sourceImage(\"my-awesome-image:v1.0.0\")\n .build());\n });\n }\n}\n```\n{{% /example %}}\n{{% /examples %}}\n", + "properties": { + "image": { + "$ref": "/docker/v4.6.0/schema.json#/resources/docker:index%2fregistryImage:RegistryImage", + "description": "The underlying RegistryImage resource." + } + }, + "type": "object", + "required": [ + "image" + ], + "inputProperties": { + "insecureSkipVerify": { + "type": "boolean", + "description": "If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false`\n" + }, + "keepRemotely": { + "type": "boolean", + "description": "If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false`\n" + }, + "repositoryUrl": { + "type": "string", + "description": "The URL of the repository (in the form aws_account_id.dkr.ecr.region.amazonaws.com/repositoryName)." + }, + "sourceImage": { + "type": "string", + "description": "The source image to push to the registry." + }, + "tag": { + "type": "string", + "description": "The tag to use for the pushed image. If not provided, it defaults to `latest`." + }, + "triggers": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image\n", + "willReplaceOnChanges": true + } + }, + "requiredInputs": [ + "repositoryUrl", + "sourceImage" + ], + "isComponent": true + }, "awsx:ecr:Repository": { "description": "A [Repository] represents an [aws.ecr.Repository] along with an associated [LifecyclePolicy] controlling how images are retained in the repo. \n\nDocker images can be built and pushed to the repo using the [buildAndPushImage] method. This will call into the `@pulumi/docker/buildAndPushImage` function using this repo as the appropriate destination registry.", "properties": { diff --git a/provider/pkg/schemagen/ecr.go b/provider/pkg/schemagen/ecr.go index 18f0eca77..7f540bc09 100644 --- a/provider/pkg/schemagen/ecr.go +++ b/provider/pkg/schemagen/ecr.go @@ -357,7 +357,8 @@ func registryImage(dockerSpec schema.PackageSpec) schema.ResourceSpec { originalSpec := dockerSpec.Resources["docker:index/registryImage:RegistryImage"] inputProperties := renameDockerPropertiesRefs(dockerSpec, originalSpec.InputProperties) inputProperties["repositoryUrl"] = schema.PropertySpec{ - Description: "Url of the ECR repository.", + Description: "The URL of the repository (in the form aws_account_id.dkr." + + "ecr.region.amazonaws.com/repositoryName).", TypeSpec: schema.TypeSpec{ Type: "string", }, diff --git a/sdk/dotnet/Ecr/RegistryImage.cs b/sdk/dotnet/Ecr/RegistryImage.cs index 2fc95fe23..227dc4f5e 100644 --- a/sdk/dotnet/Ecr/RegistryImage.cs +++ b/sdk/dotnet/Ecr/RegistryImage.cs @@ -85,7 +85,7 @@ public sealed class RegistryImageArgs : global::Pulumi.ResourceArgs public Input? KeepRemotely { get; set; } /// - /// Url of the ECR repository. + /// The URL of the repository (in the form aws_account_id.dkr.ecr.region.amazonaws.com/repositoryName). /// [Input("repositoryUrl", required: true)] public Input RepositoryUrl { get; set; } = null!; diff --git a/sdk/go/awsx/ecr/registryImage.go b/sdk/go/awsx/ecr/registryImage.go index c39d953c7..6a45dbb41 100644 --- a/sdk/go/awsx/ecr/registryImage.go +++ b/sdk/go/awsx/ecr/registryImage.go @@ -83,7 +83,7 @@ type registryImageArgs struct { InsecureSkipVerify *bool `pulumi:"insecureSkipVerify"` // If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` KeepRemotely *bool `pulumi:"keepRemotely"` - // Url of the ECR repository. + // The URL of the repository (in the form aws_account_id.dkr.ecr.region.amazonaws.com/repositoryName). RepositoryUrl string `pulumi:"repositoryUrl"` // The source image to push to the registry. SourceImage string `pulumi:"sourceImage"` @@ -99,7 +99,7 @@ type RegistryImageArgs struct { InsecureSkipVerify pulumi.BoolPtrInput // If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` KeepRemotely pulumi.BoolPtrInput - // Url of the ECR repository. + // The URL of the repository (in the form aws_account_id.dkr.ecr.region.amazonaws.com/repositoryName). RepositoryUrl pulumi.StringInput // The source image to push to the registry. SourceImage pulumi.StringInput diff --git a/sdk/java/src/main/java/com/pulumi/awsx/ecr/RegistryImageArgs.java b/sdk/java/src/main/java/com/pulumi/awsx/ecr/RegistryImageArgs.java index ec5b0493e..faaf11a5c 100644 --- a/sdk/java/src/main/java/com/pulumi/awsx/ecr/RegistryImageArgs.java +++ b/sdk/java/src/main/java/com/pulumi/awsx/ecr/RegistryImageArgs.java @@ -48,14 +48,14 @@ public Optional> keepRemotely() { } /** - * Url of the ECR repository. + * The URL of the repository (in the form aws_account_id.dkr.ecr.region.amazonaws.com/repositoryName). * */ @Import(name="repositoryUrl", required=true) private Output repositoryUrl; /** - * @return Url of the ECR repository. + * @return The URL of the repository (in the form aws_account_id.dkr.ecr.region.amazonaws.com/repositoryName). * */ public Output repositoryUrl() { @@ -179,7 +179,7 @@ public Builder keepRemotely(Boolean keepRemotely) { } /** - * @param repositoryUrl Url of the ECR repository. + * @param repositoryUrl The URL of the repository (in the form aws_account_id.dkr.ecr.region.amazonaws.com/repositoryName). * * @return builder * @@ -190,7 +190,7 @@ public Builder repositoryUrl(Output repositoryUrl) { } /** - * @param repositoryUrl Url of the ECR repository. + * @param repositoryUrl The URL of the repository (in the form aws_account_id.dkr.ecr.region.amazonaws.com/repositoryName). * * @return builder * diff --git a/sdk/nodejs/ecr/registryImage.ts b/sdk/nodejs/ecr/registryImage.ts index 1a9b8ead5..d7bc9cfd2 100644 --- a/sdk/nodejs/ecr/registryImage.ts +++ b/sdk/nodejs/ecr/registryImage.ts @@ -89,7 +89,7 @@ export interface RegistryImageArgs { */ keepRemotely?: pulumi.Input; /** - * Url of the ECR repository. + * The URL of the repository (in the form aws_account_id.dkr.ecr.region.amazonaws.com/repositoryName). */ repositoryUrl: pulumi.Input; /** diff --git a/sdk/python/pulumi_awsx/ecr/registry_image.py b/sdk/python/pulumi_awsx/ecr/registry_image.py index 7149cc622..b7cf43679 100644 --- a/sdk/python/pulumi_awsx/ecr/registry_image.py +++ b/sdk/python/pulumi_awsx/ecr/registry_image.py @@ -28,7 +28,7 @@ def __init__(__self__, *, triggers: Optional[pulumi.Input[Mapping[str, pulumi.Input[str]]]] = None): """ The set of arguments for constructing a RegistryImage resource. - :param pulumi.Input[str] repository_url: Url of the ECR repository. + :param pulumi.Input[str] repository_url: The URL of the repository (in the form aws_account_id.dkr.ecr.region.amazonaws.com/repositoryName). :param pulumi.Input[str] source_image: The source image to push to the registry. :param pulumi.Input[bool] insecure_skip_verify: If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false` :param pulumi.Input[bool] keep_remotely: If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` @@ -50,7 +50,7 @@ def __init__(__self__, *, @pulumi.getter(name="repositoryUrl") def repository_url(self) -> pulumi.Input[str]: """ - Url of the ECR repository. + The URL of the repository (in the form aws_account_id.dkr.ecr.region.amazonaws.com/repositoryName). """ return pulumi.get(self, "repository_url") @@ -151,7 +151,7 @@ def __init__(__self__, :param pulumi.ResourceOptions opts: Options for the resource. :param pulumi.Input[bool] insecure_skip_verify: If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false` :param pulumi.Input[bool] keep_remotely: If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` - :param pulumi.Input[str] repository_url: Url of the ECR repository. + :param pulumi.Input[str] repository_url: The URL of the repository (in the form aws_account_id.dkr.ecr.region.amazonaws.com/repositoryName). :param pulumi.Input[str] source_image: The source image to push to the registry. :param pulumi.Input[str] tag: The tag to use for the pushed image. If not provided, it defaults to `latest`. :param pulumi.Input[Mapping[str, pulumi.Input[str]]] triggers: A map of arbitrary strings that, when changed, will force the `docker.RegistryImage` resource to be replaced. This can be used to repush a local image From d7420ee8f6aa8c7b2229b15ccf175eef48c81413 Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Wed, 15 Jan 2025 16:27:13 +0100 Subject: [PATCH 16/17] Make linter happy --- provider/pkg/schemagen/ecr.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/provider/pkg/schemagen/ecr.go b/provider/pkg/schemagen/ecr.go index 7f540bc09..fbd7f4725 100644 --- a/provider/pkg/schemagen/ecr.go +++ b/provider/pkg/schemagen/ecr.go @@ -16,6 +16,7 @@ package gen import ( _ "embed" + "github.com/pulumi/pulumi/pkg/v3/codegen/schema" ) @@ -358,7 +359,7 @@ func registryImage(dockerSpec schema.PackageSpec) schema.ResourceSpec { inputProperties := renameDockerPropertiesRefs(dockerSpec, originalSpec.InputProperties) inputProperties["repositoryUrl"] = schema.PropertySpec{ Description: "The URL of the repository (in the form aws_account_id.dkr." + - "ecr.region.amazonaws.com/repositoryName).", + "ecr.region.amazonaws.com/repositoryName).", TypeSpec: schema.TypeSpec{ Type: "string", }, From 0ff619a65ecd3a61532a8bf2a1bdaaf198e8dd85 Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Wed, 15 Jan 2025 17:06:53 +0100 Subject: [PATCH 17/17] Make linter happy --- provider/pkg/schemagen/ecr.go | 1 + 1 file changed, 1 insertion(+) diff --git a/provider/pkg/schemagen/ecr.go b/provider/pkg/schemagen/ecr.go index fbd7f4725..aafa4514f 100644 --- a/provider/pkg/schemagen/ecr.go +++ b/provider/pkg/schemagen/ecr.go @@ -15,6 +15,7 @@ package gen import ( + // used for embedding docs _ "embed" "github.com/pulumi/pulumi/pkg/v3/codegen/schema"