Skip to content

Commit 6254eb8

Browse files
authored
feat: add oidc account support for azure web app deployment target (#534)
1 parent e6a5343 commit 6254eb8

File tree

2 files changed

+123
-35
lines changed

2 files changed

+123
-35
lines changed

pkg/cmd/target/azure-web-app/create/create.go

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ import (
2424
"github.com/spf13/cobra"
2525
)
2626

27-
type GetAllAzureAccounts func() ([]*accounts.AzureServicePrincipalAccount, error)
28-
type GetAllAzureWebApps func(account *accounts.AzureServicePrincipalAccount) ([]*azure.AzureWebApp, error)
29-
type GetAllAzureWebAppSlots func(account *accounts.AzureServicePrincipalAccount, app *azure.AzureWebApp) ([]*azure.AzureWebAppSlot, error)
27+
type GetAllAzureAccounts func() ([]accounts.IAccount, error)
28+
type GetAllAzureWebApps func(account accounts.IAccount) ([]*azure.AzureWebApp, error)
29+
type GetAllAzureWebAppSlots func(account accounts.IAccount, app *azure.AzureWebApp) ([]*azure.AzureWebAppSlot, error)
3030

3131
const (
3232
FlagName = "name"
@@ -85,13 +85,13 @@ func NewCreateOptions(createFlags *CreateFlags, dependencies *cmd.Dependencies)
8585
CreateTargetEnvironmentOptions: shared.NewCreateTargetEnvironmentOptions(dependencies),
8686
CreateTargetTenantOptions: shared.NewCreateTargetTenantOptions(dependencies),
8787
WorkerPoolOptions: shared.NewWorkerPoolOptionsForCreateTarget(dependencies),
88-
GetAllAzureAccounts: func() ([]*accounts.AzureServicePrincipalAccount, error) {
88+
GetAllAzureAccounts: func() ([]accounts.IAccount, error) {
8989
return getAllAzureAccounts(*dependencies.Client)
9090
},
91-
GetAllAzureWebApps: func(account *accounts.AzureServicePrincipalAccount) ([]*azure.AzureWebApp, error) {
91+
GetAllAzureWebApps: func(account accounts.IAccount) ([]*azure.AzureWebApp, error) {
9292
return getAllAzureWebapps(*dependencies.Client, account)
9393
},
94-
GetAllAzureWebAppSlots: func(account *accounts.AzureServicePrincipalAccount, webapp *azure.AzureWebApp) ([]*azure.AzureWebAppSlot, error) {
94+
GetAllAzureWebAppSlots: func(account accounts.IAccount, webapp *azure.AzureWebApp) ([]*azure.AzureWebAppSlot, error) {
9595
return getAllAzureWebAppSlots(*dependencies.Client, account, webapp)
9696
},
9797
}
@@ -115,7 +115,7 @@ func NewCmdCreate(f factory.Factory) *cobra.Command {
115115

116116
flags := cmd.Flags()
117117
flags.StringVarP(&createFlags.Name.Value, createFlags.Name.Name, "n", "", "A short, memorable, unique name for this Azure Web App.")
118-
flags.StringVar(&createFlags.Account.Value, createFlags.Account.Name, "", "The name or ID of the Azure Service Principal account")
118+
flags.StringVar(&createFlags.Account.Value, createFlags.Account.Name, "", "The name or ID of the Azure account")
119119
flags.StringVar(&createFlags.ResourceGroup.Value, createFlags.ResourceGroup.Name, "", "The resource group of the Azure Web App")
120120
flags.StringVar(&createFlags.WebApp.Value, createFlags.WebApp.Name, "", "The name of the Azure Web App for this deployment target")
121121
flags.StringVar(&createFlags.Slot.Value, createFlags.Slot.Name, "", "The name of the Azure Web App Slot for this deployment target")
@@ -145,6 +145,10 @@ func createRun(opts *CreateOptions) error {
145145
return err
146146
}
147147

148+
if !isAzureAccount(account) {
149+
return fmt.Errorf("account '%s' is not a valid Azure account", account.GetName())
150+
}
151+
148152
endpoint := machines.NewAzureWebAppEndpoint()
149153
endpoint.AccountID = account.GetID()
150154
endpoint.WebAppName = opts.WebApp.Value
@@ -212,15 +216,15 @@ func PromptMissing(opts *CreateOptions) error {
212216
return nil
213217
}
214218

215-
func PromptForAccount(opts *CreateOptions) (*accounts.AzureServicePrincipalAccount, error) {
216-
var account *accounts.AzureServicePrincipalAccount
219+
func PromptForAccount(opts *CreateOptions) (accounts.IAccount, error) {
220+
var account accounts.IAccount
217221
if opts.Account.Value == "" {
218222
selectedAccount, err := selectors.Select(
219223
opts.Ask,
220224
"Select the Azure Account to use\n",
221225
opts.GetAllAzureAccounts,
222-
func(p *accounts.AzureServicePrincipalAccount) string {
223-
return (*p).GetName()
226+
func(p accounts.IAccount) string {
227+
return fmt.Sprintf("%s (%s)", p.GetName(), getAzureAccountTypeName(p))
224228
})
225229
if err != nil {
226230
return nil, err
@@ -234,11 +238,11 @@ func PromptForAccount(opts *CreateOptions) (*accounts.AzureServicePrincipalAccou
234238
account = a
235239
}
236240

237-
opts.Account.Value = account.Name
241+
opts.Account.Value = account.GetName()
238242
return account, nil
239243
}
240244

241-
func PromptForWebApp(opts *CreateOptions, account *accounts.AzureServicePrincipalAccount) error {
245+
func PromptForWebApp(opts *CreateOptions, account accounts.IAccount) error {
242246
webapps, err := opts.GetAllAzureWebApps(account)
243247
if err != nil {
244248
return err
@@ -309,16 +313,47 @@ func PromptForWebApp(opts *CreateOptions, account *accounts.AzureServicePrincipa
309313
return nil
310314
}
311315

312-
func getAllAzureWebAppSlots(client client.Client, spAccount *accounts.AzureServicePrincipalAccount, webapp *azure.AzureWebApp) ([]*azure.AzureWebAppSlot, error) {
313-
slots, err := azure.GetWebSiteSlots(client, spAccount, webapp)
316+
func isAzureAccount(account accounts.IAccount) bool {
317+
switch account.GetAccountType() {
318+
case accounts.AccountTypeAzureServicePrincipal,
319+
accounts.AccountTypeAzureOIDC:
320+
return true
321+
default:
322+
return false
323+
}
324+
}
325+
326+
func getAzureAccountTypeName(account accounts.IAccount) string {
327+
switch account.GetAccountType() {
328+
case accounts.AccountTypeAzureServicePrincipal:
329+
return "Azure Service Principal"
330+
case accounts.AccountTypeAzureOIDC:
331+
return "Azure OIDC"
332+
default:
333+
return "Unknown"
334+
}
335+
}
336+
337+
func getAllAzureWebAppSlots(client client.Client, account accounts.IAccount, webapp *azure.AzureWebApp) ([]*azure.AzureWebAppSlot, error) {
338+
if !isAzureAccount(account) {
339+
return nil, fmt.Errorf("account '%s' is not an Azure account (type: %s)",
340+
account.GetName(), account.GetAccountType())
341+
}
342+
343+
slots, err := azure.GetWebSiteSlots(client, account, webapp)
314344
if err != nil {
315345
return nil, err
316346
}
317347

318348
return slots, nil
319349
}
320350

321-
func getAllAzureWebapps(client client.Client, account *accounts.AzureServicePrincipalAccount) ([]*azure.AzureWebApp, error) {
351+
func getAllAzureWebapps(client client.Client, account accounts.IAccount) ([]*azure.AzureWebApp, error) {
352+
if !isAzureAccount(account) {
353+
return nil, fmt.Errorf("account '%s' is not an Azure account (type: %s)",
354+
account.GetName(), account.GetAccountType())
355+
}
356+
322357
sites, err := azure.GetWebSites(client, account)
323358
if err != nil {
324359
return nil, err
@@ -327,23 +362,23 @@ func getAllAzureWebapps(client client.Client, account *accounts.AzureServicePrin
327362
return sites, nil
328363
}
329364

330-
func getAllAzureAccounts(client client.Client) ([]*accounts.AzureServicePrincipalAccount, error) {
365+
func getAllAzureAccounts(client client.Client) ([]accounts.IAccount, error) {
331366
allAccounts, err := client.Accounts.GetAll()
332367
if err != nil {
333368
return nil, err
334369
}
335370

336-
var spAccounts []*accounts.AzureServicePrincipalAccount
371+
var azureAccounts []accounts.IAccount
337372
for _, a := range allAccounts {
338-
if s, ok := a.(*accounts.AzureServicePrincipalAccount); ok {
339-
spAccounts = append(spAccounts, s)
373+
if isAzureAccount(a) {
374+
azureAccounts = append(azureAccounts, a)
340375
}
341376
}
342377

343-
return spAccounts, nil
378+
return azureAccounts, nil
344379
}
345380

346-
func getAzureAccount(opts *CreateOptions) (*accounts.AzureServicePrincipalAccount, error) {
381+
func getAzureAccount(opts *CreateOptions) (accounts.IAccount, error) {
347382
idOrName := opts.Account.Value
348383
allAccounts, err := opts.GetAllAzureAccounts()
349384
if err != nil {
@@ -352,9 +387,12 @@ func getAzureAccount(opts *CreateOptions) (*accounts.AzureServicePrincipalAccoun
352387

353388
for _, a := range allAccounts {
354389
if strings.EqualFold(a.GetID(), idOrName) || strings.EqualFold(a.GetName(), idOrName) {
390+
if !isAzureAccount(a) {
391+
return nil, fmt.Errorf("account %s is not an Azure account (type: %s)", idOrName, a.GetAccountType())
392+
}
355393
return a, nil
356394
}
357395
}
358396

359-
return nil, fmt.Errorf("cannot find account %s", idOrName)
397+
return nil, fmt.Errorf("cannot find Azure account %s", idOrName)
360398
}

pkg/cmd/target/azure-web-app/create/create_test.go

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func TestPromptForWebApp_FlagsSupplied(t *testing.T) {
2222
flags.Slot.Value = "production"
2323

2424
opts := create.NewCreateOptions(flags, &cmd.Dependencies{Ask: asker})
25-
opts.GetAllAzureWebApps = func(a *accounts.AzureServicePrincipalAccount) ([]*azure.AzureWebApp, error) {
25+
opts.GetAllAzureWebApps = func(a accounts.IAccount) ([]*azure.AzureWebApp, error) {
2626
return []*azure.AzureWebApp{
2727
{
2828
Name: "website",
@@ -56,7 +56,7 @@ func TestPromptForWebApp_NoFlagsSupplied(t *testing.T) {
5656
}
5757

5858
opts := create.NewCreateOptions(flags, &cmd.Dependencies{Ask: asker})
59-
opts.GetAllAzureWebApps = func(a *accounts.AzureServicePrincipalAccount) ([]*azure.AzureWebApp, error) {
59+
opts.GetAllAzureWebApps = func(a accounts.IAccount) ([]*azure.AzureWebApp, error) {
6060
return []*azure.AzureWebApp{
6161
webapp,
6262
{
@@ -66,7 +66,7 @@ func TestPromptForWebApp_NoFlagsSupplied(t *testing.T) {
6666
},
6767
}, nil
6868
}
69-
opts.GetAllAzureWebAppSlots = func(acc *accounts.AzureServicePrincipalAccount, wa *azure.AzureWebApp) ([]*azure.AzureWebAppSlot, error) {
69+
opts.GetAllAzureWebAppSlots = func(acc accounts.IAccount, wa *azure.AzureWebApp) ([]*azure.AzureWebAppSlot, error) {
7070
return []*azure.AzureWebAppSlot{
7171
{Name: "production"},
7272
{Name: "test"},
@@ -93,7 +93,7 @@ func TestPromptForWebApp_NoSlotsAvailable(t *testing.T) {
9393
flags.WebApp.Value = "website"
9494
flags.ResourceGroup.Value = "rg1"
9595
opts := create.NewCreateOptions(flags, &cmd.Dependencies{Ask: asker})
96-
opts.GetAllAzureWebApps = func(a *accounts.AzureServicePrincipalAccount) ([]*azure.AzureWebApp, error) {
96+
opts.GetAllAzureWebApps = func(a accounts.IAccount) ([]*azure.AzureWebApp, error) {
9797
return []*azure.AzureWebApp{
9898
{
9999
Name: "website",
@@ -103,7 +103,7 @@ func TestPromptForWebApp_NoSlotsAvailable(t *testing.T) {
103103
}, nil
104104
}
105105

106-
opts.GetAllAzureWebAppSlots = func(acc *accounts.AzureServicePrincipalAccount, wa *azure.AzureWebApp) ([]*azure.AzureWebAppSlot, error) {
106+
opts.GetAllAzureWebAppSlots = func(acc accounts.IAccount, wa *azure.AzureWebApp) ([]*azure.AzureWebAppSlot, error) {
107107
return []*azure.AzureWebAppSlot{}, nil
108108
}
109109

@@ -123,39 +123,89 @@ func TestPromptForAccount_FlagSupplied(t *testing.T) {
123123
flags.Account.Value = "Azure Account"
124124

125125
opts := create.NewCreateOptions(flags, &cmd.Dependencies{Ask: asker})
126-
opts.GetAllAzureAccounts = func() ([]*accounts.AzureServicePrincipalAccount, error) {
126+
opts.GetAllAzureAccounts = func() ([]accounts.IAccount, error) {
127127
a, _ := accounts.NewAzureServicePrincipalAccount("Azure account", uuid.New(), uuid.New(), uuid.New(), core.NewSensitiveValue("password"))
128-
return []*accounts.AzureServicePrincipalAccount{a}, nil
128+
return []accounts.IAccount{a}, nil
129129
}
130130

131131
a, err := create.PromptForAccount(opts)
132132

133133
checkRemainingPrompts()
134134
assert.NoError(t, err)
135135
assert.NotNil(t, a)
136-
assert.Equal(t, "Azure account", a.Name)
136+
assert.Equal(t, "Azure account", a.GetName())
137137
assert.Equal(t, "Azure account", opts.Account.Value)
138138
}
139139

140140
func TestPromptForAccount_NoFlagSupplied(t *testing.T) {
141141
pa := []*testutil.PA{
142-
testutil.NewSelectPrompt("Select the Azure Account to use\n", "", []string{"Azure account 1", "Azure account the second"}, "Azure account the second"),
142+
testutil.NewSelectPrompt("Select the Azure Account to use\n", "", []string{"Azure account 1 (Azure Service Principal)", "Azure account the second (Azure Service Principal)"}, "Azure account the second (Azure Service Principal)"),
143143
}
144144

145145
asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa)
146146
flags := create.NewCreateFlags()
147147
opts := create.NewCreateOptions(flags, &cmd.Dependencies{Ask: asker})
148-
opts.GetAllAzureAccounts = func() ([]*accounts.AzureServicePrincipalAccount, error) {
148+
opts.GetAllAzureAccounts = func() ([]accounts.IAccount, error) {
149149
a1, _ := accounts.NewAzureServicePrincipalAccount("Azure account 1", uuid.New(), uuid.New(), uuid.New(), core.NewSensitiveValue("password"))
150150
a2, _ := accounts.NewAzureServicePrincipalAccount("Azure account the second", uuid.New(), uuid.New(), uuid.New(), core.NewSensitiveValue("password"))
151-
return []*accounts.AzureServicePrincipalAccount{a1, a2}, nil
151+
return []accounts.IAccount{a1, a2}, nil
152152
}
153153

154154
a, err := create.PromptForAccount(opts)
155155

156156
checkRemainingPrompts()
157157
assert.NoError(t, err)
158158
assert.NotNil(t, a)
159-
assert.Equal(t, "Azure account the second", a.Name)
159+
assert.Equal(t, "Azure account the second", a.GetName())
160160
assert.Equal(t, "Azure account the second", opts.Account.Value)
161161
}
162+
163+
func TestPromptForAccount_OIDCFlagSupplied(t *testing.T) {
164+
pa := []*testutil.PA{}
165+
166+
asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa)
167+
flags := create.NewCreateFlags()
168+
flags.Account.Value = "Azure OIDC Account"
169+
170+
opts := create.NewCreateOptions(flags, &cmd.Dependencies{Ask: asker})
171+
opts.GetAllAzureAccounts = func() ([]accounts.IAccount, error) {
172+
oidcAccount, _ := accounts.NewAzureOIDCAccount("Azure OIDC Account", uuid.New(), uuid.New(), uuid.New())
173+
return []accounts.IAccount{oidcAccount}, nil
174+
}
175+
176+
a, err := create.PromptForAccount(opts)
177+
178+
checkRemainingPrompts()
179+
assert.NoError(t, err)
180+
assert.NotNil(t, a)
181+
assert.Equal(t, "Azure OIDC Account", a.GetName())
182+
assert.Equal(t, "Azure OIDC Account", opts.Account.Value)
183+
}
184+
185+
func TestPromptForWebApp_OIDCAccount(t *testing.T) {
186+
pa := []*testutil.PA{}
187+
188+
asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa)
189+
flags := create.NewCreateFlags()
190+
flags.WebApp.Value = "website"
191+
flags.ResourceGroup.Value = "rg1"
192+
flags.Slot.Value = "production"
193+
194+
opts := create.NewCreateOptions(flags, &cmd.Dependencies{Ask: asker})
195+
opts.GetAllAzureWebApps = func(a accounts.IAccount) ([]*azure.AzureWebApp, error) {
196+
return []*azure.AzureWebApp{
197+
{
198+
Name: "website",
199+
Region: "West US",
200+
ResourceGroup: "rg1",
201+
},
202+
}, nil
203+
}
204+
205+
oidcAccount, _ := accounts.NewAzureOIDCAccount("Azure OIDC Account", uuid.New(), uuid.New(), uuid.New())
206+
207+
err := create.PromptForWebApp(opts, oidcAccount)
208+
209+
checkRemainingPrompts()
210+
assert.NoError(t, err)
211+
}

0 commit comments

Comments
 (0)