Skip to content
This repository was archived by the owner on Nov 27, 2023. It is now read-only.

Commit 3420df6

Browse files
authored
Merge pull request #1255 from karolz-ms/658-azure-sovereign-clouds
Add Azure sovereign cloud support
2 parents c53398a + cc649d9 commit 3420df6

15 files changed

Lines changed: 964 additions & 227 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
bin/
22
dist/
3+
/.vscode/

aci/backend.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ type LoginParams struct {
5151
TenantID string
5252
ClientID string
5353
ClientSecret string
54+
CloudName string
5455
}
5556

5657
// Validate returns an error if options are not used properly

aci/backend_test.go

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ import (
2323
"github.com/stretchr/testify/mock"
2424
"gotest.tools/v3/assert"
2525

26+
"github.com/docker/compose-cli/aci/login"
2627
"github.com/docker/compose-cli/api/containers"
28+
"golang.org/x/oauth2"
2729
)
2830

2931
func TestGetContainerName(t *testing.T) {
@@ -82,7 +84,7 @@ func TestLoginParamsValidate(t *testing.T) {
8284

8385
func TestLoginServicePrincipal(t *testing.T) {
8486
loginService := mockLoginService{}
85-
loginService.On("LoginServicePrincipal", "someID", "secret", "tenant").Return(nil)
87+
loginService.On("LoginServicePrincipal", "someID", "secret", "tenant", "someCloud").Return(nil)
8688
loginBackend := aciCloudService{
8789
loginService: &loginService,
8890
}
@@ -91,6 +93,7 @@ func TestLoginServicePrincipal(t *testing.T) {
9193
ClientID: "someID",
9294
ClientSecret: "secret",
9395
TenantID: "tenant",
96+
CloudName: "someCloud",
9497
})
9598

9699
assert.NilError(t, err)
@@ -99,13 +102,14 @@ func TestLoginServicePrincipal(t *testing.T) {
99102
func TestLoginWithTenant(t *testing.T) {
100103
loginService := mockLoginService{}
101104
ctx := context.Background()
102-
loginService.On("Login", ctx, "tenant").Return(nil)
105+
loginService.On("Login", ctx, "tenant", "someCloud").Return(nil)
103106
loginBackend := aciCloudService{
104107
loginService: &loginService,
105108
}
106109

107110
err := loginBackend.Login(ctx, LoginParams{
108-
TenantID: "tenant",
111+
TenantID: "tenant",
112+
CloudName: "someCloud",
109113
})
110114

111115
assert.NilError(t, err)
@@ -114,12 +118,14 @@ func TestLoginWithTenant(t *testing.T) {
114118
func TestLoginWithoutTenant(t *testing.T) {
115119
loginService := mockLoginService{}
116120
ctx := context.Background()
117-
loginService.On("Login", ctx, "").Return(nil)
121+
loginService.On("Login", ctx, "", "someCloud").Return(nil)
118122
loginBackend := aciCloudService{
119123
loginService: &loginService,
120124
}
121125

122-
err := loginBackend.Login(ctx, LoginParams{})
126+
err := loginBackend.Login(ctx, LoginParams{
127+
CloudName: "someCloud",
128+
})
123129

124130
assert.NilError(t, err)
125131
}
@@ -128,17 +134,32 @@ type mockLoginService struct {
128134
mock.Mock
129135
}
130136

131-
func (s *mockLoginService) Login(ctx context.Context, requestedTenantID string) error {
132-
args := s.Called(ctx, requestedTenantID)
137+
func (s *mockLoginService) Login(ctx context.Context, requestedTenantID string, cloudEnvironment string) error {
138+
args := s.Called(ctx, requestedTenantID, cloudEnvironment)
133139
return args.Error(0)
134140
}
135141

136-
func (s *mockLoginService) LoginServicePrincipal(clientID string, clientSecret string, tenantID string) error {
137-
args := s.Called(clientID, clientSecret, tenantID)
142+
func (s *mockLoginService) LoginServicePrincipal(clientID string, clientSecret string, tenantID string, cloudEnvironment string) error {
143+
args := s.Called(clientID, clientSecret, tenantID, cloudEnvironment)
138144
return args.Error(0)
139145
}
140146

141147
func (s *mockLoginService) Logout(ctx context.Context) error {
142148
args := s.Called(ctx)
143149
return args.Error(0)
144150
}
151+
152+
func (s *mockLoginService) GetTenantID() (string, error) {
153+
args := s.Called()
154+
return args.String(0), args.Error(1)
155+
}
156+
157+
func (s *mockLoginService) GetCloudEnvironment() (login.CloudEnvironment, error) {
158+
args := s.Called()
159+
return args.Get(0).(login.CloudEnvironment), args.Error(1)
160+
}
161+
162+
func (s *mockLoginService) GetValidToken() (oauth2.Token, string, error) {
163+
args := s.Called()
164+
return args.Get(0).(oauth2.Token), args.String(1), args.Error(2)
165+
}

aci/cloud.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,21 @@ import (
2525
)
2626

2727
type aciCloudService struct {
28-
loginService login.AzureLoginServiceAPI
28+
loginService login.AzureLoginService
2929
}
3030

3131
func (cs *aciCloudService) Login(ctx context.Context, params interface{}) error {
3232
opts, ok := params.(LoginParams)
3333
if !ok {
3434
return errors.New("could not read Azure LoginParams struct from generic parameter")
3535
}
36+
if opts.CloudName == "" {
37+
opts.CloudName = login.AzurePublicCloudName
38+
}
3639
if opts.ClientID != "" {
37-
return cs.loginService.LoginServicePrincipal(opts.ClientID, opts.ClientSecret, opts.TenantID)
40+
return cs.loginService.LoginServicePrincipal(opts.ClientID, opts.ClientSecret, opts.TenantID, opts.CloudName)
3841
}
39-
return cs.loginService.Login(ctx, opts.TenantID)
42+
return cs.loginService.Login(ctx, opts.TenantID, opts.CloudName)
4043
}
4144

4245
func (cs *aciCloudService) Logout(ctx context.Context) error {

aci/convert/registry_credentials.go

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const (
4747

4848
type registryHelper interface {
4949
getAllRegistryCredentials() (map[string]types.AuthConfig, error)
50-
autoLoginAcr(registry string) error
50+
autoLoginAcr(registry string, loginService login.AzureLoginService) error
5151
}
5252

5353
type cliRegistryHelper struct {
@@ -65,9 +65,19 @@ func newCliRegistryConfLoader() cliRegistryHelper {
6565
}
6666

6767
func getRegistryCredentials(project compose.Project, helper registryHelper) ([]containerinstance.ImageRegistryCredential, error) {
68-
usedRegistries, acrRegistries := getUsedRegistries(project)
68+
loginService, err := login.NewAzureLoginService()
69+
if err != nil {
70+
return nil, err
71+
}
72+
73+
var cloudEnvironment *login.CloudEnvironment = nil
74+
if ce, err := loginService.GetCloudEnvironment(); err != nil {
75+
cloudEnvironment = &ce
76+
}
77+
78+
usedRegistries, acrRegistries := getUsedRegistries(project, cloudEnvironment)
6979
for _, registry := range acrRegistries {
70-
err := helper.autoLoginAcr(registry)
80+
err := helper.autoLoginAcr(registry, loginService)
7181
if err != nil {
7282
fmt.Printf("WARNING: %v\n", err)
7383
fmt.Printf("Could not automatically login to %s from your Azure login. Assuming you already logged in to the ACR registry\n", registry)
@@ -116,9 +126,10 @@ func getRegistryCredentials(project compose.Project, helper registryHelper) ([]c
116126
return registryCreds, nil
117127
}
118128

119-
func getUsedRegistries(project compose.Project) (map[string]bool, []string) {
129+
func getUsedRegistries(project compose.Project, ce *login.CloudEnvironment) (map[string]bool, []string) {
120130
usedRegistries := map[string]bool{}
121131
acrRegistries := []string{}
132+
122133
for _, service := range project.Services {
123134
imageName := service.Image
124135
tokens := strings.Split(imageName, "/")
@@ -127,24 +138,18 @@ func getUsedRegistries(project compose.Project) (map[string]bool, []string) {
127138
registry = dockerHub
128139
} else if !strings.Contains(registry, ".") {
129140
registry = dockerHub
130-
} else if strings.HasSuffix(registry, login.AcrRegistrySuffix) {
131-
acrRegistries = append(acrRegistries, registry)
141+
} else if ce != nil {
142+
if suffix, present := ce.Suffixes[login.AcrSuffixKey]; present && strings.HasSuffix(registry, suffix) {
143+
acrRegistries = append(acrRegistries, registry)
144+
}
132145
}
133146
usedRegistries[registry] = true
134147
}
135148
return usedRegistries, acrRegistries
136149
}
137150

138-
func (c cliRegistryHelper) autoLoginAcr(registry string) error {
139-
loginService, err := login.NewAzureLoginService()
140-
if err != nil {
141-
return err
142-
}
143-
token, err := loginService.GetValidToken()
144-
if err != nil {
145-
return err
146-
}
147-
tenantID, err := loginService.GetTenantID()
151+
func (c cliRegistryHelper) autoLoginAcr(registry string, loginService login.AzureLoginService) error {
152+
token, tenantID, err := loginService.GetValidToken()
148153
if err != nil {
149154
return err
150155
}

aci/convert/registry_credentials_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/Azure/go-autorest/autorest/to"
2626
"github.com/compose-spec/compose-go/types"
2727
cliconfigtypes "github.com/docker/cli/cli/config/types"
28+
"github.com/docker/compose-cli/aci/login"
2829
"github.com/stretchr/testify/mock"
2930
"gotest.tools/v3/assert"
3031
is "gotest.tools/v3/assert/cmp"
@@ -255,7 +256,7 @@ func (s *MockRegistryHelper) getAllRegistryCredentials() (map[string]cliconfigty
255256
return args.Get(0).(map[string]cliconfigtypes.AuthConfig), args.Error(1)
256257
}
257258

258-
func (s *MockRegistryHelper) autoLoginAcr(registry string) error {
259-
args := s.Called(registry)
259+
func (s *MockRegistryHelper) autoLoginAcr(registry string, loginService login.AzureLoginService) error {
260+
args := s.Called(registry, loginService)
260261
return args.Error(0)
261262
}

aci/login/client.go

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@
1717
package login
1818

1919
import (
20+
"encoding/json"
21+
"strconv"
2022
"time"
2123

2224
"github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/resources/mgmt/resources"
2325
"github.com/Azure/azure-sdk-for-go/profiles/preview/preview/subscription/mgmt/subscription"
2426
"github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2019-12-01/containerinstance"
2527
"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage"
2628
"github.com/Azure/go-autorest/autorest"
29+
"github.com/Azure/go-autorest/autorest/adal"
30+
"github.com/Azure/go-autorest/autorest/date"
2731
"github.com/pkg/errors"
2832

2933
"github.com/docker/compose-cli/api/errdefs"
@@ -32,8 +36,12 @@ import (
3236

3337
// NewContainerGroupsClient get client toi manipulate containerGrouos
3438
func NewContainerGroupsClient(subscriptionID string) (containerinstance.ContainerGroupsClient, error) {
35-
containerGroupsClient := containerinstance.NewContainerGroupsClient(subscriptionID)
36-
err := setupClient(&containerGroupsClient.Client)
39+
authorizer, mgmtURL, err := getClientSetupData()
40+
if err != nil {
41+
return containerinstance.ContainerGroupsClient{}, err
42+
}
43+
containerGroupsClient := containerinstance.NewContainerGroupsClientWithBaseURI(mgmtURL, subscriptionID)
44+
setupClient(&containerGroupsClient.Client, authorizer)
3745
if err != nil {
3846
return containerinstance.ContainerGroupsClient{}, err
3947
}
@@ -43,68 +51,100 @@ func NewContainerGroupsClient(subscriptionID string) (containerinstance.Containe
4351
return containerGroupsClient, nil
4452
}
4553

46-
func setupClient(aciClient *autorest.Client) error {
54+
func setupClient(aciClient *autorest.Client, auth autorest.Authorizer) {
4755
aciClient.UserAgent = internal.UserAgentName + "/" + internal.Version
48-
auth, err := NewAuthorizerFromLogin()
49-
if err != nil {
50-
return err
51-
}
5256
aciClient.Authorizer = auth
53-
return nil
5457
}
5558

5659
// NewStorageAccountsClient get client to manipulate storage accounts
5760
func NewStorageAccountsClient(subscriptionID string) (storage.AccountsClient, error) {
58-
containerGroupsClient := storage.NewAccountsClient(subscriptionID)
59-
err := setupClient(&containerGroupsClient.Client)
61+
authorizer, mgmtURL, err := getClientSetupData()
6062
if err != nil {
6163
return storage.AccountsClient{}, err
6264
}
63-
containerGroupsClient.PollingDelay = 5 * time.Second
64-
containerGroupsClient.RetryAttempts = 30
65-
containerGroupsClient.RetryDuration = 1 * time.Second
66-
return containerGroupsClient, nil
65+
storageAccuntsClient := storage.NewAccountsClientWithBaseURI(mgmtURL, subscriptionID)
66+
setupClient(&storageAccuntsClient.Client, authorizer)
67+
storageAccuntsClient.PollingDelay = 5 * time.Second
68+
storageAccuntsClient.RetryAttempts = 30
69+
storageAccuntsClient.RetryDuration = 1 * time.Second
70+
return storageAccuntsClient, nil
6771
}
6872

6973
// NewFileShareClient get client to manipulate file shares
7074
func NewFileShareClient(subscriptionID string) (storage.FileSharesClient, error) {
71-
containerGroupsClient := storage.NewFileSharesClient(subscriptionID)
72-
err := setupClient(&containerGroupsClient.Client)
75+
authorizer, mgmtURL, err := getClientSetupData()
7376
if err != nil {
7477
return storage.FileSharesClient{}, err
7578
}
76-
containerGroupsClient.PollingDelay = 5 * time.Second
77-
containerGroupsClient.RetryAttempts = 30
78-
containerGroupsClient.RetryDuration = 1 * time.Second
79-
return containerGroupsClient, nil
79+
fileSharesClient := storage.NewFileSharesClientWithBaseURI(mgmtURL, subscriptionID)
80+
setupClient(&fileSharesClient.Client, authorizer)
81+
fileSharesClient.PollingDelay = 5 * time.Second
82+
fileSharesClient.RetryAttempts = 30
83+
fileSharesClient.RetryDuration = 1 * time.Second
84+
return fileSharesClient, nil
8085
}
8186

8287
// NewSubscriptionsClient get subscription client
8388
func NewSubscriptionsClient() (subscription.SubscriptionsClient, error) {
84-
subc := subscription.NewSubscriptionsClient()
85-
err := setupClient(&subc.Client)
89+
authorizer, mgmtURL, err := getClientSetupData()
8690
if err != nil {
8791
return subscription.SubscriptionsClient{}, errors.Wrap(errdefs.ErrLoginRequired, err.Error())
8892
}
93+
subc := subscription.NewSubscriptionsClientWithBaseURI(mgmtURL)
94+
setupClient(&subc.Client, authorizer)
8995
return subc, nil
9096
}
9197

9298
// NewGroupsClient get client to manipulate groups
9399
func NewGroupsClient(subscriptionID string) (resources.GroupsClient, error) {
94-
groupsClient := resources.NewGroupsClient(subscriptionID)
95-
err := setupClient(&groupsClient.Client)
100+
authorizer, mgmtURL, err := getClientSetupData()
96101
if err != nil {
97102
return resources.GroupsClient{}, err
98103
}
104+
groupsClient := resources.NewGroupsClientWithBaseURI(mgmtURL, subscriptionID)
105+
setupClient(&groupsClient.Client, authorizer)
99106
return groupsClient, nil
100107
}
101108

102109
// NewContainerClient get client to manipulate containers
103110
func NewContainerClient(subscriptionID string) (containerinstance.ContainersClient, error) {
104-
containerClient := containerinstance.NewContainersClient(subscriptionID)
105-
err := setupClient(&containerClient.Client)
111+
authorizer, mgmtURL, err := getClientSetupData()
106112
if err != nil {
107113
return containerinstance.ContainersClient{}, err
108114
}
115+
containerClient := containerinstance.NewContainersClientWithBaseURI(mgmtURL, subscriptionID)
116+
setupClient(&containerClient.Client, authorizer)
109117
return containerClient, nil
110118
}
119+
120+
func getClientSetupData() (autorest.Authorizer, string, error) {
121+
return getClientSetupDataImpl(GetTokenStorePath())
122+
}
123+
124+
func getClientSetupDataImpl(tokenStorePath string) (autorest.Authorizer, string, error) {
125+
als, err := newAzureLoginServiceFromPath(tokenStorePath, azureAPIHelper{}, CloudEnvironments)
126+
if err != nil {
127+
return nil, "", err
128+
}
129+
130+
oauthToken, _, err := als.GetValidToken()
131+
if err != nil {
132+
return nil, "", errors.Wrap(err, "not logged in to azure, you need to run \"docker login azure\" first")
133+
}
134+
135+
ce, err := als.GetCloudEnvironment()
136+
if err != nil {
137+
return nil, "", err
138+
}
139+
140+
token := adal.Token{
141+
AccessToken: oauthToken.AccessToken,
142+
Type: oauthToken.TokenType,
143+
ExpiresIn: json.Number(strconv.Itoa(int(time.Until(oauthToken.Expiry).Seconds()))),
144+
ExpiresOn: json.Number(strconv.Itoa(int(oauthToken.Expiry.Sub(date.UnixEpoch()).Seconds()))),
145+
RefreshToken: "",
146+
Resource: "",
147+
}
148+
149+
return autorest.NewBearerAuthorizer(&token), ce.ResourceManagerURL, nil
150+
}

0 commit comments

Comments
 (0)