internal/cs/pushimage.go (112 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package cs implements features related to Lightsail (C)ontainer (S)ervice.
package cs
import (
"context"
"crypto/rand"
"encoding/base32"
"fmt"
"io"
"log"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/lightsail"
"github.com/docker/docker/api/types/registry"
)
type PushImageInput struct {
Service string
Image string
Label string
}
type RegistryLoginCreator interface {
CreateContainerServiceRegistryLogin(
context.Context,
*lightsail.CreateContainerServiceRegistryLoginInput,
...func(*lightsail.Options),
) (*lightsail.CreateContainerServiceRegistryLoginOutput, error)
}
type LightsailImageOperator interface {
RegistryLoginCreator
RegisterContainerImage(
context.Context,
*lightsail.RegisterContainerImageInput,
...func(*lightsail.Options),
) (*lightsail.RegisterContainerImageOutput, error)
}
type ImageOperator interface {
TagImage(ctx context.Context, source, target string) error
UntagImage(ctx context.Context, image string) error
PushImage(ctx context.Context, r RemoteImage) (digest string, err error)
}
// PushImage pushes and registers the image to Lightsail service registry.
func PushImage(ctx context.Context, in *PushImageInput, lio LightsailImageOperator, imgo ImageOperator) error {
authConfig, err := getServiceRegistryAuth(ctx, lio)
if err != nil {
return err
}
remoteImage := RemoteImage{AuthConfig: *authConfig, Tag: generateUniqueTag()}
err = imgo.TagImage(ctx, in.Image, remoteImage.Ref())
if err != nil {
return err
}
defer tryUntagImage(ctx, imgo, remoteImage.Ref())
digest, err := imgo.PushImage(ctx, remoteImage)
if err != nil {
return err
}
registered, err := lio.RegisterContainerImage(
ctx,
&lightsail.RegisterContainerImageInput{
ServiceName: &in.Service,
Label: &in.Label,
Digest: &digest,
},
)
if err != nil {
return err
}
fmt.Printf("Digest: %s\nImage %q registered.\nRefer to this image as %q in deployments.\n",
aws.ToString(registered.ContainerImage.Digest),
in.Image,
aws.ToString(registered.ContainerImage.Image))
return nil
}
// getServiceRegistryAuth returns the server address and
// the temporary credentials sufficient to push images to
// Lightsail Containers service repo (aka "sr").
//
// Note that "sr" repo only retains image tags generated
// when RegisterContainerImage API is called with specific image
// digests. The purpose of this repo is to keep images that are
// strictly related to your Lightsail container service deployments.
func getServiceRegistryAuth(ctx context.Context, rlc RegistryLoginCreator) (*registry.AuthConfig, error) {
out, err := rlc.CreateContainerServiceRegistryLogin(
ctx,
new(lightsail.CreateContainerServiceRegistryLoginInput),
)
if err != nil {
return nil, err
}
return ®istry.AuthConfig{
Username: aws.ToString(out.RegistryLogin.Username),
Password: aws.ToString(out.RegistryLogin.Password),
ServerAddress: aws.ToString(out.RegistryLogin.Registry) + "/sr",
}, nil
}
// tryUntagImage is the same as ImageOperator.UntagImage
// except it doesn't return error and instead logs it.
func tryUntagImage(ctx context.Context, imgo ImageOperator, image string) {
if err := imgo.UntagImage(ctx, image); err != nil {
log.Println(err)
}
}
func generateUniqueTag() string {
now := time.Now()
if testNow != nil {
now = testNow()
}
return fmt.Sprintf("%v-%s", now.UnixNano(), randomName13())
}
func randomName13() string {
r := rand.Reader
if testRngReader != nil {
r = testRngReader
}
b := make([]byte, 8)
if _, err := io.ReadFull(r, b); err != nil {
panic(err)
}
return b32.EncodeToString(b)
}
var (
b32 = base32.NewEncoding("0123456789abcdefghijklmnopqrstuv").WithPadding(base32.NoPadding)
testNow func() time.Time
testRngReader io.Reader
)