Skip to content
Merged
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
89 changes: 89 additions & 0 deletions config/docker/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package docker

import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
)

const (
v2HubRegistryURL string = "https://registry.hub.docker.com/v2/"
v1RegistryURL string = "https://index.docker.io/v1/" // Default registry
v2RegistryURL string = "https://index.docker.io/v2/" // v2 registry is not supported
)

type (
Auth struct {
Auth string `json:"auth"`
}

Config struct {
Auths map[string]Auth `json:"auths"`
CredHelpers map[string]string `json:"credHelpers,omitempty"`
}
)

type RegistryCredentials struct {
Registry string
Username string
Password string
}

func NewConfig() *Config {
return &Config{
Auths: make(map[string]Auth),
CredHelpers: make(map[string]string),
}
}

func (c *Config) SetAuth(registry, username, password string) {
authBytes := []byte(username + ":" + password)
encodedString := base64.StdEncoding.EncodeToString(authBytes)
c.Auths[registry] = Auth{Auth: encodedString}
}

func (c *Config) SetCredHelper(registry, helper string) {
c.CredHelpers[registry] = helper
}

func (c *Config) CreateDockerConfigJson(credentials []RegistryCredentials) ([]byte, error) {
for _, cred := range credentials {
if cred.Registry != "" && strings.Contains(cred.Registry, "docker") {

if cred.Username == "" {
return nil, fmt.Errorf("Username must be specified for registry: %s", cred.Registry)
}
if cred.Password == "" {
return nil, fmt.Errorf("Password must be specified for registry: %s", cred.Registry)
}
c.SetAuth(cred.Registry, cred.Username, cred.Password)
}
}

jsonBytes, err := json.Marshal(c)
if err != nil {
return nil, errors.New("failed to serialize docker config json")
}

return jsonBytes, nil
}

func WriteDockerConfig(data []byte, path string) (string error) {
err := os.MkdirAll(path, 0600)
if err != nil {
if !os.IsExist(err) {
return errors.New("failed to create %s directory")
}
}

filePath := path + "/config.json"
err = ioutil.WriteFile(filePath, data, 0644)
if err != nil {
return errors.New("failed to create docker config file at %s")
}
return nil
}
64 changes: 64 additions & 0 deletions config/docker/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package docker

import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
)

const (
RegistryV1 string = "https://index.docker.io/v1/"
RegistryV2 string = "https://index.docker.io/v2/"
RegistryECRPublic string = "public.ecr.aws"
)

func TestConfig(t *testing.T) {
c := NewConfig()
assert.NotNil(t, c.Auths)
assert.NotNil(t, c.CredHelpers)

c.SetAuth(RegistryV1, "test", "password")
expectedAuth := Auth{Auth: "dGVzdDpwYXNzd29yZA=="}
assert.Equal(t, expectedAuth, c.Auths[RegistryV1])

c.SetCredHelper(RegistryECRPublic, "ecr-login")
assert.Equal(t, "ecr-login", c.CredHelpers[RegistryECRPublic])

tempDir, err := ioutil.TempDir("", "docker-config-test")
assert.NoError(t, err)
defer os.RemoveAll(tempDir)

credentials := []RegistryCredentials{
{
Registry: "https://index.docker.io/v1/",
Username: "user1",
Password: "pass1",
},
{
Registry: "gcr.io",
Username: "user2",
Password: "pass2",
},
}

jsonBytes, err := c.CreateDockerConfigJson(credentials)
assert.NoError(t, err)

configPath := filepath.Join(tempDir, "config.json")
err = ioutil.WriteFile(configPath, jsonBytes, 0644)
assert.NoError(t, err)

data, err := ioutil.ReadFile(configPath)
assert.NoError(t, err)

var configFromFile Config
err = json.Unmarshal(data, &configFromFile)
assert.NoError(t, err)

assert.Equal(t, c.Auths, configFromFile.Auths)
assert.Equal(t, c.CredHelpers, configFromFile.CredHelpers)
}
139 changes: 83 additions & 56 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"time"

"github.com/drone-plugins/drone-plugin-lib/drone"
"github.com/drone-plugins/drone-buildx/config/docker"
)

type (
Expand Down Expand Up @@ -92,7 +93,7 @@ type (
CardPath string // Card path to write file to
MetadataFile string // Location to write the metadata file
ArtifactFile string // Artifact path to write file to
CacheMetricsFile string // Location to write the cache metrics file
CacheMetricsFile string // Location to write the cache metrics file
BaseImageRegistry string // Docker registry to pull base image
BaseImageUsername string // Docker registry username to pull base image
BaseImagePassword string // Docker registry password to pull base image
Expand Down Expand Up @@ -169,10 +170,57 @@ func (p Plugin) Exec() error {
os.MkdirAll(dockerHome, 0600)

path := filepath.Join(dockerHome, "config.json")
err := os.WriteFile(path, []byte(p.Login.Config), 0600)
file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
return fmt.Errorf("Error writing config.json: %s", err)
}
err = os.WriteFile(path, []byte(p.Login.Config), 0600)
if err != nil {
return fmt.Errorf("Error writing config.json: %s", err)
}
file.Close()
}

// add base image docker credentials to the existing config file, else create new
if p.BaseImagePassword != "" {
json, err := setDockerAuth(p.Login.Username, p.Login.Password, p.Login.Registry,
p.BaseImageUsername, p.BaseImagePassword, p.BaseImageRegistry)
if err != nil {
return fmt.Errorf("Failed to set authentication in docker config %s", err)
}
os.MkdirAll(dockerHome, 0600)
path := filepath.Join(dockerHome, "config.json")
file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
return fmt.Errorf("Error opening config.json: %s", err)
}
defer file.Close()
_, err = file.Write(json)
if err != nil {
return fmt.Errorf("Error writing config.json: %s", err)
}
}
// login to the Docker registry
if p.Login.Password != "" {
cmd := commandLogin(p.Login)
raw, err := cmd.CombinedOutput()
if err != nil {
out := string(raw)
out = strings.Replace(out, "WARNING! Using --password via the CLI is insecure. Use --password-stdin.", "", -1)
fmt.Println(out)
return fmt.Errorf("Error authenticating: exit status 1")
}
} else if p.Login.AccessToken != "" {
cmd := commandLoginAccessToken(p.Login, p.Login.AccessToken)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("error logging in to Docker registry: %s", err)
}
if strings.Contains(string(output), "Login Succeeded") {
fmt.Println("Login successful")
} else {
return fmt.Errorf("login did not succeed")
}
}

// cache export feature is currently not supported for docker driver hence we have to create docker-container driver
Expand Down Expand Up @@ -206,52 +254,8 @@ func (p Plugin) Exec() error {
cmds = append(cmds, commandVersion()) // docker version
cmds = append(cmds, commandInfo()) // docker info

differentBaseRegistry := p.BaseImagePassword != ""
// login to base image registry
baseImageLogin := Login{
Registry: p.BaseImageRegistry,
Username: p.BaseImageUsername,
Password: p.BaseImagePassword,
}
var cmdPushLogin, cmdBaseImageLogin *exec.Cmd
if p.Login.Password != "" {
cmdPushLogin = commandLogin(p.Login)
} else if p.Login.AccessToken != "" {
cmdPushLogin = commandLoginAccessToken(p.Login, p.Login.AccessToken)
}

// login to the registry when different base image registry not found
if !differentBaseRegistry {
raw, err := cmdPushLogin.CombinedOutput()
if err != nil {
out := string(raw)
out = strings.Replace(out, "WARNING! Using --password via the CLI is insecure. Use --password-stdin.", "", -1)
fmt.Println(out)
return fmt.Errorf("error logging in to Docker registry: %s", err)
}
if strings.Contains(string(raw), "Login Succeeded") {
fmt.Println("Login successful")
} else {
return fmt.Errorf("login did not succeed")
}
} else {
cmdBaseImageLogin = commandLogin(baseImageLogin)
// 1. append login command for base image docker registry if found
cmds = append(cmds, cmdBaseImageLogin)
}

// 2. Command to only build tag with and without push options
//- as for push there is a possibility of authentication to different registry
cmds = append(cmds, commandBuildx(p.Build, p.Builder, p.Dryrun, differentBaseRegistry, p.MetadataFile)) // docker build

// 3. add cmds to login to push registry and push the tag when different base image registry is found
if differentBaseRegistry {
cmds = append(cmds, cmdPushLogin)
// 4. command to only push the image, if dryrun not set
if !p.Dryrun {
cmds = append(cmds, commandPush(p.Build, p.Build.Tags[0]))
}
}
// Command to build, tag and push
cmds = append(cmds, commandBuildx(p.Build, p.Builder, p.Dryrun, p.MetadataFile)) // docker build

// execute all commands in batch mode.
for _, cmd := range cmds {
Expand Down Expand Up @@ -372,6 +376,35 @@ func commandLogin(login Login) *exec.Cmd {
return cmd
}

// helper function to set the credentials
func setDockerAuth(username, password, registry, baseImageUsername,
baseImagePassword, baseImageRegistry string) ([]byte, error) {
var credentials []docker.RegistryCredentials
// add only docker registry to the config
dockerConfig := docker.NewConfig()
if password != "" {
pushToRegistryCreds := docker.RegistryCredentials{
Registry: registry,
Username: username,
Password: password,
}
// push registry auth
credentials = append(credentials, pushToRegistryCreds)
}

if baseImageRegistry != "" {
pullFromRegistryCreds := docker.RegistryCredentials{
Registry: baseImageRegistry,
Username: baseImageUsername,
Password: baseImagePassword,
}
// base image registry auth
credentials = append(credentials, pullFromRegistryCreds)
}
// Creates docker config for both the registries used for authentication
return dockerConfig.CreateDockerConfigJson(credentials)
}

// helper to login via access token
func commandLoginAccessToken(login Login, accessToken string) *exec.Cmd {
cmd := exec.Command(dockerExe,
Expand Down Expand Up @@ -414,7 +447,7 @@ func commandInfo() *exec.Cmd {
}

// helper function to create the docker buildx command.
func commandBuildx(build Build, builder Builder, dryrun, differentBaseRegistry bool, metadataFile string) *exec.Cmd {
func commandBuildx(build Build, builder Builder, dryrun bool, metadataFile string) *exec.Cmd {
args := []string{
"buildx",
"build",
Expand All @@ -433,13 +466,7 @@ func commandBuildx(build Build, builder Builder, dryrun, differentBaseRegistry b
args = append(args, "--load")
}
} else {
// check to keep the old behaviour unchanged, i.e one registry to pull and push the artifact
if !differentBaseRegistry {
args = append(args, "--push")
} else {
// --load will keep the built image with the specified tag locally to be pushed later
args = append(args, "--load")
}
args = append(args, "--push")
}
args = append(args, build.Context)
if metadataFile != "" {
Expand Down
2 changes: 1 addition & 1 deletion docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ func TestCommandBuildx(t *testing.T) {
tc := tc

t.Run(tc.name, func(t *testing.T) {
cmd := commandBuildx(tc.build, tc.builder, tc.dryrun, false, tc.metadata)
cmd := commandBuildx(tc.build, tc.builder, tc.dryrun, tc.metadata)

if !reflect.DeepEqual(cmd.String(), tc.want.String()) {
t.Errorf("Got cmd %v, want %v", cmd, tc.want)
Expand Down
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,24 @@ module github.com/drone-plugins/drone-buildx
require (
github.com/aws/aws-sdk-go v1.26.7
github.com/coreos/go-semver v0.3.0
github.com/drone-plugins/drone-plugin-lib v0.4.2
github.com/drone/drone-go v1.7.1
github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743
github.com/joho/godotenv v1.3.0
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.7.0
github.com/urfave/cli v1.22.2
)

require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/drone-plugins/drone-plugin-lib v0.4.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
golang.org/x/sys v0.0.0-20220731174439-a90be440212d // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)

go 1.20
3 changes: 1 addition & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/drone-plugins/drone-plugin-lib v0.4.1 h1:47rZlmcMpr1hSp+6Gl+1Z4t+efi/gMQU3lxukC1Yg64=
github.com/drone-plugins/drone-plugin-lib v0.4.1/go.mod h1:KwCu92jFjHV3xv2hu5Qg/8zBNvGwbhoJDQw/EwnTvoM=
github.com/drone-plugins/drone-plugin-lib v0.4.2 h1:EiJ3Kco6ypP5noBQqVt1bBbuO1eUAumtPvLTX/NVAYg=
github.com/drone-plugins/drone-plugin-lib v0.4.2/go.mod h1:KwCu92jFjHV3xv2hu5Qg/8zBNvGwbhoJDQw/EwnTvoM=
github.com/drone/drone-go v1.7.1 h1:ZX+3Rs8YHUSUQ5mkuMLmm1zr1ttiiE2YGNxF3AnyDKw=
Expand Down Expand Up @@ -40,6 +38,7 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
Expand Down