mirror of https://github.com/docker/compose.git
Allow pulling private images from hub or other registries, as long as the user is logged in against the registry(ies).
Registry creds are passed along with the ACI payload (cf https://docs.microsoft.com/en-us/azure/container-registry/container-registry-auth-aci). Manually tested against hub & ACR private repo
This commit is contained in:
parent
3a2e16dc7a
commit
11f1c057dc
|
@ -42,14 +42,21 @@ func ToContainerGroup(aciContext store.AciContext, p compose.Project) (container
|
|||
} else {
|
||||
volumes = &allVolumes
|
||||
}
|
||||
|
||||
registryCreds, err := getRegistryCredentials(p, newCliRegistryConfLoader())
|
||||
if err != nil {
|
||||
return containerinstance.ContainerGroup{}, err
|
||||
}
|
||||
|
||||
var containers []containerinstance.Container
|
||||
groupDefinition := containerinstance.ContainerGroup{
|
||||
Name: &containerGroupName,
|
||||
Location: &aciContext.Location,
|
||||
ContainerGroupProperties: &containerinstance.ContainerGroupProperties{
|
||||
OsType: containerinstance.Linux,
|
||||
Containers: &containers,
|
||||
Volumes: volumes,
|
||||
OsType: containerinstance.Linux,
|
||||
Containers: &containers,
|
||||
Volumes: volumes,
|
||||
ImageRegistryCredentials: ®istryCreds,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
package convert
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/config/types"
|
||||
|
||||
"github.com/docker/api/compose"
|
||||
)
|
||||
|
||||
// Specific username from ACR docs : https://github.com/Azure/acr/blob/master/docs/AAD-OAuth.md#getting-credentials-programatically
|
||||
const (
|
||||
tokenUsername = "00000000-0000-0000-0000-000000000000"
|
||||
dockerHub = "index.docker.io"
|
||||
)
|
||||
|
||||
type registryConfLoader interface {
|
||||
getAllRegistryCredentials() (map[string]types.AuthConfig, error)
|
||||
}
|
||||
|
||||
type cliRegistryConfLoader struct {
|
||||
cfg *configfile.ConfigFile
|
||||
}
|
||||
|
||||
func (c cliRegistryConfLoader) getAllRegistryCredentials() (map[string]types.AuthConfig, error) {
|
||||
return c.cfg.GetAllCredentials()
|
||||
}
|
||||
|
||||
func newCliRegistryConfLoader() cliRegistryConfLoader {
|
||||
return cliRegistryConfLoader{
|
||||
cfg: config.LoadDefaultConfigFile(os.Stderr),
|
||||
}
|
||||
}
|
||||
|
||||
func getRegistryCredentials(project compose.Project, registryLoader registryConfLoader) ([]containerinstance.ImageRegistryCredential, error) {
|
||||
allCreds, err := registryLoader.getAllRegistryCredentials()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
usedRegistries := map[string]bool{}
|
||||
for _, service := range project.Services {
|
||||
imageName := service.Image
|
||||
tokens := strings.Split(imageName, "/")
|
||||
registry := tokens[0]
|
||||
if len(tokens) == 1 { // ! image names can include "." ...
|
||||
registry = dockerHub
|
||||
} else if !strings.Contains(registry, ".") {
|
||||
registry = dockerHub
|
||||
}
|
||||
usedRegistries[registry] = true
|
||||
}
|
||||
var registryCreds []containerinstance.ImageRegistryCredential
|
||||
for name, oneCred := range allCreds {
|
||||
parsedURL, err := url.Parse(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostname := parsedURL.Host
|
||||
if hostname == "" {
|
||||
hostname = parsedURL.Path
|
||||
}
|
||||
if _, ok := usedRegistries[hostname]; ok {
|
||||
if oneCred.Username != "" {
|
||||
aciCredential := containerinstance.ImageRegistryCredential{
|
||||
Server: to.StringPtr(hostname),
|
||||
Password: to.StringPtr(oneCred.Password),
|
||||
Username: to.StringPtr(oneCred.Username),
|
||||
}
|
||||
registryCreds = append(registryCreds, aciCredential)
|
||||
} else if oneCred.IdentityToken != "" {
|
||||
aciCredential := containerinstance.ImageRegistryCredential{
|
||||
Server: to.StringPtr(hostname),
|
||||
Password: to.StringPtr(oneCred.IdentityToken),
|
||||
Username: to.StringPtr(tokenUsername),
|
||||
}
|
||||
registryCreds = append(registryCreds, aciCredential)
|
||||
}
|
||||
}
|
||||
}
|
||||
return registryCreds, nil
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
package convert
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
cliconfigtypes "github.com/docker/cli/cli/config/types"
|
||||
|
||||
"github.com/docker/api/compose"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
const getAllCredentials = "getAllRegistryCredentials"
|
||||
|
||||
type RegistryConvertTestSuite struct {
|
||||
suite.Suite
|
||||
loader *MockRegistryLoader
|
||||
}
|
||||
|
||||
func (suite *RegistryConvertTestSuite) BeforeTest(suiteName, testName string) {
|
||||
suite.loader = &MockRegistryLoader{}
|
||||
}
|
||||
|
||||
func (suite *RegistryConvertTestSuite) TestHubPrivateImage() {
|
||||
suite.loader.On(getAllCredentials).Return(registry("https://index.docker.io", userPwdCreds("toto", "pwd")), nil)
|
||||
|
||||
creds, err := getRegistryCredentials(composeServices("gtardif/privateimg"), suite.loader)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(creds).To(Equal([]containerinstance.ImageRegistryCredential{
|
||||
{
|
||||
Server: to.StringPtr(dockerHub),
|
||||
Username: to.StringPtr("toto"),
|
||||
Password: to.StringPtr("pwd"),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *RegistryConvertTestSuite) TestRegistryNameWithoutProtocol() {
|
||||
suite.loader.On(getAllCredentials).Return(registry("index.docker.io", userPwdCreds("toto", "pwd")), nil)
|
||||
|
||||
creds, err := getRegistryCredentials(composeServices("gtardif/privateimg"), suite.loader)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(creds).To(Equal([]containerinstance.ImageRegistryCredential{
|
||||
{
|
||||
Server: to.StringPtr(dockerHub),
|
||||
Username: to.StringPtr("toto"),
|
||||
Password: to.StringPtr("pwd"),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *RegistryConvertTestSuite) TestImageWithDotInName() {
|
||||
suite.loader.On(getAllCredentials).Return(registry("index.docker.io", userPwdCreds("toto", "pwd")), nil)
|
||||
|
||||
creds, err := getRegistryCredentials(composeServices("my.image"), suite.loader)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(creds).To(Equal([]containerinstance.ImageRegistryCredential{
|
||||
{
|
||||
Server: to.StringPtr(dockerHub),
|
||||
Username: to.StringPtr("toto"),
|
||||
Password: to.StringPtr("pwd"),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *RegistryConvertTestSuite) TestAcrPrivateImage() {
|
||||
suite.loader.On(getAllCredentials).Return(registry("https://mycontainerregistrygta.azurecr.io", tokenCreds("123456")), nil)
|
||||
|
||||
creds, err := getRegistryCredentials(composeServices("mycontainerregistrygta.azurecr.io/privateimg"), suite.loader)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(creds).To(Equal([]containerinstance.ImageRegistryCredential{
|
||||
{
|
||||
Server: to.StringPtr("mycontainerregistrygta.azurecr.io"),
|
||||
Username: to.StringPtr(tokenUsername),
|
||||
Password: to.StringPtr("123456"),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *RegistryConvertTestSuite) TestNoMoreRegistriesThanImages() {
|
||||
configs := map[string]cliconfigtypes.AuthConfig{
|
||||
"https://mycontainerregistrygta.azurecr.io": tokenCreds("123456"),
|
||||
"https://index.docker.io": userPwdCreds("toto", "pwd"),
|
||||
}
|
||||
suite.loader.On(getAllCredentials).Return(configs, nil)
|
||||
|
||||
creds, err := getRegistryCredentials(composeServices("mycontainerregistrygta.azurecr.io/privateimg"), suite.loader)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(creds).To(Equal([]containerinstance.ImageRegistryCredential{
|
||||
{
|
||||
Server: to.StringPtr("mycontainerregistrygta.azurecr.io"),
|
||||
Username: to.StringPtr(tokenUsername),
|
||||
Password: to.StringPtr("123456"),
|
||||
},
|
||||
}))
|
||||
|
||||
creds, err = getRegistryCredentials(composeServices("someuser/privateimg"), suite.loader)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(creds).To(Equal([]containerinstance.ImageRegistryCredential{
|
||||
{
|
||||
Server: to.StringPtr(dockerHub),
|
||||
Username: to.StringPtr("toto"),
|
||||
Password: to.StringPtr("pwd"),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *RegistryConvertTestSuite) TestHubAndSeveralACRRegistries() {
|
||||
configs := map[string]cliconfigtypes.AuthConfig{
|
||||
"https://mycontainerregistry1.azurecr.io": tokenCreds("123456"),
|
||||
"https://mycontainerregistry2.azurecr.io": tokenCreds("456789"),
|
||||
"https://mycontainerregistry3.azurecr.io": tokenCreds("123456789"),
|
||||
"https://index.docker.io": userPwdCreds("toto", "pwd"),
|
||||
"https://other.registry.io": userPwdCreds("user", "password"),
|
||||
}
|
||||
suite.loader.On(getAllCredentials).Return(configs, nil)
|
||||
|
||||
creds, err := getRegistryCredentials(composeServices("mycontainerregistry1.azurecr.io/privateimg", "someuser/privateImg2", "mycontainerregistry2.azurecr.io/privateimg"), suite.loader)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(creds).To(ContainElement(containerinstance.ImageRegistryCredential{
|
||||
Server: to.StringPtr("mycontainerregistry1.azurecr.io"),
|
||||
Username: to.StringPtr(tokenUsername),
|
||||
Password: to.StringPtr("123456"),
|
||||
}))
|
||||
Expect(creds).To(ContainElement(containerinstance.ImageRegistryCredential{
|
||||
Server: to.StringPtr("mycontainerregistry2.azurecr.io"),
|
||||
Username: to.StringPtr(tokenUsername),
|
||||
Password: to.StringPtr("456789"),
|
||||
}))
|
||||
Expect(creds).To(ContainElement(containerinstance.ImageRegistryCredential{
|
||||
Server: to.StringPtr(dockerHub),
|
||||
Username: to.StringPtr("toto"),
|
||||
Password: to.StringPtr("pwd"),
|
||||
}))
|
||||
}
|
||||
|
||||
func composeServices(images ...string) compose.Project {
|
||||
var services []types.ServiceConfig
|
||||
for index, name := range images {
|
||||
service := types.ServiceConfig{
|
||||
Name: "service" + strconv.Itoa(index),
|
||||
Image: name,
|
||||
}
|
||||
services = append(services, service)
|
||||
}
|
||||
return compose.Project{
|
||||
Config: types.Config{
|
||||
Services: services,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func registry(host string, configregistryData cliconfigtypes.AuthConfig) map[string]cliconfigtypes.AuthConfig {
|
||||
return map[string]cliconfigtypes.AuthConfig{
|
||||
host: configregistryData,
|
||||
}
|
||||
}
|
||||
|
||||
func userPwdCreds(user string, password string) cliconfigtypes.AuthConfig {
|
||||
return cliconfigtypes.AuthConfig{
|
||||
Username: user,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
|
||||
func tokenCreds(token string) cliconfigtypes.AuthConfig {
|
||||
return cliconfigtypes.AuthConfig{
|
||||
IdentityToken: token,
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryConvertTestSuite(t *testing.T) {
|
||||
RegisterTestingT(t)
|
||||
suite.Run(t, new(RegistryConvertTestSuite))
|
||||
}
|
||||
|
||||
type MockRegistryLoader struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (s *MockRegistryLoader) getAllRegistryCredentials() (map[string]cliconfigtypes.AuthConfig, error) {
|
||||
args := s.Called()
|
||||
return args.Get(0).(map[string]cliconfigtypes.AuthConfig), args.Error(1)
|
||||
}
|
3
go.mod
3
go.mod
|
@ -17,8 +17,10 @@ require (
|
|||
github.com/compose-spec/compose-go v0.0.0-20200423124427-63dcf8c22cae
|
||||
github.com/containerd/console v1.0.0
|
||||
github.com/containerd/containerd v1.3.4 // indirect
|
||||
github.com/docker/cli v0.0.0-20200528204125-dd360c7c0de8
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible
|
||||
github.com/docker/docker-credential-helpers v0.6.3 // indirect
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee // indirect
|
||||
github.com/gobwas/pool v0.2.0 // indirect
|
||||
|
@ -31,6 +33,7 @@ require (
|
|||
github.com/onsi/gomega v1.9.0
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/opencontainers/runc v0.1.1 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.5.1 // indirect
|
||||
github.com/robpike/filter v0.0.0-20150108201509-2984852a2183
|
||||
|
|
9
go.sum
9
go.sum
|
@ -76,10 +76,17 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
|||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/docker/cli v0.0.0-20200303162255-7d407207c304 h1:A7SYzidcyuQ/yS4wezWGYeUioUFJQk8HYWY9aMYTF4I=
|
||||
github.com/docker/cli v0.0.0-20200303162255-7d407207c304/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v0.0.0-20200528204125-dd360c7c0de8 h1:JRquW4uqIU+eSilDhuo9X9QFX4NEmGj5B1x97ZA8djM=
|
||||
github.com/docker/cli v0.0.0-20200528204125-dd360c7c0de8/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v17.12.1-ce-rc2+incompatible h1:ESUycEAqvFuLglAHkUW66rCc2djYtd3i1x231svLq9o=
|
||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible h1:G2hY8RD7jB9QaSmcb8mYEIg8QbEvVAB7se8+lXHZHfg=
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ=
|
||||
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
|
@ -189,6 +196,8 @@ github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2i
|
|||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
|
||||
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
|
|
Loading…
Reference in New Issue