-
Notifications
You must be signed in to change notification settings - Fork 691
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Implement new pusher * Fix type error * Fix styling to resolve comments * Enhanced error messages
- Loading branch information
1 parent
574278c
commit 5647f4f
Showing
3 changed files
with
308 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# Copyright 2017 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. | ||
# ### | ||
# Build file for new puller binary based on go-containerregistry backend. | ||
load("@bazel_gazelle//:def.bzl", "gazelle") | ||
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") | ||
|
||
# gazelle:prefix github.com/bazelbuild/rules_docker | ||
gazelle(name = "gazelle") | ||
|
||
go_library( | ||
name = "go_pusher", | ||
srcs = ["pusher.go"], | ||
importpath = "github.com/bazelbuild/rules_docker", | ||
visibility = ["//visibility:private"], | ||
deps = [ | ||
"//container/go/pkg/oci:go_default_library", | ||
"@com_github_google_go_containerregistry//pkg/authn:go_default_library", | ||
"@com_github_google_go_containerregistry//pkg/name:go_default_library", | ||
"@com_github_google_go_containerregistry//pkg/v1:go_default_library", | ||
"@com_github_google_go_containerregistry//pkg/v1/remote:go_default_library", | ||
"@com_github_google_go_containerregistry//pkg/v1/tarball:go_default_library", | ||
"@com_github_pkg_errors//:go_default_library", | ||
], | ||
) | ||
|
||
go_binary( | ||
name = "pusher", | ||
embed = [":go_pusher"], | ||
visibility = ["//visibility:public"], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// 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 pushes an image to a Docker Registry. | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"log" | ||
"os" | ||
|
||
"github.com/bazelbuild/rules_docker/container/go/pkg/oci" | ||
"github.com/google/go-containerregistry/pkg/authn" | ||
"github.com/google/go-containerregistry/pkg/name" | ||
v1 "github.com/google/go-containerregistry/pkg/v1" | ||
"github.com/google/go-containerregistry/pkg/v1/remote" | ||
"github.com/google/go-containerregistry/pkg/v1/tarball" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
var ( | ||
dst = flag.String("dst", "", "The destination location including repo and digest/tag of the docker image to push. Supports fully-qualified tag or digest references.") | ||
src = flag.String("src", "", "Path to the directory which has the image index that will be pushed.") | ||
format = flag.String("format", "", "The format of the source image, oci|docker. The docker format should be a tarball of the image as generated by docker save.") | ||
clientConfigDir = flag.String("client-config-dir", "", "The path to the directory where the client configuration files are located. Overiddes the value from DOCKER_CONFIG.") | ||
) | ||
|
||
func main() { | ||
flag.Parse() | ||
log.Println("Running the Image Pusher to push images to a Docker Registry...") | ||
|
||
if *dst == "" { | ||
log.Fatalln("Required option -dst was not specified.") | ||
} | ||
if *src == "" { | ||
log.Fatalln("Required option -src was not specified.") | ||
} | ||
if *format == "" { | ||
log.Fatalln("Required option -format was not specified.") | ||
} | ||
|
||
// If the user provided a client config directory, instruct the keychain resolver | ||
// to use it to look for the docker client config. | ||
if *clientConfigDir != "" { | ||
os.Setenv("DOCKER_CONFIG", *clientConfigDir) | ||
} | ||
|
||
img, err := readImage(*src, *format) | ||
if err != nil { | ||
log.Fatalf("Error reading from %s: %v", *src, err) | ||
} | ||
|
||
if err := push(*dst, img); err != nil { | ||
log.Fatalf("Error pushing image to %s: %v", *dst, err) | ||
} | ||
|
||
log.Println("Successfully pushed %s image from %s to %s", *format, *src, *dst) | ||
} | ||
|
||
// push pushes the given image to the given destination. | ||
// NOTE: This function is adapted from https://github.com/google/go-containerregistry/blob/master/pkg/crane/push.go | ||
// with modification for option to push OCI layout or Docker tarball format . | ||
// Push the given image to destination <dst>. | ||
func push(dst string, img v1.Image) error { | ||
// Push the image to dst. | ||
ref, err := name.ParseReference(dst) | ||
if err != nil { | ||
return errors.Wrapf(err, "error parsing %q as an image reference", dst) | ||
} | ||
|
||
if err := remote.Write(ref, img, remote.WithAuthFromKeychain(authn.DefaultKeychain)); err != nil { | ||
return errors.Wrapf(err, "unable to push image to %s", dst) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// readImage returns a v1.Image after reading an OCI index or a Docker tarball from src. | ||
func readImage(src, format string) (v1.Image, error) { | ||
if format == "oci" { | ||
return oci.Read(src) | ||
} | ||
if format == "docker" { | ||
return tarball.ImageFromPath(src, nil) | ||
} | ||
return nil, errors.Errorf("unknown image format %q", format) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
# Copyright 2017 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. | ||
"""An new implementation of container_push based on google/containerregistry using google/go-containerregistry. | ||
This wraps the rules_docker.container.go.cmd.pusher.pusher executable in a | ||
Bazel rule for publishing images. | ||
""" | ||
|
||
load("@bazel_skylib//lib:dicts.bzl", "dicts") | ||
load("@io_bazel_rules_docker//container:providers.bzl", "PushInfo") | ||
load( | ||
"//container:layer_tools.bzl", | ||
_layer_tools = "tools", | ||
) | ||
load( | ||
"//skylib:path.bzl", | ||
"runfile", | ||
) | ||
|
||
def _get_runfile_path(ctx, f): | ||
return "${RUNFILES}/%s" % runfile(ctx, f) | ||
|
||
def _impl(ctx): | ||
"""Core implementation of new_container_push.""" | ||
|
||
# TODO (xiaohegong): 1) Possible optimization for efficiently pushing intermediate | ||
# representation, similar with the old python implementation, e.g., push-by-layer. | ||
# Some of the digester arguments omitted from before: --tarball, --config, --manifest, --digest, --layer, --oci. | ||
# 2) The old implementation outputs a {image_name}.digest for compatibility with container_digest, omitted for now. | ||
# 3) Use and implementation of attr.stamp. | ||
|
||
pusher_args = [] | ||
|
||
# Parse and get destination registry to be pushed to | ||
registry = ctx.expand_make_variables("registry", ctx.attr.registry, {}) | ||
repository = ctx.expand_make_variables("repository", ctx.attr.repository, {}) | ||
tag = ctx.expand_make_variables("tag", ctx.attr.tag, {}) | ||
|
||
# If a tag file is provided, override <tag> with tag value | ||
runfiles_tag_file = [] | ||
if ctx.file.tag_file: | ||
tag = "$(cat {})".format(_get_runfile_path(ctx, ctx.file.tag_file)) | ||
runfiles_tag_file = [ctx.file.tag_file] | ||
|
||
pusher_args += ["-dst", "{registry}/{repository}:{tag}".format( | ||
registry = registry, | ||
repository = repository, | ||
tag = tag, | ||
)] | ||
|
||
# Find and set src to correct paths depending the image format to be pushed | ||
if ctx.attr.format == "oci": | ||
found = False | ||
for f in ctx.files.image: | ||
if f.basename == "index.json": | ||
pusher_args += ["-src", "{index_dir}".format( | ||
index_dir = f.dirname, | ||
)] | ||
found = True | ||
if not found: | ||
fail("Did not find an index.json in the image attribute {} specified to {}".format(ctx.attr.image, ctx.label)) | ||
if ctx.attr.format == "docker": | ||
if len(ctx.files.image) == 0: | ||
fail("Attribute image {} to {} did not contain an image tarball".format(ctx.attr.image, ctx.label)) | ||
if len(ctx.files.image) > 1: | ||
fail("Attribute image {} to {} had {} files. Expected exactly 1".format(ctx.attr.image, ctx.label, len(ctx.files.image))) | ||
pusher_args += ["-src", str(ctx.files.image[0].path)] | ||
|
||
pusher_args += ["-format", str(ctx.attr.format)] | ||
|
||
# If the docker toolchain is configured to use a custom client config | ||
# directory, use that instead | ||
toolchain_info = ctx.toolchains["@io_bazel_rules_docker//toolchains/docker:toolchain_type"].info | ||
if toolchain_info.client_config != "": | ||
pusher_args += ["-client-config-dir", str(toolchain_info.client_config)] | ||
|
||
ctx.actions.expand_template( | ||
template = ctx.file._tag_tpl, | ||
substitutions = { | ||
"%{args}": " ".join(pusher_args), | ||
"%{container_pusher}": _get_runfile_path(ctx, ctx.executable._pusher), | ||
}, | ||
output = ctx.outputs.executable, | ||
is_executable = True, | ||
) | ||
|
||
runfiles = ctx.runfiles(files = [ctx.executable._pusher] + runfiles_tag_file) | ||
runfiles = runfiles.merge(ctx.attr._pusher[DefaultInfo].default_runfiles) | ||
|
||
return [ | ||
DefaultInfo( | ||
executable = ctx.outputs.executable, | ||
runfiles = runfiles, | ||
), | ||
PushInfo( | ||
registry = registry, | ||
repository = repository, | ||
tag = tag, | ||
), | ||
] | ||
|
||
# Pushes a container image to a registry. | ||
new_container_push = rule( | ||
attrs = dicts.add({ | ||
"format": attr.string( | ||
default = "oci", | ||
values = [ | ||
"oci", | ||
"docker", | ||
], | ||
doc = "The form to push: docker or oci, default to 'oci'.", | ||
), | ||
"image": attr.label( | ||
allow_files = True, | ||
mandatory = True, | ||
doc = "The label of the image to push.", | ||
), | ||
"registry": attr.string( | ||
mandatory = True, | ||
doc = "The registry to which we are pushing.", | ||
), | ||
"repository": attr.string( | ||
mandatory = True, | ||
doc = "The name of the image.", | ||
), | ||
"stamp": attr.bool( | ||
default = False, | ||
mandatory = False, | ||
), | ||
"tag": attr.string( | ||
default = "latest", | ||
doc = "(optional) The tag of the image, default to 'latest'.", | ||
), | ||
"tag_file": attr.label( | ||
allow_single_file = True, | ||
doc = "(optional) The label of the file with tag value. Overrides 'tag'.", | ||
), | ||
"_digester": attr.label( | ||
default = "@containerregistry//:digester", | ||
cfg = "host", | ||
executable = True, | ||
), | ||
"_pusher": attr.label( | ||
default = Label("@io_bazel_rules_docker//container/go/cmd/pusher:pusher"), | ||
cfg = "host", | ||
executable = True, | ||
allow_files = True, | ||
), | ||
"_tag_tpl": attr.label( | ||
default = Label("//container:push-tag.sh.tpl"), | ||
allow_single_file = True, | ||
), | ||
}, _layer_tools), | ||
executable = True, | ||
toolchains = ["@io_bazel_rules_docker//toolchains/docker:toolchain_type"], | ||
implementation = _impl, | ||
) |