Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
SaschaSchwarze0 committed Apr 25, 2022
1 parent c2ed8d4 commit 57db7ce
Show file tree
Hide file tree
Showing 45 changed files with 969 additions and 453 deletions.
186 changes: 46 additions & 140 deletions cmd/mutate-image/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,16 @@ package main

import (
"context"
"crypto/tls"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
"strings"

"github.com/docker/cli/cli/config"
"github.com/google/go-containerregistry/pkg/crane"
"github.com/google/go-containerregistry/pkg/name"
containerreg "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/shipwright-io/build/pkg/image"
"github.com/spf13/pflag"
)

Expand All @@ -38,30 +34,12 @@ func (e ExitError) Error() string {
return fmt.Sprintf("%s (exit code %d)", e.Message, e.Code)
}

// headerTransport sets headers on outgoing requests
type headerTransport struct {
httpHeaders map[string]string
inner http.RoundTripper
}

// RoundTrip implements http.RoundTripper
func (ht *headerTransport) RoundTrip(in *http.Request) (*http.Response, error) {
for k, v := range ht.httpHeaders {
if http.CanonicalHeaderKey(k) == "User-Agent" {
// Docker sets this, which is annoying, since we're not docker.
// We might want to revisit completely ignoring this.
continue
}
in.Header.Set(k, v)
}

return ht.inner.RoundTrip(in)
}

type settings struct {
help bool
push string
annotation,
label *[]string
insecure bool
image,
resultFileImageDigest,
resultFileImageSize string
Expand Down Expand Up @@ -96,8 +74,10 @@ func initializeFlag() {
// Main flags for the image mutate step to define the configuration, for example
// the flag `image` will always be used.
pflag.StringVar(&flagValues.image, "image", "", "The name of image in container registry")
pflag.BoolVar(&flagValues.insecure, "insecure", false, "Flag indicating the the container registry is insecure")
flagValues.annotation = pflag.StringArray("annotation", nil, "New annotations to add")
flagValues.label = pflag.StringArray("label", nil, "New labels to add")
pflag.StringVar(&flagValues.push, "push", "", "Push the image contained in this directory")
pflag.StringVar(&flagValues.resultFileImageDigest, "result-file-image-digest", "", "A file to write the image digest to")
pflag.StringVar(&flagValues.resultFileImageSize, "result-file-image-size", "", "A file to write the image size to")
}
Expand Down Expand Up @@ -130,133 +110,87 @@ func Execute(ctx context.Context) error {
}

func runMutateImage(ctx context.Context) error {
annotation := getAnnotation()
label := getLabel()

// parse the image name
if flagValues.image == "" {
return &ExitError{Code: 100, Message: "the 'image' argument must not be empty"}
}

options := getOptions(ctx)
ref := flagValues.image

if len(annotation) != 0 {
desc, err := crane.Head(ref, *options...)
if err != nil {
return fmt.Errorf("checking %s: %v", ref, err)
}

if desc.MediaType.IsIndex() {
return fmt.Errorf("mutating annotations on an index is not yet supported")
}
}

img, err := crane.Pull(ref, *options...)
imageName, err := name.ParseReference(flagValues.image)
if err != nil {
return fmt.Errorf("pulling %s: %v", ref, err)
return err
}

cfg, err := img.ConfigFile()
// parse annotations
annotations, err := splitKeyVals(getAnnotation())
if err != nil {
return fmt.Errorf("getting config: %v", err)
return err
}
cfg = cfg.DeepCopy()

// Set labels.
if cfg.Config.Labels == nil {
cfg.Config.Labels = map[string]string{}
// parse labels
labels, err := splitKeyVals(getLabel())
if err != nil {
return err
}

labels, err := splitKeyVals(label)
// prepare the registry options
options, err := image.GetOptions(ctx, imageName, flagValues.insecure, "")
if err != nil {
return err
}

for k, v := range labels {
cfg.Config.Labels[k] = v
// load the image or image index (usually multi-platform image)
var img containerreg.Image
var imageIndex containerreg.ImageIndex
if flagValues.push == "" {
log.Printf("Loading the image from the registry %q\n", imageName.String())
img, imageIndex, err = image.LoadImageOrImageIndexFromRegistry(imageName, options)
} else {
log.Printf("Loading the image from the directory %q\n", flagValues.push)
img, imageIndex, err = image.LoadImageOrImageIndexFromDirectory(flagValues.push)
}

annotations, err := splitKeyVals(annotation)
if err != nil {
log.Printf("Failed to load the image: %v\n", err)
return err
}

// Mutate and write image.
img, err = mutate.Config(img, cfg.Config)
if err != nil {
return fmt.Errorf("mutating config: %v", err)
if img != nil {
log.Printf("Loaded single image")
}

img = mutate.Annotations(img, annotations).(containerreg.Image)

digest, err := img.Digest()
if err != nil {
return fmt.Errorf("digesting new image: %v", err)
if imageIndex != nil {
log.Printf("Loaded image index")
}

r, err := name.ParseReference(ref)
// mutate the image
log.Println("Mutating the image")
img, imageIndex, err = image.MutateImageOrImageIndex(img, imageIndex, annotations, labels)
if err != nil {
return fmt.Errorf("parsing %s: %v", ref, err)
}

if _, ok := r.(name.Digest); ok {
ref = r.Context().Digest(digest.String()).String()
log.Printf("Failed to mutate the image: %v\n", err)
return err
}

if err := crane.Push(img, ref, *options...); err != nil {
return fmt.Errorf("pushing %s: %v", ref, err)
// push the image and determine the digest and size
log.Printf("Pushing the image to registry %q\n", imageName.String())
digest, size, err := image.PushImageOrImageIndex(imageName, img, imageIndex, options)
if err != nil {
log.Printf("Failed to push the image: %v\n", err)
return err
}

fmt.Printf(
"The image %s was mutated successfully. The new digest is: %s.\n",
flagValues.image, r.Context().Digest(digest.String()),
)

// Writing image digest to file
if resultFileImageDigest := flagValues.resultFileImageDigest; resultFileImageDigest != "" {
if err := ioutil.WriteFile(
resultFileImageDigest, []byte(digest.String()), 0644,
); err != nil {
if digest != "" && flagValues.resultFileImageDigest != "" {
if err := ioutil.WriteFile(flagValues.resultFileImageDigest, []byte(digest), 0400); err != nil {
return err
}
}

// Writing image size in bytes to file
if resultFileImageSize := flagValues.resultFileImageSize; resultFileImageSize != "" {
size, err := GetCompressedImageSize(img)
if err != nil {
return err
}

if err := ioutil.WriteFile(
resultFileImageSize, []byte(strconv.FormatInt(size, 10)), 0644,
); err != nil {
if size > 0 && flagValues.resultFileImageSize != "" {
if err := ioutil.WriteFile(flagValues.resultFileImageSize, []byte(strconv.FormatInt(size, 10)), 0400); err != nil {
return err
}
}

return nil
}

// GetCompressedImageSize calculate the compressed size of the image.
// By adding up the config and layer sizes we will get the
// total compressed size of the image
func GetCompressedImageSize(img containerreg.Image) (int64, error) {
manifest, err := img.Manifest()
if err != nil {
return 0, err
}

configSize := manifest.Config.Size

var layersSize int64
for _, layer := range manifest.Layers {
layersSize += layer.Size
}

return layersSize + configSize, nil
}

// splitKeyVals splits key value pairs which is in form hello=world
func splitKeyVals(kvPairs []string) (map[string]string, error) {
m := map[string]string{}
Expand All @@ -271,31 +205,3 @@ func splitKeyVals(kvPairs []string) (map[string]string, error) {

return m, nil
}

func getOptions(ctx context.Context) *[]crane.Option {
var options []crane.Option

options = append(options, crane.WithContext(ctx))

transport := http.DefaultTransport.(*http.Transport).Clone()
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: false,
MinVersion: tls.VersionTLS12,
}

var rt http.RoundTripper = transport
// Add any http headers if they are set in the config file.
cf, err := config.Load(os.Getenv("DOCKER_CONFIG"))
if err != nil {
log.Printf("failed to read config file: %v", err)
} else if len(cf.HTTPHeaders) != 0 {
rt = &headerTransport{
inner: rt,
httpHeaders: cf.HTTPHeaders,
}
}

options = append(options, crane.WithTransport(rt))

return &options
}
5 changes: 0 additions & 5 deletions cmd/mutate-image/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"io/ioutil"
"log"
"os"
"strconv"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
Expand Down Expand Up @@ -315,10 +314,6 @@ var _ = Describe("Image Mutate Resource", func() {
"--result-file-image-size",
filename,
)).To(BeNil())

size, err := GetCompressedImageSize(getImage(tag))
Expect(err).To(BeNil())
Expect(filecontent(filename)).To(Equal(strconv.FormatInt(size, 10)))
})
})
})
Expand Down
19 changes: 19 additions & 0 deletions deploy/crds/shipwright.io_buildruns.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ spec:
image:
description: Image is the reference of the image.
type: string
insecure:
description: Insecure defines whether the registry is not
secure
type: boolean
labels:
additionalProperties:
type: string
Expand Down Expand Up @@ -240,6 +244,10 @@ spec:
image:
description: Image is the reference of the image.
type: string
insecure:
description: Insecure defines whether the registry is not
secure
type: boolean
labels:
additionalProperties:
type: string
Expand Down Expand Up @@ -600,6 +608,9 @@ spec:
image:
description: Image is the reference of the image.
type: string
insecure:
description: Insecure defines whether the registry is not secure
type: boolean
labels:
additionalProperties:
type: string
Expand Down Expand Up @@ -799,6 +810,10 @@ spec:
image:
description: Image is the reference of the image.
type: string
insecure:
description: Insecure defines whether the registry is not
secure
type: boolean
labels:
additionalProperties:
type: string
Expand Down Expand Up @@ -946,6 +961,10 @@ spec:
image:
description: Image is the reference of the image.
type: string
insecure:
description: Insecure defines whether the registry is not
secure
type: boolean
labels:
additionalProperties:
type: string
Expand Down
6 changes: 6 additions & 0 deletions deploy/crds/shipwright.io_builds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ spec:
image:
description: Image is the reference of the image.
type: string
insecure:
description: Insecure defines whether the registry is not secure
type: boolean
labels:
additionalProperties:
type: string
Expand Down Expand Up @@ -221,6 +224,9 @@ spec:
image:
description: Image is the reference of the image.
type: string
insecure:
description: Insecure defines whether the registry is not secure
type: boolean
labels:
additionalProperties:
type: string
Expand Down
3 changes: 2 additions & 1 deletion docs/buildstrategies.md
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,8 @@ Contrary to the strategy `spec.parameters`, you can use system parameters and th
| ------------------------------ | ----------- |
| `$(params.shp-source-root)` | The absolute path to the directory that contains the user's sources. |
| `$(params.shp-source-context)` | The absolute path to the context directory of the user's sources. If the user specified no value for `spec.source.contextDir` in their `Build`, then this value will equal the value for `$(params.shp-source-root)`. Note that this directory is not guaranteed to exist at the time the container for your step is started, you can therefore not use this parameter as a step's working directory. |
| `$(params.shp-output-image)` | The URL of the image that the user wants to push as specified in the Build's `spec.output.image`, or the override from the BuildRun's `spec.output.image`. |
| `$(params.shp-output-directory)` | The absolute path to a directory that the build strategy should store the image in. |
| `$(params.shp-output-image)` | The URL of the image that the user wants to push as specified in the Build's `spec.output.image`, or the override from the BuildRun's `spec.output.image`. |

## System parameters vs Strategy Parameters Comparison

Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/build/v1alpha1/build_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ type Image struct {
// Image is the reference of the image.
Image string `json:"image"`

// Insecure defines whether the registry is not secure
//
// +optional
Insecure *bool `json:"insecure,omitempty"`

// Credentials references a Secret that contains credentials to access
// the image registry.
//
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/build/v1alpha1/zz_generated.deepcopy.go

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

Loading

0 comments on commit 57db7ce

Please sign in to comment.