github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/azure/credentials_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package azure_test
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"io"
    10  
    11  	"github.com/Azure/go-autorest/autorest"
    12  	"github.com/Azure/go-autorest/autorest/adal"
    13  	"github.com/juju/cmd/cmdtesting"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/testing"
    16  	jc "github.com/juju/testing/checkers"
    17  	gc "gopkg.in/check.v1"
    18  
    19  	"github.com/juju/juju/cloud"
    20  	"github.com/juju/juju/environs"
    21  	envtesting "github.com/juju/juju/environs/testing"
    22  	"github.com/juju/juju/provider/azure"
    23  	"github.com/juju/juju/provider/azure/internal/azureauth"
    24  	"github.com/juju/juju/provider/azure/internal/azurecli"
    25  )
    26  
    27  type credentialsSuite struct {
    28  	testing.IsolationSuite
    29  	servicePrincipalCreator servicePrincipalCreator
    30  	azureCLI                azureCLI
    31  	provider                environs.EnvironProvider
    32  }
    33  
    34  var _ = gc.Suite(&credentialsSuite{})
    35  
    36  func (s *credentialsSuite) SetUpTest(c *gc.C) {
    37  	s.IsolationSuite.SetUpTest(c)
    38  	s.servicePrincipalCreator = servicePrincipalCreator{}
    39  	s.azureCLI = azureCLI{}
    40  	s.provider = newProvider(c, azure.ProviderConfig{
    41  		ServicePrincipalCreator: &s.servicePrincipalCreator,
    42  		AzureCLI:                &s.azureCLI,
    43  	})
    44  }
    45  
    46  func (s *credentialsSuite) TestCredentialSchemas(c *gc.C) {
    47  	envtesting.AssertProviderAuthTypes(c, s.provider,
    48  		"interactive",
    49  		"service-principal-secret",
    50  	)
    51  }
    52  
    53  var sampleCredentialAttributes = map[string]string{
    54  	"application-id":       "application",
    55  	"application-password": "password",
    56  	"subscription-id":      "subscription",
    57  }
    58  
    59  func (s *credentialsSuite) TestServicePrincipalSecretCredentialsValid(c *gc.C) {
    60  	envtesting.AssertProviderCredentialsValid(c, s.provider, "service-principal-secret", map[string]string{
    61  		"application-id":       "application",
    62  		"application-password": "password",
    63  		"subscription-id":      "subscription",
    64  	})
    65  }
    66  
    67  func (s *credentialsSuite) TestServicePrincipalSecretHiddenAttributes(c *gc.C) {
    68  	envtesting.AssertProviderCredentialsAttributesHidden(c, s.provider, "service-principal-secret", "application-password")
    69  }
    70  
    71  func (s *credentialsSuite) TestDetectCredentialsNoAccounts(c *gc.C) {
    72  	_, err := s.provider.DetectCredentials()
    73  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
    74  	calls := s.azureCLI.Calls()
    75  	c.Assert(calls, gc.HasLen, 1)
    76  	c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts")
    77  }
    78  
    79  func (s *credentialsSuite) TestDetectCredentialsListError(c *gc.C) {
    80  	s.azureCLI.SetErrors(errors.New("test error"))
    81  	_, err := s.provider.DetectCredentials()
    82  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
    83  }
    84  
    85  func (s *credentialsSuite) TestDetectCredentialsOneAccount(c *gc.C) {
    86  	s.azureCLI.Accounts = []azurecli.Account{{
    87  		CloudName: "AzureCloud",
    88  		ID:        "test-account-id",
    89  		IsDefault: true,
    90  		Name:      "test-account",
    91  		State:     "Enabled",
    92  		TenantId:  "tenant-id",
    93  	}}
    94  	s.azureCLI.Clouds = []azurecli.Cloud{{
    95  		Endpoints: azurecli.CloudEndpoints{
    96  			ActiveDirectoryGraphResourceID: "https://graph.invalid/",
    97  			ResourceManager:                "https://arm.invalid/",
    98  		},
    99  		IsActive: true,
   100  		Name:     "AzureCloud",
   101  	}}
   102  	cred, err := s.provider.DetectCredentials()
   103  	c.Assert(err, jc.ErrorIsNil)
   104  	c.Assert(cred, gc.Not(gc.IsNil))
   105  	c.Assert(cred.DefaultCredential, gc.Equals, "test-account")
   106  	c.Assert(cred.DefaultRegion, gc.Equals, "")
   107  	c.Assert(cred.AuthCredentials, gc.HasLen, 1)
   108  	c.Assert(cred.AuthCredentials["test-account"].Label, gc.Equals, "AzureCloud subscription test-account")
   109  
   110  	calls := s.azureCLI.Calls()
   111  	c.Assert(calls, gc.HasLen, 4)
   112  	c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts")
   113  	c.Assert(calls[1].FuncName, gc.Equals, "ListClouds")
   114  	c.Assert(calls[2].FuncName, gc.Equals, "GetAccessToken")
   115  	c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{"test-account-id", "https://graph.invalid/"})
   116  	c.Assert(calls[3].FuncName, gc.Equals, "GetAccessToken")
   117  	c.Assert(calls[3].Args, jc.DeepEquals, []interface{}{"test-account-id", "https://arm.invalid/"})
   118  
   119  	calls = s.servicePrincipalCreator.Calls()
   120  	c.Assert(calls, gc.HasLen, 1)
   121  	c.Assert(calls[0].FuncName, gc.Equals, "Create")
   122  	c.Assert(calls[0].Args[1], jc.DeepEquals, azureauth.ServicePrincipalParams{
   123  		GraphEndpoint:   "https://graph.invalid/",
   124  		GraphResourceId: "https://graph.invalid/",
   125  		GraphAuthorizer: autorest.NewBearerAuthorizer(&adal.Token{
   126  			AccessToken: "test-account-id|https://graph.invalid/|access-token",
   127  			Type:        "Bearer",
   128  		}),
   129  		ResourceManagerEndpoint:   "https://arm.invalid/",
   130  		ResourceManagerResourceId: "https://arm.invalid/",
   131  		ResourceManagerAuthorizer: autorest.NewBearerAuthorizer(&adal.Token{
   132  			AccessToken: "test-account-id|https://arm.invalid/|access-token",
   133  			Type:        "Bearer",
   134  		}),
   135  		SubscriptionId: "test-account-id",
   136  		TenantId:       "tenant-id",
   137  	})
   138  }
   139  
   140  func (s *credentialsSuite) TestDetectCredentialsCloudError(c *gc.C) {
   141  	s.azureCLI.Accounts = []azurecli.Account{{
   142  		CloudName: "AzureCloud",
   143  		ID:        "test-account-id",
   144  		IsDefault: true,
   145  		Name:      "test-account",
   146  		State:     "Enabled",
   147  		TenantId:  "tenant-id",
   148  	}}
   149  	s.azureCLI.Clouds = []azurecli.Cloud{{
   150  		Endpoints: azurecli.CloudEndpoints{
   151  			ActiveDirectoryGraphResourceID: "https://graph.invalid",
   152  			ResourceManager:                "https://arm.invalid",
   153  		},
   154  		IsActive: true,
   155  		Name:     "AzureCloud",
   156  	}}
   157  	s.azureCLI.SetErrors(nil, errors.New("test error"))
   158  	cred, err := s.provider.DetectCredentials()
   159  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   160  	c.Assert(cred, gc.IsNil)
   161  
   162  	calls := s.azureCLI.Calls()
   163  	c.Assert(calls, gc.HasLen, 2)
   164  	c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts")
   165  	c.Assert(calls[1].FuncName, gc.Equals, "ListClouds")
   166  
   167  	calls = s.servicePrincipalCreator.Calls()
   168  	c.Assert(calls, gc.HasLen, 0)
   169  }
   170  
   171  func (s *credentialsSuite) TestDetectCredentialsTwoAccounts(c *gc.C) {
   172  	s.azureCLI.Accounts = []azurecli.Account{{
   173  		CloudName: "AzureCloud",
   174  		ID:        "test-account1-id",
   175  		IsDefault: true,
   176  		Name:      "test-account1",
   177  		State:     "Enabled",
   178  		TenantId:  "tenant-id",
   179  	}, {
   180  		CloudName: "AzureCloud",
   181  		ID:        "test-account2-id",
   182  		IsDefault: false,
   183  		Name:      "test-account2",
   184  		State:     "Enabled",
   185  		TenantId:  "tenant-id",
   186  	}}
   187  	s.azureCLI.Clouds = []azurecli.Cloud{{
   188  		Endpoints: azurecli.CloudEndpoints{
   189  			ActiveDirectoryGraphResourceID: "https://graph.invalid/",
   190  			ResourceManager:                "https://arm.invalid/",
   191  		},
   192  		IsActive: true,
   193  		Name:     "AzureCloud",
   194  	}}
   195  	cred, err := s.provider.DetectCredentials()
   196  	c.Assert(err, jc.ErrorIsNil)
   197  	c.Assert(cred, gc.Not(gc.IsNil))
   198  	c.Assert(cred.DefaultCredential, gc.Equals, "test-account1")
   199  	c.Assert(cred.DefaultRegion, gc.Equals, "")
   200  	c.Assert(cred.AuthCredentials, gc.HasLen, 2)
   201  	c.Assert(cred.AuthCredentials["test-account1"].Label, gc.Equals, "AzureCloud subscription test-account1")
   202  	c.Assert(cred.AuthCredentials["test-account2"].Label, gc.Equals, "AzureCloud subscription test-account2")
   203  
   204  	calls := s.azureCLI.Calls()
   205  	c.Assert(calls, gc.HasLen, 6)
   206  	c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts")
   207  	c.Assert(calls[1].FuncName, gc.Equals, "ListClouds")
   208  	c.Assert(calls[2].FuncName, gc.Equals, "GetAccessToken")
   209  	c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{"test-account1-id", "https://graph.invalid/"})
   210  	c.Assert(calls[3].FuncName, gc.Equals, "GetAccessToken")
   211  	c.Assert(calls[3].Args, jc.DeepEquals, []interface{}{"test-account1-id", "https://arm.invalid/"})
   212  	c.Assert(calls[4].FuncName, gc.Equals, "GetAccessToken")
   213  	c.Assert(calls[4].Args, jc.DeepEquals, []interface{}{"test-account2-id", "https://graph.invalid/"})
   214  	c.Assert(calls[5].FuncName, gc.Equals, "GetAccessToken")
   215  	c.Assert(calls[5].Args, jc.DeepEquals, []interface{}{"test-account2-id", "https://arm.invalid/"})
   216  
   217  	calls = s.servicePrincipalCreator.Calls()
   218  	c.Assert(calls, gc.HasLen, 2)
   219  	c.Assert(calls[0].FuncName, gc.Equals, "Create")
   220  	c.Assert(calls[0].Args[1], jc.DeepEquals, azureauth.ServicePrincipalParams{
   221  		GraphEndpoint:   "https://graph.invalid/",
   222  		GraphResourceId: "https://graph.invalid/",
   223  		GraphAuthorizer: autorest.NewBearerAuthorizer(&adal.Token{
   224  			AccessToken: "test-account1-id|https://graph.invalid/|access-token",
   225  			Type:        "Bearer",
   226  		}),
   227  		ResourceManagerEndpoint:   "https://arm.invalid/",
   228  		ResourceManagerResourceId: "https://arm.invalid/",
   229  		ResourceManagerAuthorizer: autorest.NewBearerAuthorizer(&adal.Token{
   230  			AccessToken: "test-account1-id|https://arm.invalid/|access-token",
   231  			Type:        "Bearer",
   232  		}),
   233  		SubscriptionId: "test-account1-id",
   234  		TenantId:       "tenant-id",
   235  	})
   236  	c.Assert(calls[1].FuncName, gc.Equals, "Create")
   237  	c.Assert(calls[1].Args[1], jc.DeepEquals, azureauth.ServicePrincipalParams{
   238  		GraphEndpoint:   "https://graph.invalid/",
   239  		GraphResourceId: "https://graph.invalid/",
   240  		GraphAuthorizer: autorest.NewBearerAuthorizer(&adal.Token{
   241  			AccessToken: "test-account2-id|https://graph.invalid/|access-token",
   242  			Type:        "Bearer",
   243  		}),
   244  		ResourceManagerEndpoint:   "https://arm.invalid/",
   245  		ResourceManagerResourceId: "https://arm.invalid/",
   246  		ResourceManagerAuthorizer: autorest.NewBearerAuthorizer(&adal.Token{
   247  			AccessToken: "test-account2-id|https://arm.invalid/|access-token",
   248  			Type:        "Bearer",
   249  		}),
   250  		SubscriptionId: "test-account2-id",
   251  		TenantId:       "tenant-id",
   252  	})
   253  }
   254  
   255  func (s *credentialsSuite) TestDetectCredentialsTwoAccountsOneError(c *gc.C) {
   256  	s.azureCLI.Accounts = []azurecli.Account{{
   257  		CloudName: "AzureCloud",
   258  		ID:        "test-account1-id",
   259  		IsDefault: false,
   260  		Name:      "test-account1",
   261  		State:     "Enabled",
   262  		TenantId:  "tenant-id",
   263  	}, {
   264  		CloudName: "AzureCloud",
   265  		ID:        "test-account2-id",
   266  		IsDefault: true,
   267  		Name:      "test-account2",
   268  		State:     "Enabled",
   269  		TenantId:  "tenant-id",
   270  	}}
   271  	s.azureCLI.Clouds = []azurecli.Cloud{{
   272  		Endpoints: azurecli.CloudEndpoints{
   273  			ActiveDirectoryGraphResourceID: "https://graph.invalid/",
   274  			ResourceManager:                "https://arm.invalid/",
   275  		},
   276  		IsActive: true,
   277  		Name:     "AzureCloud",
   278  	}}
   279  	s.azureCLI.SetErrors(nil, nil, nil, nil, errors.New("test error"))
   280  	cred, err := s.provider.DetectCredentials()
   281  	c.Assert(err, jc.ErrorIsNil)
   282  	c.Assert(cred, gc.Not(gc.IsNil))
   283  	c.Assert(cred.DefaultCredential, gc.Equals, "")
   284  	c.Assert(cred.DefaultRegion, gc.Equals, "")
   285  	c.Assert(cred.AuthCredentials, gc.HasLen, 1)
   286  	c.Assert(cred.AuthCredentials["test-account1"].Label, gc.Equals, "AzureCloud subscription test-account1")
   287  
   288  	calls := s.azureCLI.Calls()
   289  	c.Assert(calls, gc.HasLen, 5)
   290  	c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts")
   291  	c.Assert(calls[1].FuncName, gc.Equals, "ListClouds")
   292  	c.Assert(calls[2].FuncName, gc.Equals, "GetAccessToken")
   293  	c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{"test-account1-id", "https://graph.invalid/"})
   294  	c.Assert(calls[3].FuncName, gc.Equals, "GetAccessToken")
   295  	c.Assert(calls[3].Args, jc.DeepEquals, []interface{}{"test-account1-id", "https://arm.invalid/"})
   296  	c.Assert(calls[4].FuncName, gc.Equals, "GetAccessToken")
   297  	c.Assert(calls[4].Args, jc.DeepEquals, []interface{}{"test-account2-id", "https://graph.invalid/"})
   298  
   299  	calls = s.servicePrincipalCreator.Calls()
   300  	c.Assert(calls, gc.HasLen, 1)
   301  	c.Assert(calls[0].FuncName, gc.Equals, "Create")
   302  	c.Assert(calls[0].Args[1], jc.DeepEquals, azureauth.ServicePrincipalParams{
   303  		GraphEndpoint:   "https://graph.invalid/",
   304  		GraphResourceId: "https://graph.invalid/",
   305  		GraphAuthorizer: autorest.NewBearerAuthorizer(&adal.Token{
   306  			AccessToken: "test-account1-id|https://graph.invalid/|access-token",
   307  			Type:        "Bearer",
   308  		}),
   309  		ResourceManagerEndpoint:   "https://arm.invalid/",
   310  		ResourceManagerResourceId: "https://arm.invalid/",
   311  		ResourceManagerAuthorizer: autorest.NewBearerAuthorizer(&adal.Token{
   312  			AccessToken: "test-account1-id|https://arm.invalid/|access-token",
   313  			Type:        "Bearer",
   314  		}),
   315  		SubscriptionId: "test-account1-id",
   316  		TenantId:       "tenant-id",
   317  	})
   318  }
   319  
   320  func (s *credentialsSuite) TestFinalizeCredentialInteractive(c *gc.C) {
   321  	in := cloud.NewCredential("interactive", map[string]string{"subscription-id": "subscription"})
   322  	ctx := cmdtesting.Context(c)
   323  	out, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{
   324  		Credential:            in,
   325  		CloudEndpoint:         "https://arm.invalid",
   326  		CloudStorageEndpoint:  "https://core.invalid",
   327  		CloudIdentityEndpoint: "https://graph.invalid",
   328  	})
   329  	c.Assert(err, jc.ErrorIsNil)
   330  	c.Assert(out, gc.NotNil)
   331  	c.Assert(out.AuthType(), gc.Equals, cloud.AuthType("service-principal-secret"))
   332  	c.Assert(out.Attributes(), jc.DeepEquals, map[string]string{
   333  		"application-id":       "appid",
   334  		"application-password": "service-principal-password",
   335  		"subscription-id":      "subscription",
   336  	})
   337  
   338  	s.servicePrincipalCreator.CheckCallNames(c, "InteractiveCreate")
   339  	args := s.servicePrincipalCreator.Calls()[0].Args
   340  	c.Assert(args[2], jc.DeepEquals, azureauth.ServicePrincipalParams{
   341  		GraphEndpoint:             "https://graph.invalid",
   342  		GraphResourceId:           "https://graph.invalid/",
   343  		ResourceManagerEndpoint:   "https://arm.invalid",
   344  		ResourceManagerResourceId: "https://management.core.invalid/",
   345  		SubscriptionId:            "subscription",
   346  	})
   347  }
   348  
   349  func (s *credentialsSuite) TestFinalizeCredentialInteractiveError(c *gc.C) {
   350  	in := cloud.NewCredential("interactive", map[string]string{"subscription-id": "subscription"})
   351  	s.servicePrincipalCreator.SetErrors(errors.New("blargh"))
   352  	ctx := cmdtesting.Context(c)
   353  	_, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{
   354  		Credential:            in,
   355  		CloudEndpoint:         "https://arm.invalid",
   356  		CloudIdentityEndpoint: "https://graph.invalid",
   357  	})
   358  	c.Assert(err, gc.ErrorMatches, "blargh")
   359  }
   360  
   361  func (s *credentialsSuite) TestFinalizeCredentialAzureCLI(c *gc.C) {
   362  	s.azureCLI.Accounts = []azurecli.Account{{
   363  		CloudName: "AzureCloud",
   364  		ID:        "test-account1-id",
   365  		IsDefault: true,
   366  		Name:      "test-account1",
   367  		State:     "Enabled",
   368  		TenantId:  "tenant-id",
   369  	}, {
   370  		CloudName: "AzureCloud",
   371  		ID:        "test-account2-id",
   372  		IsDefault: false,
   373  		Name:      "test-account2",
   374  		State:     "Enabled",
   375  		TenantId:  "tenant-id",
   376  	}}
   377  	s.azureCLI.Clouds = []azurecli.Cloud{{
   378  		Endpoints: azurecli.CloudEndpoints{
   379  			ActiveDirectoryGraphResourceID: "https://graph.invalid/",
   380  			ResourceManager:                "https://arm.invalid/",
   381  		},
   382  		IsActive: true,
   383  		Name:     "AzureCloud",
   384  	}}
   385  	in := cloud.NewCredential("interactive", nil)
   386  	ctx := cmdtesting.Context(c)
   387  	cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{
   388  		Credential:            in,
   389  		CloudEndpoint:         "https://arm.invalid",
   390  		CloudIdentityEndpoint: "https://graph.invalid",
   391  	})
   392  	c.Assert(err, jc.ErrorIsNil)
   393  	c.Assert(cred, gc.Not(gc.IsNil))
   394  	c.Assert(cred.AuthType(), gc.Equals, cloud.AuthType("service-principal-secret"))
   395  	attrs := cred.Attributes()
   396  	c.Assert(attrs["subscription-id"], gc.Equals, "test-account1-id")
   397  	c.Assert(attrs["application-id"], gc.Equals, "appid")
   398  	c.Assert(attrs["application-password"], gc.Equals, "service-principal-password")
   399  }
   400  
   401  func (s *credentialsSuite) TestFinalizeCredentialAzureCLIShowAccountError(c *gc.C) {
   402  	s.azureCLI.Accounts = []azurecli.Account{{
   403  		CloudName: "AzureCloud",
   404  		ID:        "test-account1-id",
   405  		IsDefault: true,
   406  		Name:      "test-account1",
   407  		State:     "Enabled",
   408  		TenantId:  "tenant-id",
   409  	}, {
   410  		CloudName: "AzureCloud",
   411  		ID:        "test-account2-id",
   412  		IsDefault: false,
   413  		Name:      "test-account2",
   414  		State:     "Enabled",
   415  		TenantId:  "tenant-id",
   416  	}}
   417  	s.azureCLI.Clouds = []azurecli.Cloud{{
   418  		Endpoints: azurecli.CloudEndpoints{
   419  			ActiveDirectoryGraphResourceID: "https://graph.invalid/",
   420  			ResourceManager:                "https://arm.invalid/",
   421  		},
   422  		IsActive: true,
   423  		Name:     "AzureCloud",
   424  	}}
   425  	s.azureCLI.SetErrors(nil, errors.New("test error"))
   426  	in := cloud.NewCredential("interactive", nil)
   427  	ctx := cmdtesting.Context(c)
   428  	cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{
   429  		Credential:            in,
   430  		CloudEndpoint:         "https://arm.invalid",
   431  		CloudIdentityEndpoint: "https://graph.invalid",
   432  	})
   433  	c.Assert(err, gc.ErrorMatches, `cannot get accounts: test error`)
   434  	c.Assert(cred, gc.IsNil)
   435  }
   436  
   437  func (s *credentialsSuite) TestFinalizeCredentialAzureCLIGraphTokenError(c *gc.C) {
   438  	s.azureCLI.Accounts = []azurecli.Account{{
   439  		CloudName: "AzureCloud",
   440  		ID:        "test-account1-id",
   441  		IsDefault: true,
   442  		Name:      "test-account1",
   443  		State:     "Enabled",
   444  		TenantId:  "tenant-id",
   445  	}, {
   446  		CloudName: "AzureCloud",
   447  		ID:        "test-account2-id",
   448  		IsDefault: false,
   449  		Name:      "test-account2",
   450  		State:     "Enabled",
   451  		TenantId:  "tenant-id",
   452  	}}
   453  	s.azureCLI.Clouds = []azurecli.Cloud{{
   454  		Endpoints: azurecli.CloudEndpoints{
   455  			ActiveDirectoryGraphResourceID: "https://graph.invalid/",
   456  			ResourceManager:                "https://arm.invalid/",
   457  		},
   458  		IsActive: true,
   459  		Name:     "AzureCloud",
   460  	}}
   461  	s.azureCLI.SetErrors(nil, nil, nil, errors.New("test error"))
   462  	in := cloud.NewCredential("interactive", nil)
   463  	ctx := cmdtesting.Context(c)
   464  	cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{
   465  		Credential:            in,
   466  		CloudEndpoint:         "https://arm.invalid",
   467  		CloudIdentityEndpoint: "https://graph.invalid",
   468  	})
   469  	c.Assert(err, gc.ErrorMatches, `cannot get access token for test-account1-id: test error`)
   470  	c.Assert(cred, gc.IsNil)
   471  }
   472  
   473  func (s *credentialsSuite) TestFinalizeCredentialAzureCLIResourceManagerTokenError(c *gc.C) {
   474  	s.azureCLI.Accounts = []azurecli.Account{{
   475  		CloudName: "AzureCloud",
   476  		ID:        "test-account1-id",
   477  		IsDefault: true,
   478  		Name:      "test-account1",
   479  		State:     "Enabled",
   480  		TenantId:  "tenant-id",
   481  	}, {
   482  		CloudName: "AzureCloud",
   483  		ID:        "test-account2-id",
   484  		IsDefault: false,
   485  		Name:      "test-account2",
   486  		State:     "Enabled",
   487  		TenantId:  "tenant-id",
   488  	}}
   489  	s.azureCLI.Clouds = []azurecli.Cloud{{
   490  		Endpoints: azurecli.CloudEndpoints{
   491  			ActiveDirectoryGraphResourceID: "https://graph.invalid/",
   492  			ResourceManager:                "https://arm.invalid/",
   493  		},
   494  		IsActive: true,
   495  		Name:     "AzureCloud",
   496  	}}
   497  	s.azureCLI.SetErrors(nil, nil, nil, errors.New("test error"))
   498  	in := cloud.NewCredential("interactive", nil)
   499  	ctx := cmdtesting.Context(c)
   500  	cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{
   501  		Credential:            in,
   502  		CloudEndpoint:         "https://arm.invalid",
   503  		CloudIdentityEndpoint: "https://graph.invalid",
   504  	})
   505  	c.Assert(err, gc.ErrorMatches, `cannot get access token for test-account1-id: test error`)
   506  	c.Assert(cred, gc.IsNil)
   507  }
   508  
   509  func (s *credentialsSuite) TestFinalizeCredentialAzureCLIServicePrincipalError(c *gc.C) {
   510  	s.azureCLI.Accounts = []azurecli.Account{{
   511  		CloudName: "AzureCloud",
   512  		ID:        "test-account1-id",
   513  		IsDefault: true,
   514  		Name:      "test-account1",
   515  		State:     "Enabled",
   516  		TenantId:  "tenant-id",
   517  	}, {
   518  		CloudName: "AzureCloud",
   519  		ID:        "test-account2-id",
   520  		IsDefault: false,
   521  		Name:      "test-account2",
   522  		State:     "Enabled",
   523  		TenantId:  "tenant-id",
   524  	}}
   525  	s.azureCLI.Clouds = []azurecli.Cloud{{
   526  		Endpoints: azurecli.CloudEndpoints{
   527  			ActiveDirectoryGraphResourceID: "https://graph.invalid/",
   528  			ResourceManager:                "https://arm.invalid/",
   529  		},
   530  		IsActive: true,
   531  		Name:     "AzureCloud",
   532  	}}
   533  	s.servicePrincipalCreator.SetErrors(errors.New("test error"))
   534  	in := cloud.NewCredential("interactive", nil)
   535  	ctx := cmdtesting.Context(c)
   536  	cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{
   537  		Credential:            in,
   538  		CloudEndpoint:         "https://arm.invalid",
   539  		CloudIdentityEndpoint: "https://graph.invalid",
   540  	})
   541  	c.Assert(err, gc.ErrorMatches, `cannot get service principal: test error`)
   542  	c.Assert(cred, gc.IsNil)
   543  }
   544  
   545  func (s *credentialsSuite) TestFinalizeCredentialAzureCLIDeviceFallback(c *gc.C) {
   546  	s.azureCLI.Accounts = []azurecli.Account{{
   547  		CloudName: "AzureCloud",
   548  		ID:        "test-account1-id",
   549  		IsDefault: true,
   550  		Name:      "test-account1",
   551  		State:     "Enabled",
   552  		TenantId:  "tenant-id",
   553  	}, {
   554  		CloudName: "AzureCloud",
   555  		ID:        "test-account2-id",
   556  		IsDefault: false,
   557  		Name:      "test-account2",
   558  		State:     "Enabled",
   559  		TenantId:  "tenant-id",
   560  	}}
   561  	s.azureCLI.Clouds = []azurecli.Cloud{{
   562  		Endpoints: azurecli.CloudEndpoints{
   563  			ActiveDirectoryGraphResourceID: "https://graph.invalid/",
   564  			ResourceManager:                "https://arm.invalid/",
   565  		},
   566  		IsActive: true,
   567  		Name:     "AzureCloud",
   568  	}}
   569  	s.azureCLI.SetErrors(nil, nil, errors.New("test error"))
   570  	in := cloud.NewCredential("interactive", nil)
   571  	ctx := cmdtesting.Context(c)
   572  	cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{
   573  		Credential:            in,
   574  		CloudEndpoint:         "https://arm.invalid",
   575  		CloudIdentityEndpoint: "https://graph.invalid",
   576  	})
   577  	c.Assert(err, jc.ErrorIsNil)
   578  	c.Assert(cred, gc.Not(gc.IsNil))
   579  	c.Assert(cred.AuthType(), gc.Equals, cloud.AuthType("service-principal-secret"))
   580  	attrs := cred.Attributes()
   581  	c.Assert(attrs["subscription-id"], gc.Equals, "test-account1-id")
   582  	c.Assert(attrs["application-id"], gc.Equals, "appid")
   583  	c.Assert(attrs["application-password"], gc.Equals, "service-principal-password")
   584  	s.servicePrincipalCreator.CheckCallNames(c, "InteractiveCreate")
   585  }
   586  
   587  type servicePrincipalCreator struct {
   588  	testing.Stub
   589  }
   590  
   591  func (c *servicePrincipalCreator) InteractiveCreate(sdkCtx context.Context, stderr io.Writer, params azureauth.ServicePrincipalParams) (appId, password string, _ error) {
   592  	c.MethodCall(c, "InteractiveCreate", sdkCtx, stderr, params)
   593  	return "appid", "service-principal-password", c.NextErr()
   594  }
   595  
   596  func (c *servicePrincipalCreator) Create(sdkCtx context.Context, params azureauth.ServicePrincipalParams) (appId, password string, _ error) {
   597  	c.MethodCall(c, "Create", sdkCtx, params)
   598  	return "appid", "service-principal-password", c.NextErr()
   599  }
   600  
   601  type azureCLI struct {
   602  	testing.Stub
   603  	Accounts []azurecli.Account
   604  	Clouds   []azurecli.Cloud
   605  }
   606  
   607  func (e *azureCLI) ListAccounts() ([]azurecli.Account, error) {
   608  	e.MethodCall(e, "ListAccounts")
   609  	if err := e.NextErr(); err != nil {
   610  		return nil, err
   611  	}
   612  	return e.Accounts, nil
   613  }
   614  
   615  func (e *azureCLI) FindAccountsWithCloudName(name string) ([]azurecli.Account, error) {
   616  	e.MethodCall(e, "FindAccountsWithCloudName", name)
   617  	if err := e.NextErr(); err != nil {
   618  		return nil, err
   619  	}
   620  	var accs []azurecli.Account
   621  	for _, acc := range e.Accounts {
   622  		if acc.CloudName == name {
   623  			accs = append(accs, acc)
   624  		}
   625  	}
   626  	return accs, nil
   627  }
   628  
   629  func (e *azureCLI) ShowAccount(subscription string) (*azurecli.Account, error) {
   630  	e.MethodCall(e, "ShowAccount", subscription)
   631  	if err := e.NextErr(); err != nil {
   632  		return nil, err
   633  	}
   634  	return e.findAccount(subscription)
   635  }
   636  
   637  func (e *azureCLI) findAccount(subscription string) (*azurecli.Account, error) {
   638  	for _, acc := range e.Accounts {
   639  		if acc.ID == subscription {
   640  			return &acc, nil
   641  		}
   642  		if subscription == "" && acc.IsDefault {
   643  			return &acc, nil
   644  		}
   645  	}
   646  	return nil, errors.New("account not found")
   647  }
   648  
   649  func (e *azureCLI) GetAccessToken(subscription, resource string) (*azurecli.AccessToken, error) {
   650  	e.MethodCall(e, "GetAccessToken", subscription, resource)
   651  	if err := e.NextErr(); err != nil {
   652  		return nil, err
   653  	}
   654  	acc, err := e.findAccount(subscription)
   655  	if err != nil {
   656  		return nil, err
   657  	}
   658  	return &azurecli.AccessToken{
   659  		AccessToken: fmt.Sprintf("%s|%s|access-token", subscription, resource),
   660  		Tenant:      acc.TenantId,
   661  		TokenType:   "Bearer",
   662  	}, nil
   663  }
   664  
   665  func (e *azureCLI) ShowCloud(name string) (*azurecli.Cloud, error) {
   666  	e.MethodCall(e, "ShowCloud", name)
   667  	if err := e.NextErr(); err != nil {
   668  		return nil, err
   669  	}
   670  	for _, cloud := range e.Clouds {
   671  		if cloud.Name == name || name == "" {
   672  			return &cloud, nil
   673  		}
   674  	}
   675  	return nil, errors.New("cloud not found")
   676  }
   677  
   678  func (e *azureCLI) FindCloudsWithResourceManagerEndpoint(url string) ([]azurecli.Cloud, error) {
   679  	e.MethodCall(e, "FindCloudsWithResourceManagerEndpoint", url)
   680  	if err := e.NextErr(); err != nil {
   681  		return nil, err
   682  	}
   683  	for _, cloud := range e.Clouds {
   684  		if cloud.Endpoints.ResourceManager == url {
   685  			return []azurecli.Cloud{cloud}, nil
   686  		}
   687  	}
   688  	return nil, errors.New("cloud not found")
   689  }
   690  
   691  func (e *azureCLI) ListClouds() ([]azurecli.Cloud, error) {
   692  	e.MethodCall(e, "ListClouds")
   693  	if err := e.NextErr(); err != nil {
   694  		return nil, err
   695  	}
   696  	return e.Clouds, nil
   697  }