forked from LaconicNetwork/kompose
Support custom registry on pushing image
This commit is contained in:
parent
ef474809e3
commit
82440ed8c0
@ -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.")
|
||||
|
||||
@ -52,6 +52,7 @@ type ConvertOptions struct {
|
||||
BuildBranch string
|
||||
Build string
|
||||
PushImage bool
|
||||
PushImageRegistry string
|
||||
CreateChart bool
|
||||
GenerateYaml bool
|
||||
GenerateJSON bool
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
58
pkg/utils/docker/image.go
Normal 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
|
||||
}
|
||||
96
pkg/utils/docker/image_test.go
Normal file
96
pkg/utils/docker/image_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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
29
pkg/utils/docker/tag.go
Normal 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
|
||||
}
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user