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

Support index manifest to push multi-arch container images #2087

Closed
wants to merge 5 commits into from
Closed
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
46 changes: 35 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ Generated API documentation is in the docs folder, or you can browse it online a
* [container_load](/docs/container.md#container_load)
* [container_pull](/docs/container.md#container_pull) ([example](#container_pull))
* [container_push](/docs/container.md#container_push) ([example](#container_push))
* [container_push_index](/docs/container.md#container_push_index) ([example](#container_push_index))

These rules used to be `docker_build`, `docker_push`, etc. and the aliases for
these (mostly) legacy names still exist largely for backwards-compatibility. We
also have **early-stage** `oci_image`, `oci_push`, etc. aliases for folks that
enjoy the consistency of a consistent rule prefix. The only place the
format-specific names currently do any more than alias things is in `foo_push`,
where they also specify the appropriate format as which to publish the image.
these (mostly) legacy names still exist largely for backwards-compatibility.
The only place the format-specific names currently do any more than alias things
is in `foo_push`, where they also specify the appropriate format as which
to publish the image.

### Overview

Expand Down Expand Up @@ -283,9 +283,9 @@ to use `container_push` with custom docker authentication credentials.
## Varying image names

A common request from folks using
`container_push`, `container_bundle`, or `container_image` is to
be able to vary the tag that is pushed or embedded. There are two options
at present for doing this.
`container_push`, `container_push_index`, `container_bundle`,
or `container_image` is to be able to vary the tag that is pushed
or embedded. There are two options at present for doing this.

### Stamping

Expand Down Expand Up @@ -329,7 +329,6 @@ A common example is to provide the current git SHA, with

That flag is typically passed in the `.bazelrc` file, see for example [`.bazelrc` in kubernetes](https://github.com/kubernetes/kubernetes/blob/81ce94ae1d8f5d04058eeb214e9af498afe78ff2/build/root/.bazelrc#L6).


### Make variables

The second option is to employ `Makefile`-style variables:
Expand Down Expand Up @@ -1138,13 +1137,14 @@ container_push(
)
```

We also support the `docker_push` (from `docker/docker.bzl`) and `oci_push`
(from `oci/oci.bzl`) aliases, which bake in the `format = "..."` attribute.
We also support the `docker_push` (from `docker/docker.bzl`) aliases,
which bake in the `format = "Docker"` attribute.

See [here](#container_push-custom-client-configuration) for an example of how
to use container_push with custom docker authentication credentials.

### container_push (Custom client configuration)

If you wish to use container_push using custom docker authentication credentials,
in `WORKSPACE`:

Expand Down Expand Up @@ -1185,6 +1185,25 @@ container_push(
)
```

### container_push_index

This target pushes on `bazel run :push_foo_all`:

``` python
container_push_index(
name = "push_foo_all",
images = {
":foo_amd64": "linux/amd64",
":foo_arm64": "linux/arm64/v8",
":foo_ppc64le": "linux/ppc64le",
},
format = "OCI",
registry = "gcr.io",
repository = "my-project/my-image",
tag = "{STABLE_VERSION}",
)
```

### container_pull (DockerHub)

In `WORKSPACE`:
Expand Down Expand Up @@ -1342,6 +1361,11 @@ shared layers and letting them diverge could result in sub-optimal push and pull

**MOVED**: See [docs/container.md](/docs/container.md#container_push)

<a name="container_push_index"></a>
## container_push_index

**MOVED**: See [docs/container.md](/docs/container.md#container_push_index)

<a name="container_layer"></a>
## container_layer

Expand Down
7 changes: 6 additions & 1 deletion container/container.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ load("//container:import.bzl", _container_import = "container_import")
load("//container:layer.bzl", _container_layer = "container_layer")
load("//container:load.bzl", _container_load = "container_load")
load("//container:pull.bzl", _container_pull = "container_pull")
load("//container:push.bzl", _container_push = "container_push")
load(
"//container:push.bzl",
_container_push = "container_push",
_container_push_index = "container_push_index",
)

# Explicitly re-export the functions
container_bundle = _container_bundle
Expand All @@ -31,6 +35,7 @@ container_layer = _container_layer
container_import = _container_import
container_pull = _container_pull
container_push = _container_push
container_push_index = _container_push_index
container_load = _container_load

container = struct(
Expand Down
7 changes: 6 additions & 1 deletion container/container.docs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ load("//container:import.bzl", _container_import = "container_import")
load("//container:layer.bzl", _container_layer = "container_layer_")
load("//container:load.bzl", _container_load = "container_load")
load("//container:pull.bzl", _container_pull = "container_pull")
load("//container:push.bzl", _container_push = "container_push_")
load(
"//container:push.bzl",
_container_push = "container_push_",
_container_push_index = "container_push_index_",
)

# Explicitly re-export the functions
container_bundle = _container_bundle
Expand All @@ -26,4 +30,5 @@ container_layer = _container_layer
container_import = _container_import
container_pull = _container_pull
container_push = _container_push
container_push_index = _container_push_index
container_load = _container_load
2 changes: 1 addition & 1 deletion container/go/cmd/digester/digester.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//////////////////////////////////////////////////////////////////////
//This binary implements the ability to load a docker image, calculate its image manifest sha256 hash and output a digest file.
// This binary implements the ability to load a docker image, calculate its image manifest sha256 hash and output a digest file.
package main

import (
Expand Down
20 changes: 20 additions & 0 deletions container/go/cmd/digester_index/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")

go_library(
name = "go_default_library",
srcs = ["digester_index.go"],
importpath = "github.com/bazelbuild/rules_docker/container/go/cmd/digester_index",
visibility = ["//visibility:private"],
deps = [
"//container/go/pkg/compat:go_default_library",
"//container/go/pkg/oci:go_default_library",
"//container/go/pkg/utils:go_default_library",
"@com_github_google_go_containerregistry//pkg/v1:go_default_library",
],
)

go_binary(
name = "digester_index",
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)
194 changes: 194 additions & 0 deletions container/go/cmd/digester_index/digester_index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// Copyright 2015 The Bazel Authors. All rights reserved.
//
// 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.
//////////////////////////////////////////////////////////////////////
// This binary implements the ability to load a docker image index, calculate its image manifest sha256 hash and output a digest file.
package main

import (
"bytes"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"os"

v1 "github.com/google/go-containerregistry/pkg/v1"

"github.com/bazelbuild/rules_docker/container/go/pkg/compat"
"github.com/bazelbuild/rules_docker/container/go/pkg/oci"
"github.com/bazelbuild/rules_docker/container/go/pkg/utils"
)

type applicationArgs struct {
Format string
OutputFilePath string
Images []utils.ImageIndexArgs
}

func parseArgs(argv []string) (*applicationArgs, error) {
This conversation was marked as resolved.
Show resolved Hide resolved
var args applicationArgs

appName := argv[0]

fl := flag.NewFlagSet(appName, flag.ContinueOnError)
fl.StringVar(
&args.OutputFilePath, "dst", "",
"The destination location of the digest file to write to.",
)
fl.StringVar(
&args.Format, "format", "",
"The format of the uploaded image (Docker or OCI).",
)

if err := fl.Parse(argv[1:]); err != nil {
return nil, err
}

if args.OutputFilePath == "" {
return nil, errors.New("required options -dst was not specified")
}

if args.Format == "" {
return nil, errors.New("required options -format was not specified")
}

pargs := fl.Args()

for len(pargs) > 0 {
var ia utils.ImageIndexArgs

npargs, err := ia.Parse(appName, pargs)
if err != nil {
return nil, err
}

args.Images = append(args.Images, ia)

pargs = npargs
}

return &args, nil
}

func main() {
args, err := parseArgs(os.Args)
if err != nil {
if err == flag.ErrHelp {
os.Exit(0)
}

log.Fatalf("Error: %v", err)
}

if err := run(args); err != nil {
log.Fatalf("Error: %v", err)
}
}

func run(args *applicationArgs) error {
if len(args.Images) == 0 {
log.Println("No provided images.")
return nil
}

ii, err := readImageIndex(args)
if err != nil {
return fmt.Errorf("read image index: %v", err)
}

digest, err := ii.Digest()
if err != nil {
return fmt.Errorf("can't get digest of image index: %v", err)
This conversation was marked as resolved.
Show resolved Hide resolved
}

var content bytes.Buffer
fmt.Fprintln(&content, digest.String())

im, err := ii.IndexManifest()
if err != nil {
return fmt.Errorf("can't get index manifest: %v", err)
This conversation was marked as resolved.
Show resolved Hide resolved
}

for _, manifest := range im.Manifests {
platform := manifest.Platform
if platform == nil {
platform = &v1.Platform{
Architecture: "amd64",
OS: "linux",
}
}

os := platform.OS
if os == "" {
os = "linux"
}

arch := platform.Architecture
if arch == "" {
arch = "amd64"
}

platformIdent := os + "/" + arch
if platform.Variant != "" {
platformIdent += "/" + platform.Variant
}

fmt.Fprintf(&content, "%s\t%s\n", platformIdent, manifest.Digest)
}

if err := ioutil.WriteFile(args.OutputFilePath, content.Bytes(), os.ModePerm); err != nil {
return fmt.Errorf("error outputting digest file to %s: %v", args.OutputFilePath, err)
This conversation was marked as resolved.
Show resolved Hide resolved
}

return nil
}

func readImageIndex(args *applicationArgs) (v1.ImageIndex, error) {
platforms := make([]v1.Platform, len(args.Images))
images := make([]v1.Image, len(args.Images))

for i, imgArgs := range args.Images {
parts, err := compat.ImagePartsFromArgs(imgArgs.Config, imgArgs.Manifest, imgArgs.Tarball, imgArgs.Layers)
if err != nil {
return nil, err
}

img, err := compat.ReadImage(parts)
if err != nil {
return nil, err
}

platforms[i] = v1.Platform{
OS: imgArgs.Platform.OS,
Architecture: imgArgs.Platform.Arch,
Variant: imgArgs.Platform.Variant,
}
images[i] = img
}

ii, err := compat.NewImageIndex(platforms, images)
if err != nil {
return nil, err
}

if args.Format == "OCI" {
ii, err = oci.AsOCIImageIndex(ii)
if err != nil {
return nil, fmt.Errorf("failed to convert image index to OCI format: %v", err)
This conversation was marked as resolved.
Show resolved Hide resolved
This conversation was marked as resolved.
Show resolved Hide resolved
}
}

return ii, nil
}
Loading