Support custom registry on pushing image

This commit is contained in:
Lex Cao 2021-08-10 11:02:28 +08:00
parent ef474809e3
commit 82440ed8c0
10 changed files with 256 additions and 63 deletions

View File

@ -47,6 +47,7 @@ var (
ConvertReplicas int
ConvertController string
ConvertPushImage bool
ConvertPushImageRegistry string
ConvertOpt kobject.ConvertOptions
ConvertYAMLIndent int
@ -88,6 +89,7 @@ var convertCmd = &cobra.Command{
BuildRepo: ConvertBuildRepo,
BuildBranch: ConvertBuildBranch,
PushImage: ConvertPushImage,
PushImageRegistry: ConvertPushImageRegistry,
CreateDeploymentConfig: ConvertDeploymentConfig,
EmptyVols: ConvertEmptyVols,
Volumes: ConvertVolumes,
@ -147,6 +149,7 @@ func init() {
// Standard between the two
convertCmd.Flags().StringVar(&ConvertBuild, "build", "none", `Set the type of build ("local"|"build-config"(OpenShift only)|"none")`)
convertCmd.Flags().BoolVar(&ConvertPushImage, "push-image", false, "If we should push the docker image we built")
convertCmd.Flags().StringVar(&ConvertPushImageRegistry, "push-image-registry", "", "Specify registry for pushing image, which will override registry from image name.")
convertCmd.Flags().BoolVarP(&ConvertYaml, "yaml", "y", false, "Generate resource files into YAML format")
convertCmd.Flags().MarkDeprecated("yaml", "YAML is the default format now.")
convertCmd.Flags().MarkShorthandDeprecated("y", "YAML is the default format now.")

View File

@ -52,6 +52,7 @@ type ConvertOptions struct {
BuildBranch string
Build string
PushImage bool
PushImageRegistry string
CreateChart bool
GenerateYaml bool
GenerateJSON bool

View File

@ -1167,12 +1167,9 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
}
// Push the built image to the repo!
if opt.PushImage {
log.Infof("Push image enabled. Attempting to push image '%s'", service.Image)
err = transformer.PushDockerImage(service, name)
if err != nil {
return nil, errors.Wrapf(err, "Unable to push Docker image for service %v", name)
}
err = transformer.PushDockerImageWithOpt(service, name, opt)
if err != nil {
return nil, errors.Wrapf(err, "Unable to push Docker image for service %v", name)
}
}
@ -1316,12 +1313,9 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
}
// Push the built image to the repo!
if opt.PushImage {
log.Infof("Push image enabled. Attempting to push image '%s'", service.Image)
err = transformer.PushDockerImage(service, name)
if err != nil {
return nil, errors.Wrapf(err, "Unable to push Docker image for service %v", name)
}
err = transformer.PushDockerImageWithOpt(service, name, opt)
if err != nil {
return nil, errors.Wrapf(err, "Unable to push Docker image for service %v", name)
}
}

View File

@ -318,11 +318,9 @@ func (o *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.C
}
// Push the built container to the repo!
if opt.PushImage {
err = transformer.PushDockerImage(service, name)
if err != nil {
log.Fatalf("Unable to push Docker image for service %v: %v", name, err)
}
err = transformer.PushDockerImageWithOpt(service, name, opt)
if err != nil {
log.Fatalf("Unable to push Docker image for service %v: %v", name, err)
}
}

View File

@ -308,26 +308,47 @@ func BuildDockerImage(service kobject.ServiceConfig, name string) error {
return nil
}
// PushDockerImage pushes docker image
func PushDockerImage(service kobject.ServiceConfig, serviceName string) error {
log.Debugf("Pushing Docker image '%s'", service.Image)
// PushDockerImageWithOpt pushes docker image
func PushDockerImageWithOpt(service kobject.ServiceConfig, serviceName string, opt kobject.ConvertOptions) error {
if !opt.PushImage {
// Don't do anything if registry is specified but push is disabled, just WARN about it
if opt.PushImageRegistry != "" {
log.Warnf("Push image registry '%s' is specified but push image is disabled, skipping pushing to repository", opt.PushImageRegistry)
}
return nil
}
log.Infof("Push image is enabled. Attempting to push image '%s'", service.Image)
// Don't do anything if service.Image is blank, but at least WARN about it
// lse, let's push the image
// else, let's push the image
if service.Image == "" {
log.Warnf("No image name has been passed for service %s, skipping pushing to repository", serviceName)
return nil
}
// Connect to the Docker client
image, err := docker.ParseImage(service.Image, opt.PushImageRegistry)
if err != nil {
return err
}
client, err := docker.Client()
if err != nil {
return err
}
push := docker.Push{Client: *client}
err = push.PushImage(service.Image)
if opt.PushImageRegistry != "" {
log.Info("Push image registry is specified. Tag the image into registry firstly.")
tag := docker.Tag{Client: *client}
err = tag.TagImage(image)
if err != nil {
return err
}
}
push := docker.Push{Client: *client}
err = push.PushImage(image)
if err != nil {
return err
}

58
pkg/utils/docker/image.go Normal file
View File

@ -0,0 +1,58 @@
package docker
import (
"path"
dockerparser "github.com/novln/docker-parser"
)
// Image contains the basic information parsed from full image name
// see github.com/novln/docker-parser Reference
type Image struct {
Name string // the image's name (ie: debian[:8.2])
ShortName string // the image's name (ie: debian)
Tag string // the image's tag (or digest)
Registry string // the image's registry. (ie: host[:port])
Repository string // the image's repository. (ie: registry/name)
Remote string // the image's remote identifier. (ie: registry/name[:tag])
}
func NewImageFromParsed(parsed *dockerparser.Reference) Image {
return Image{
Name: parsed.Name(),
ShortName: parsed.ShortName(),
Tag: parsed.Tag(),
Registry: parsed.Registry(),
Repository: parsed.Repository(),
Remote: parsed.Remote(),
}
}
// ParseImage Using https://github.com/novln/docker-parser in order to parse the appropriate name and registry.
// 1. Return default registry when the registry is not specified from image
// 2. Return target registry when the registry is specified from command line
func ParseImage(fullImageName string, targetRegistry string) (Image, error) {
var image Image
// First parse to fill default fields for image
// See github.com/novln/docker-parser/docker/reference.go
parsedImage, err := dockerparser.Parse(fullImageName)
if err != nil {
return image, err
}
// Registry from command argument is high priority than parsed from name of image.
if targetRegistry != "" {
// Parse again for validating registry
fullImageName = path.Join(targetRegistry, parsedImage.Name())
parsedImage, err = dockerparser.Parse(fullImageName)
if err != nil {
return image, err
}
}
image = NewImageFromParsed(parsedImage)
return image, nil
}

View File

@ -0,0 +1,96 @@
package docker
import (
"reflect"
"testing"
)
func TestParseImage(t *testing.T) {
type args struct {
fullImageName string
targetRegistry string
}
tests := []struct {
name string
args args
want Image
wantErr bool
}{
{
"Given empty registry Then default registry expected",
args{
"foo/bar",
"",
},
Image{
"foo/bar:latest",
"foo/bar",
"latest",
"docker.io",
"docker.io/foo/bar",
"docker.io/foo/bar:latest",
},
false,
},
{
"Given registry from image Then parsed registry expected",
args{
"docker.io/foo/bar",
"",
},
Image{
"foo/bar:latest",
"foo/bar",
"latest",
"docker.io",
"docker.io/foo/bar",
"docker.io/foo/bar:latest",
},
false,
},
{
"Given target registry Then target registry expected",
args{
"foo/bar",
"localhost:5000",
},
Image{
"foo/bar:latest",
"foo/bar",
"latest",
"localhost:5000",
"localhost:5000/foo/bar",
"localhost:5000/foo/bar:latest",
},
false,
},
{
"Given registry from image and target registry Then target registry expected",
args{
"docker.io/foo/bar",
"localhost:5000",
},
Image{
"foo/bar:latest",
"foo/bar",
"latest",
"localhost:5000",
"localhost:5000/foo/bar",
"localhost:5000/foo/bar:latest",
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseImage(tt.args.fullImageName, tt.args.targetRegistry)
if (err != nil) != tt.wantErr {
t.Errorf("ParseImage() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ParseImage() got = %+v, want %+v", got, tt.want)
}
})
}
}

View File

@ -18,9 +18,9 @@ package docker
import (
"bytes"
"strings"
dockerlib "github.com/fsouza/go-dockerclient"
dockerparser "github.com/novln/docker-parser"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
@ -35,23 +35,15 @@ PushImage pushes a Docker image via the Docker API. Takes the image name,
parses the URL details and then push based on environment authentication
credentials.
*/
func (c *Push) PushImage(fullImageName string) error {
outputBuffer := bytes.NewBuffer(nil)
// Using https://github.com/novln/docker-parser in order to parse the appropriate
// name and registry.
parsedImage, err := dockerparser.Parse(fullImageName)
if err != nil {
return err
}
image, registry := parsedImage.Name(), parsedImage.Registry()
log.Infof("Pushing image '%s' to registry '%s'", image, registry)
func (c *Push) PushImage(image Image) error {
log.Infof("Pushing image '%s' to registry '%s'", image.Name, image.Registry)
// Let's setup the push and authentication options
outputBuffer := bytes.NewBuffer(nil)
options := dockerlib.PushImageOptions{
Name: fullImageName,
Registry: parsedImage.Registry(),
Tag: image.Tag,
Name: image.Repository,
Registry: image.Registry,
OutputStream: outputBuffer,
}
@ -63,34 +55,27 @@ func (c *Push) PushImage(fullImageName string) error {
log.Warn(errors.Wrap(err, "Unable to retrieve .docker/config.json authentication details. Check that 'docker login' works successfully on the command line."))
}
// Fallback to unauthenticated access in case if no auth credentials are retrieved
if credentials == nil || len(credentials.Configs) == 0 {
log.Info("Authentication credentials are not detected. Will try push without authentication.")
credentials = &dockerlib.AuthConfigurations{
Configs: map[string]dockerlib.AuthConfiguration{
registry: {},
},
}
// Handle legacy docker registry address
if strings.Contains(image.Registry, "docker.io") {
image.Registry = "https://index.docker.io/v1/"
}
// Push the image to the repository (based on the URL)
// We will iterate through all available authentication configurations until we find one that pushes successfully
// and then return nil.
if len(credentials.Configs) > 1 {
log.Info("Multiple authentication credentials detected. Will try each configuration.")
// Find the authentication matched to registry
auth, ok := credentials.Configs[image.Registry]
if !ok {
// Fallback to unauthenticated access in case if no auth credentials are retrieved
log.Infof("Authentication credential of registry '%s' is not found. Will try push without authentication.", image.Registry)
auth = dockerlib.AuthConfiguration{}
}
for k, v := range credentials.Configs {
log.Infof("Attempting authentication credentials '%s", k)
err = c.Client.PushImage(options, v)
if err != nil {
log.Errorf("Unable to push image '%s' to registry '%s'. Error: %s", image, registry, err)
} else {
log.Debugf("Image '%s' push output:\n%s", image, outputBuffer)
log.Infof("Successfully pushed image '%s' to registry '%s'", image, registry)
return nil
}
log.Debugf("Pushing image with options %+v", options)
err = c.Client.PushImage(options, auth)
if err != nil {
log.Errorf("Unable to push image '%s' to registry '%s'. Error: %s", image.Name, image.Registry, err)
return errors.New("unable to push docker image(s). Check that `docker login` works successfully on the command line")
}
return errors.New("unable to push docker image(s). Check that `docker login` works successfully on the command line")
log.Debugf("Image '%+v' push output:\n%s", image, outputBuffer)
log.Infof("Successfully pushed image '%s' to registry '%s'", image.Name, image.Registry)
return nil
}

29
pkg/utils/docker/tag.go Normal file
View File

@ -0,0 +1,29 @@
package docker
import (
dockerlib "github.com/fsouza/go-dockerclient"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
// Tag will provide methods for interaction with API regarding tagging images
type Tag struct {
Client dockerlib.Client
}
func (c *Tag) TagImage(image Image) error {
options := dockerlib.TagImageOptions{
Tag: image.Tag,
Repo: image.Repository,
}
log.Infof("Tagging image '%s' into repository '%s'", image.Name, image.Repository)
err := c.Client.TagImage(image.ShortName, options)
if err != nil {
log.Errorf("Unable to tag image '%s' into repository '%s'. Error: %s", image.Name, image.Registry, err)
return errors.New("unable to tag docker image(s)")
}
log.Infof("Successfully tagged image '%s'", image.Remote)
return nil
}

View File

@ -95,3 +95,11 @@ convert::check_artifacts_generated "kompose --build local -f $KOMPOSE_ROOT/scrip
# Test build v3 relative compose file with context
relative_path=$(realpath --relative-to="$PWD" "$KOMPOSE_ROOT/script/test/fixtures/buildconfig/docker-compose-v3.yml")
convert::check_artifacts_generated "kompose --build local -f $relative_path convert -o $TEMP_DIR/output_file" "$TEMP_DIR/output_file"
#####
# Test the build config with push image
# Default behavior with push image disabled
cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/buildconfig/docker-compose-build-no-image.yml -o $TEMP_DIR/output_file convert --build=local --push-image-registry=whatever"
convert::expect_warning "$cmd" "Push image registry 'whatever' is specified but push image is disabled, skipping pushing to repository"
# TODO Push image to docker.io as default. Then verify push success
# TODO Push image to a private registry. Then verify push success