Skip to content
This repository has been archived by the owner on Jan 8, 2024. It is now read-only.

Commit

Permalink
Merge pull request #4858 from hashicorp/backport/ecr-pull/manual
Browse files Browse the repository at this point in the history
Backport/ecr pull/manual
  • Loading branch information
izaaklauer committed Jul 28, 2023
2 parents cef74a9 + 0d3d5ce commit e5644e9
Show file tree
Hide file tree
Showing 26 changed files with 258 additions and 10 deletions.
3 changes: 3 additions & 0 deletions .changelog/4847.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
plugin/aws/ecr-pull: Support entrypoint injection in ecr-pull builder
```
3 changes: 3 additions & 0 deletions builtin/aws/alb/plugin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions builtin/aws/ami/plugin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions builtin/aws/ec2/plugin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion builtin/aws/ecr/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ func ECRImageMapper(src *Image) *docker.Image {
return &docker.Image{
Image: src.Image,
Tag: src.Tag,

Location: &docker.Image_Registry{
Registry: &docker.Image_RegistryLocation{},
},
}
}

func DockerToEcrImageMapper(src *docker.Image) *Image {
return &Image{
Image: src.Image,
Tag: src.Tag,
}
}
3 changes: 3 additions & 0 deletions builtin/aws/ecr/plugin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

168 changes: 161 additions & 7 deletions builtin/aws/ecr/pull/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,19 @@ import (
"github.com/aws/aws-sdk-go/aws"
awsecr "github.com/aws/aws-sdk-go/service/ecr"
"github.com/aws/aws-sdk-go/service/lambda"

"github.com/hashicorp/waypoint-plugin-sdk/component"
"github.com/hashicorp/waypoint-plugin-sdk/docs"
"github.com/hashicorp/waypoint-plugin-sdk/terminal"
"github.com/hashicorp/waypoint/builtin/aws/ecr"
"github.com/hashicorp/waypoint/builtin/aws/utils"

"encoding/base64"
"encoding/json"

wpdocker "github.com/hashicorp/waypoint/builtin/docker"
wpdockerpull "github.com/hashicorp/waypoint/builtin/docker/pull"

validation "github.com/go-ozzo/ozzo-validation/v4"
)

Expand All @@ -32,12 +40,22 @@ func (b *Builder) BuildFunc() interface{} {
return b.Build
}

// BuildFunc implements component.BuilderODR
func (b *Builder) BuildODRFunc() interface{} {
return b.BuildODR
}

// Config is the configuration structure for the registry.
type Config struct {
Region string `hcl:"region,optional"`
Repository string `hcl:"repository,attr"`
Tag string `hcl:"tag,attr"`
ForceArchitecture string `hcl:"force_architecture,optional"`
DisableCEB bool `hcl:"disable_entrypoint,optional"`
}

type authInfo struct {
Base64Token *string
}

func (b *Builder) Documentation() (*docs.Documentation, error) {
Expand Down Expand Up @@ -96,6 +114,16 @@ build {
docs.Default("`\"\"`"),
)

doc.SetField(
"disable_entrypoint",
"if set, the entrypoint binary won't be injected into the image",
docs.Summary(
"The entrypoint binary is what provides extended functionality",
"such as logs and exec. If it is not injected at build time",
"the expectation is that the image already contains it",
),
)

return doc, nil
}

Expand Down Expand Up @@ -147,7 +175,125 @@ func (b *Builder) Config() (interface{}, error) {

// Build
func (b *Builder) Build(ctx context.Context, ui terminal.UI, log hclog.Logger) (*ecr.Image, error) {
sg := ui.StepGroup()
ecrImage, buildAuthInfo, err := b.getErcImage(ctx, ui, log, sg)
if err != nil {
return nil, err
}

if b.config.DisableCEB {
return ecrImage, nil
}

repoUser, repoPass, err := credentialsFromEcr(*buildAuthInfo.Base64Token)
if err != nil {
return nil, err
}

// Use the authorization token to create the base64 package that
// Docker requires to perform authentication.
authInfo := map[string]string{
"username": repoUser,
"password": repoPass,
}

authData, err := json.Marshal(authInfo)
if err != nil {
return nil, err
}
encodedAuth := base64.StdEncoding.EncodeToString(authData)

pullBuilder := &wpdockerpull.Builder{}
raw, err := pullBuilder.Config()
if err != nil {
return nil, err
}
pullConfig := raw.(*wpdockerpull.BuilderConfig)
pullConfig.EncodedAuth = encodedAuth
pullConfig.Image = ecrImage.Image
pullConfig.Tag = ecrImage.Tag
pullConfig.DisableCEB = b.config.DisableCEB

img, err := pullBuilder.Build(wpdockerpull.BuildArgs{
Ctx: ctx,
UI: ui,
Log: log,
HasRegistry: true,
})

if err != nil {
return nil, err
}
return ecr.DockerToEcrImageMapper(img), nil
}

// Build
func (b *Builder) BuildODR(ctx context.Context, ui terminal.UI, log hclog.Logger, src *component.Source, ai *wpdocker.AccessInfo) (*ecr.Image, error) {
sg := ui.StepGroup()
ecrImage, buildAuthInfo, err := b.getErcImage(ctx, ui, log, sg)
if err != nil {
return nil, err
}

if b.config.DisableCEB {
return ecrImage, nil
}

repoUser, repoPass, err := credentialsFromEcr(*buildAuthInfo.Base64Token)
if err != nil {
return nil, err
}

// Use the authorization token to create the base64 package that
// Docker requires to perform authentication.
authInfo := map[string]string{
"username": repoUser,
"password": repoPass,
}

authData, err := json.Marshal(authInfo)
if err != nil {
return nil, err
}
encodedAuth := base64.StdEncoding.EncodeToString(authData)

pullBuilder := &wpdockerpull.Builder{}
raw, err := pullBuilder.Config()
if err != nil {
return nil, err
}
pullConfig := raw.(*wpdockerpull.BuilderConfig)
pullConfig.EncodedAuth = encodedAuth
pullConfig.Image = ecrImage.Image
pullConfig.Tag = ecrImage.Tag
pullConfig.DisableCEB = b.config.DisableCEB

img, err := pullBuilder.BuildODR(ctx, ui, src, log, ai)
if err != nil {
return nil, err
}
return ecr.DockerToEcrImageMapper(img), nil
}

// CredentialsFromEcr returns the username and password present in the encoded
// auth string. This encoded auth string is one that users can pass as authentication
// information to registry.
func credentialsFromEcr(encodedAuth string) (string, string, error) {
// Create a reader that base64 decodes our encoded auth and then splits off USER:PASS
dec, err := base64.StdEncoding.DecodeString(encodedAuth)
if err != nil {
return "", "", fmt.Errorf("invalid encoded auth string: %s", encodedAuth[:5])
}

userPassSplit := strings.SplitN(string(dec), ":", 2)
if len(userPassSplit) != 2 {
return "", "", fmt.Errorf("ecr credentials were invalid or did not container user:password. Number of elements in split: %d", len(userPassSplit))
}

return userPassSplit[0], userPassSplit[1], nil
}

func (b *Builder) getErcImage(ctx context.Context, ui terminal.UI, log hclog.Logger, sg terminal.StepGroup) (*ecr.Image, *authInfo, error) {
// If there is no region setup. Try and load it from environment variables.
if b.config.Region == "" {
b.config.Region = os.Getenv("AWS_REGION")
Expand All @@ -158,12 +304,11 @@ func (b *Builder) Build(ctx context.Context, ui terminal.UI, log hclog.Logger) (
}

if b.config.Region == "" {
return nil, status.Error(
return nil, nil, status.Error(
codes.FailedPrecondition,
"Please set your aws region in the deployment config, or set the environment variable 'AWS_REGION' or 'AWS_DEFAULT_REGION'")
}

sg := ui.StepGroup()
step := sg.Add("")
defer func() {
if step != nil {
Expand All @@ -180,7 +325,7 @@ func (b *Builder) Build(ctx context.Context, ui terminal.UI, log hclog.Logger) (

if err != nil {
log.Error("error connecting to AWS", "error", err)
return nil, err
return nil, nil, err
}

step.Done()
Expand All @@ -192,6 +337,15 @@ func (b *Builder) Build(ctx context.Context, ui terminal.UI, log hclog.Logger) (
cfgTag := b.config.Tag
cfgRepository := b.config.Repository

tokenResp, err := ecrsvc.GetAuthorizationTokenWithContext(ctx, &awsecr.GetAuthorizationTokenInput{})
if err != nil {
log.Error("error getting authorization token", "error", err)
return nil, nil, err
}
// docs say the token is good for all registries the user has access to. so just grab the first token
token := tokenResp.AuthorizationData[0].AuthorizationToken
log.Debug("successfully retrieved authorization token")

// should be acceptable to filter images by TAGGED status
imgs, err := ecrsvc.DescribeImages(&awsecr.DescribeImagesInput{
RepositoryName: aws.String(cfgRepository),
Expand All @@ -201,12 +355,12 @@ func (b *Builder) Build(ctx context.Context, ui terminal.UI, log hclog.Logger) (
})
if err != nil {
log.Error("error describing images", "error", err, "repository", cfgRepository)
return nil, err
return nil, nil, err
}

if len(imgs.ImageDetails) == 0 {
log.Error("no tagged images found", "repository", cfgRepository)
return nil, status.Error(codes.FailedPrecondition, "No images found")
return nil, nil, status.Error(codes.FailedPrecondition, "No images found")
}
log.Debug("found images", "image count", len(imgs.ImageDetails))

Expand Down Expand Up @@ -245,11 +399,11 @@ func (b *Builder) Build(ctx context.Context, ui terminal.UI, log hclog.Logger) (
// if no image was found, return an error
if output.Image == "" {
log.Error("no matching image was found", "tag", cfgTag, "repository", cfgRepository)
return nil, status.Error(codes.FailedPrecondition, "No matching tags found")
return nil, nil, status.Error(codes.FailedPrecondition, "No matching tags found")
}

step.Update("Using image: " + output.Image + ":" + output.Tag)
step.Done()

return &output, nil
return &output, &authInfo{Base64Token: token}, nil
}
7 changes: 7 additions & 0 deletions builtin/aws/ecr/pull/components/builder/parameters.hcl
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
# This file was generated via `make gen/integrations-hcl`
parameter {
key = "disable_entrypoint"
description = "if set, the entrypoint binary won't be injected into the image\nThe entrypoint binary is what provides extended functionality such as logs and exec. If it is not injected at build time the expectation is that the image already contains it"
type = "bool"
required = false
}

parameter {
key = "force_architecture"
description = "**Note**: This is a temporary field that enables overriding the `architecture` output attribute. Valid values are: `\"x86_64\"`, `\"arm64\"`"
Expand Down
3 changes: 3 additions & 0 deletions builtin/aws/ecs/plugin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions builtin/aws/lambda/function_url/plugin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions builtin/aws/lambda/plugin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions builtin/azure/aci/plugin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions builtin/docker/plugin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions builtin/docker/pull/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func (b *Builder) Config() (interface{}, error) {

// We use the struct form of arguments so that we can access named
// values (such as "HasRegistry").
type buildArgs struct {
type BuildArgs struct {
argmapper.Struct

Ctx context.Context
Expand Down Expand Up @@ -183,7 +183,7 @@ func (b *Builder) BuildODR(
}

// Build
func (b *Builder) Build(args buildArgs) (*wpdocker.Image, error) {
func (b *Builder) Build(args BuildArgs) (*wpdocker.Image, error) {
// Pull all the args out to top-level values. This is mostly done
// cause the struct was added later, but also because these are very common.
ctx := args.Ctx
Expand Down
3 changes: 3 additions & 0 deletions builtin/exec/plugin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions builtin/files/plugin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions builtin/google/cloudrun/plugin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions builtin/k8s/apply/plugin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e5644e9

Please sign in to comment.