Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SHIP-0026 Shipwright-managed push #1046

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ jobs:
run: |
export TEST_NAMESPACE=shipwright-build
export TEST_IMAGE_REPO=registry.registry.svc.cluster.local:32222/shipwright-io/build-e2e
export TEST_IMAGE_REPO_INSECURE=true
export TEST_E2E_TIMEOUT_MULTIPLIER=2
make test-e2e
- name: Build controller logs
Expand Down
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ TEST_E2E_TIMEOUT_MULTIPLIER ?= 1

# test repository to store images build during end-to-end tests
TEST_IMAGE_REPO ?= ghcr.io/shipwright-io/build/build-e2e
# test container registyr secret name
# test repository insecure (HTTP or self-signed certificate)
TEST_IMAGE_REPO_INSECURE ?= false
# test container registry secret name
TEST_IMAGE_REPO_SECRET ?=
# test container registry secret, must be defined during runtime
TEST_IMAGE_REPO_DOCKERCONFIGJSON ?=
Expand Down Expand Up @@ -153,7 +155,7 @@ install-counterfeiter:
# Install golangci-lint via: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
.PHONY: sanity-check
sanity-check:
golangci-lint run
golangci-lint run --timeout=10m

# https://github.com/shipwright-io/build/issues/123
.PHONY: test
Expand Down
117 changes: 14 additions & 103 deletions cmd/bundle/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ import (
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"

"github.com/docker/cli/cli/config"
"github.com/golang-jwt/jwt/v4"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
Expand All @@ -27,6 +25,7 @@ import (
"github.com/spf13/pflag"

"github.com/shipwright-io/build/pkg/bundle"
"github.com/shipwright-io/build/pkg/image"
)

type settings struct {
Expand Down Expand Up @@ -78,7 +77,7 @@ func Do(ctx context.Context) error {
return err
}

auth, err := resolveAuthBasedOnTarget(ref)
options, auth, err := image.GetOptions(ctx, ref, true, flagValues.secretPath, "Shipwright Build")
if err != nil {
return err
}
Expand All @@ -87,8 +86,7 @@ func Do(ctx context.Context) error {
img, err := bundle.PullAndUnpack(
ref,
flagValues.target,
remote.WithContext(ctx),
remote.WithAuth(auth))
options...)
if err != nil {
return err
}
Expand Down Expand Up @@ -116,87 +114,14 @@ func Do(ctx context.Context) error {
}

log.Printf("Deleting image %q", ref)
if err := Prune(ctx, ref, auth); err != nil {
if err := Prune(ref, options, *auth); err != nil {
return err
}
}

return nil
}

func resolveAuthBasedOnTarget(ref name.Reference) (authn.Authenticator, error) {
// In case no secret is mounted, use anonymous
if flagValues.secretPath == "" {
log.Printf("No access credentials provided, using anonymous mode")
return authn.Anonymous, nil
}

// Read the registry credentials from the well-known location if it exists
var mountedSecretDefaultFileName = filepath.Join(flagValues.secretPath, ".dockerconfigjson")
if _, err := os.Stat(mountedSecretDefaultFileName); err == nil {
return ResolveAuthBasedOnTargetUsingConfigFile(ref, mountedSecretDefaultFileName)
}

// Otherwise, treat secret path as a file (for none Kubernetes setups)
return ResolveAuthBasedOnTargetUsingConfigFile(ref, flagValues.secretPath)
}

// ResolveAuthBasedOnTargetUsingConfigFile resolves if possible the respective authenticator to be used for
// given image reference (full registry and image name)
func ResolveAuthBasedOnTargetUsingConfigFile(ref name.Reference, dockerConfigFile string) (authn.Authenticator, error) {
file, err := os.Open(dockerConfigFile)
if err != nil {
return nil, err
}
defer file.Close()

cf, err := config.LoadFromReader(file)
if err != nil {
return nil, err
}

// Look-up the respective registry server inside the credentials
registryName := ref.Context().RegistryStr()
if registryName == name.DefaultRegistry {
registryName = authn.DefaultAuthKey
}

authConfig, err := cf.GetAuthConfig(registryName)
if err != nil {
return nil, err
}

// Return an error in case the credentials do not match the desired
// registry and list all servers that actually are available
if authConfig.ServerAddress != registryName {
var servers []string
for name := range cf.GetAuthConfigs() {
servers = append(servers, name)
}

var availableConfigs string
if len(servers) > 0 {
availableConfigs = strings.Join(servers, ", ")
} else {
availableConfigs = "none"
}

return nil, fmt.Errorf("failed to find registry credentials for %s, available configurations: %s",
registryName,
availableConfigs,
)
}

log.Printf("Using provided access credentials for %s", registryName)
return authn.FromConfig(authn.AuthConfig{
Username: authConfig.Username,
Password: authConfig.Password,
Auth: authConfig.Auth,
IdentityToken: authConfig.IdentityToken,
RegistryToken: authConfig.RegistryToken,
}), nil
}

// Prune removes the image from the container registry
//
// Deleting a tag, or a whole repo is not as straightforward as initially
Expand All @@ -208,10 +133,10 @@ func ResolveAuthBasedOnTargetUsingConfigFile(ref name.Reference, dockerConfigFil
// is a provider switch to deal with images on DockerHub differently.
//
// DockerHub images:
// - In case the repository only has one tag, the repository is deleted.
// - If there are multiple tags, the tag to be deleted is overwritten
// with an empty image (to remove the content, and save quota).
// - Edge case would be no tags in the repository, which is ignored.
// - In case the repository only has one tag, the repository is deleted.
// - If there are multiple tags, the tag to be deleted is overwritten
// with an empty image (to remove the content, and save quota).
// - Edge case would be no tags in the repository, which is ignored.
//
// IBM Container Registry images:
// Custom delete API call has to be used, since ICR does not support the
Expand All @@ -223,11 +148,10 @@ func ResolveAuthBasedOnTargetUsingConfigFile(ref name.Reference, dockerConfigFil
//
// Other registries:
// Use standard spec delete API request to delete the provided tag.
//
func Prune(ctx context.Context, ref name.Reference, auth authn.Authenticator) error {
func Prune(ref name.Reference, options []remote.Option, auth authn.AuthConfig) error {
switch {
case strings.Contains(ref.Context().RegistryStr(), "docker.io"):
list, err := remote.List(ref.Context(), remote.WithContext(ctx), remote.WithAuth(auth))
list, err := remote.List(ref.Context(), options...)
if err != nil {
return err
}
Expand All @@ -237,14 +161,8 @@ func Prune(ctx context.Context, ref name.Reference, auth authn.Authenticator) er
return nil

case 1:
var authr *authn.AuthConfig
authr, err = auth.Authorization()
if err != nil {
return err
}

var token string
token, err = dockerHubLogin(authr.Username, authr.Password)
token, err = dockerHubLogin(auth.Username, auth.Password)
if err != nil {
return err
}
Expand All @@ -268,18 +186,12 @@ func Prune(ctx context.Context, ref name.Reference, auth authn.Authenticator) er
return remote.Write(
ref,
empty.Image,
remote.WithContext(ctx),
remote.WithAuth(auth),
options...,
)
}

case strings.Contains(ref.Context().RegistryStr(), "icr.io"):
authr, err := auth.Authorization()
if err != nil {
return err
}

token, accountID, err := icrLogin(ref.Context().RegistryStr(), authr.Username, authr.Password)
token, accountID, err := icrLogin(ref.Context().RegistryStr(), auth.Username, auth.Password)
if err != nil {
return err
}
Expand All @@ -289,8 +201,7 @@ func Prune(ctx context.Context, ref name.Reference, auth authn.Authenticator) er
default:
return remote.Delete(
ref,
remote.WithContext(ctx),
remote.WithAuth(auth),
options...,
)
}
}
Expand Down
17 changes: 9 additions & 8 deletions cmd/bundle/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
. "github.com/onsi/gomega"

. "github.com/shipwright-io/build/cmd/bundle"
"github.com/shipwright-io/build/pkg/image"

"github.com/google/go-containerregistry/pkg/name"
containerreg "github.com/google/go-containerregistry/pkg/v1"
Expand Down Expand Up @@ -144,19 +145,19 @@ var _ = Describe("Bundle Loader", func() {
var dockerConfigFile string

var copyImage = func(src, dst name.Reference) {
srcAuth, err := ResolveAuthBasedOnTargetUsingConfigFile(src, dockerConfigFile)
options, _, err := image.GetOptions(context.TODO(), src, true, dockerConfigFile, "test-agent")
Expect(err).ToNot(HaveOccurred())

srcDesc, err := remote.Get(src, remote.WithAuth(srcAuth))
srcDesc, err := remote.Get(src, options...)
Expect(err).ToNot(HaveOccurred())

srcImage, err := srcDesc.Image()
Expect(err).ToNot(HaveOccurred())

dstAuth, err := ResolveAuthBasedOnTargetUsingConfigFile(dst, dockerConfigFile)
options, _, err = image.GetOptions(context.TODO(), dst, true, dockerConfigFile, "test-agent")
Expect(err).ToNot(HaveOccurred())

err = remote.Write(dst, srcImage, remote.WithAuth(dstAuth))
err = remote.Write(dst, srcImage, options...)
Expect(err).ToNot(HaveOccurred())
}

Expand Down Expand Up @@ -190,11 +191,11 @@ var _ = Describe("Bundle Loader", func() {
ref, err := name.ParseReference(testImage)
Expect(err).ToNot(HaveOccurred())

auth, err := ResolveAuthBasedOnTargetUsingConfigFile(ref, dockerConfigFile)
options, auth, err := image.GetOptions(context.TODO(), ref, true, dockerConfigFile, "test-agent")
Expect(err).ToNot(HaveOccurred())

// Delete test image (best effort)
_ = Prune(context.TODO(), ref, auth)
_ = Prune(ref, options, *auth)
})

It("should pull and unpack an image from a private registry", func() {
Expand Down Expand Up @@ -223,10 +224,10 @@ var _ = Describe("Bundle Loader", func() {
ref, err := name.ParseReference(testImage)
Expect(err).ToNot(HaveOccurred())

auth, err := ResolveAuthBasedOnTargetUsingConfigFile(ref, dockerConfigFile)
options, _, err := image.GetOptions(context.TODO(), ref, true, dockerConfigFile, "test-agent")
Expect(err).ToNot(HaveOccurred())

_, err = remote.Head(ref, remote.WithAuth(auth))
_, err = remote.Head(ref, options...)
Expect(err).To(HaveOccurred())
})
})
Expand Down
13 changes: 8 additions & 5 deletions cmd/mutate-image/README.md → cmd/image-processing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ Copyright The Shipwright Contributors

SPDX-License-Identifier: Apache-2.0
-->
# Mutate Image Wrapper
# Image processing

As part of the build, the output image needs to be mutated (annotated and labeled). This package contains Shipwright Build owned image mutate code, which is using the [crane](https://github.com/google/go-containerregistry/tree/main/cmd/crane) in the background.
As part of the build, the output image needs to be mutated (annotated and labeled), and pushed. This package contains Shipwright Build owned image processing code.

## Features

- Mutate the image with [annotations](https://github.com/opencontainers/image-spec/blob/main/annotations.md)
- Mutate the image with labels
- Push the image

## Development

Expand All @@ -19,10 +20,12 @@ As part of the build, the output image needs to be mutated (annotated and labele
- Run it locally:

```sh
go run cmd/mutate-image/main.go \
go run cmd/image-processing/main.go \
--image $IMAGE \
--annotation "org.opencontainers.image.url=https://my-company.com/images" \
--label "[email protected]"
--label "[email protected]" \
[--insecure] \
[--push some-local-dir-or-tarball]
```

If we are trying to mutate the image in a private registry, authentication to the registry should be done before running the command.
Expand All @@ -34,7 +37,7 @@ As part of the build, the output image needs to be mutated (annotated and labele
--rm \
--volume $HOME/.docker/config.json:/.docker/config.json \
-e DOCKER_CONFIG=.docker \
$(KO_DOCKER_REPO=ko.local ko publish --bare ./cmd/mutate-image) \
$(KO_DOCKER_REPO=ko.local ko publish --bare ./cmd/image-processing) \
--image $IMAGE \
--annotation "org.opencontainers.image.url=https://my-company.com/images" \
--label "[email protected]"
Expand Down
Loading