Merge branch 'main' into main

This commit is contained in:
Kevin Franklin Kim 2025-08-25 09:56:12 +02:00 committed by GitHub
commit 4c13fb8534
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 153 additions and 50 deletions

View File

@ -13,7 +13,7 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0

View File

@ -16,13 +16,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- uses: sourcemeta/jsonschema@v9.3.5
- uses: sourcemeta/jsonschema@v11.1.1
#- uses: gotesttools/gotestfmt-action@v2
# with:

View File

@ -2,6 +2,8 @@ package az
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/foomo/posh/pkg/log"
@ -11,11 +13,21 @@ import (
func AuthChecker(ctx context.Context, l log.Logger) []check.Info {
name := "Azure"
out, err := shell.New(ctx, l, "az", "account", "list", "--output", "none").Quiet().CombinedOutput()
out, err := shell.New(ctx, l, "az", "account", "list", "--output", "json").Quiet().CombinedOutput()
if err != nil {
return []check.Info{check.NewFailureInfo(name, "Error: "+err.Error())}
} else if strings.Contains(string(out), "az login") {
return []check.Info{check.NewNoteInfo(name, "Unauthenticated")}
}
return []check.Info{check.NewSuccessInfo(name, "Authenticated")}
var res []map[string]any
note := "Authenticated"
if err := json.Unmarshal(out, &res); err == nil {
if len(res) > 0 && res[0]["user"] != nil {
if user, ok := res[0]["user"].(map[string]any); ok {
note += fmt.Sprintf(" as %s: %s", user["type"], user["name"])
}
}
}
return []check.Info{check.NewSuccessInfo(name, note)}
}

View File

@ -69,7 +69,14 @@ func NewCommand(l log.Logger, az *AZ, kubectl *kubectl.Kubectl, opts ...CommandO
{
Name: "login",
Description: "Log in to Azure",
Execute: inst.login,
Flags: func(ctx context.Context, r *readline.Readline, fs *readline.FlagSets) error {
fs.Internal().String("service-principal", "", "Service principal to use for authentication")
if err := fs.Internal().SetValues("service-principal", inst.az.Config().ServicePrincipalNames()...); err != nil {
return err
}
return nil
},
Execute: inst.login,
},
{
Name: "logout",
@ -240,10 +247,34 @@ func (c *Command) exec(ctx context.Context, r *readline.Readline) error {
func (c *Command) login(ctx context.Context, r *readline.Readline) error {
fs := r.FlagSets().Default()
return shell.New(ctx, c.l, "az", "login",
"--allow-no-subscriptions",
"--tenant", c.az.Config().TenantID,
).
ifs := r.FlagSets().Internal()
servicePricipal, err := ifs.GetString("service-principal")
if err != nil {
return err
}
var args []string
if servicePricipal != "" {
sp, err := c.az.cfg.ServicePrincipal(servicePricipal)
if err != nil {
return err
}
args = append(args,
"--service-principal",
"--username", sp.ClientID,
"--password", sp.ClientSecret,
"--tenant", sp.TenantID,
)
} else {
args = append(args,
"--allow-no-subscriptions",
"--tenant", c.az.Config().TenantID,
)
}
return shell.New(ctx, c.l, "az", "login").
Args(args...).
Args(fs.Visited().Args()...).
Args(r.AdditionalArgs()...).
Args(r.AdditionalFlags()...).

View File

@ -14,6 +14,17 @@ type Config struct {
TenantID string `json:"tenantId" yaml:"tenantId"`
// Subscription configurations
Subscriptions map[string]Subscription `json:"subscriptions" yaml:"subscriptions"`
// Authentication service principals
ServicePrincipals map[string]ServicePrincipal `json:"servicePrincipals" yaml:"servicePrincipals"`
}
type ServicePrincipal struct {
// Tenant id
TenantID string `json:"tenantId" yaml:"tenantId"`
// Application client id
ClientID string `json:"clientId" yaml:"clientId"`
// Application password
ClientSecret string `json:"clientSecret" yaml:"clientSecret"`
}
func (c Config) Subscription(name string) (Subscription, error) {
@ -29,3 +40,17 @@ func (c Config) SubscriptionNames() []string {
sort.Strings(keys)
return keys
}
func (c Config) ServicePrincipal(name string) (ServicePrincipal, error) {
value, ok := c.ServicePrincipals[name]
if !ok {
return ServicePrincipal{}, errors.Errorf("service principal not found: %s", name)
}
return value, nil
}
func (c Config) ServicePrincipalNames() []string {
keys := lo.Keys(c.ServicePrincipals)
sort.Strings(keys)
return keys
}

View File

@ -53,6 +53,13 @@
},
"type": "object",
"description": "Subscription configurations"
},
"servicePrincipals": {
"additionalProperties": {
"$ref": "#/$defs/ServicePrincipal"
},
"type": "object",
"description": "Authentication service principals"
}
},
"additionalProperties": false,
@ -60,7 +67,31 @@
"required": [
"configPath",
"tenantId",
"subscriptions"
"subscriptions",
"servicePrincipals"
]
},
"ServicePrincipal": {
"properties": {
"tenantId": {
"type": "string",
"description": "Tenant id"
},
"clientId": {
"type": "string",
"description": "Application client id"
},
"clientSecret": {
"type": "string",
"description": "Application password"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"tenantId",
"clientId",
"clientSecret"
]
},
"Subscription": {

View File

@ -1,6 +1,11 @@
# yaml-language-server: $schema=config.schema.json
configPath: .posh/config/azure
tenantId: xxxx-xx-xx-xx-xxxx
servicePrincipals:
dev:
tenantId: xxxx-xx-xx-xx-xxxx
clientId: xxxx-xx-xx-xx-xxxx
clientSecret: xxxx-xx-xx-xx-xxxx
subscriptions:
development:
name: xxxx-xx-xx-xx-xxxx

2
go.mod
View File

@ -11,7 +11,7 @@ require (
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/c-bata/go-prompt v0.2.6
github.com/cloudrecipes/packagejson v1.0.0
github.com/digitalocean/godo v1.161.0
github.com/digitalocean/godo v1.162.0
github.com/foomo/go v0.0.3
github.com/foomo/gokazi v0.1.5
github.com/foomo/posh v0.13.0

4
go.sum
View File

@ -39,8 +39,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/digitalocean/godo v1.161.0 h1:Q/3ImcotZp0GV9FY/dnLj9TmfOd+a7ZN/UNuhgDHI/Q=
github.com/digitalocean/godo v1.161.0/go.mod h1:tYeiWY5ZXVpU48YaFv0M5irUFHXGorZpDNm7zzdWMzM=
github.com/digitalocean/godo v1.162.0 h1:7dtS9H8xsUuYtPf9w4eDsiRl0UlgzyCCSPWpMFOZKhg=
github.com/digitalocean/godo v1.162.0/go.mod h1:NJ1VlXmFMSnG1GEe2rWyDZVrhR69c3nHmL0s1cSSQ6M=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=

View File

@ -104,7 +104,7 @@ func NewCommand(l log.Logger, az *az.AZ, op *onepassword.OnePassword, cache cach
return nil
},
Execute: func(ctx context.Context, r *readline.Readline) error {
be, err := inst.cfg.Backend(r.Args().At(0))
backend, err := inst.cfg.Backend(r.Args().At(0))
if err != nil {
return err
}
@ -123,11 +123,11 @@ func NewCommand(l log.Logger, az *az.AZ, op *onepassword.OnePassword, cache cach
storageArgs = strings.Trim(strings.Trim(storageArgs, "\""), "'")
// Create a new resource group
inst.l.Info("creating resource group:", be.ResourceGroup)
inst.l.Info("creating resource group:", backend.ResourceGroup)
if err := shell.New(ctx, inst.l, "az", "group", "create").
Args("--resource-group", be.ResourceGroup).
Args("--subscription", be.Subscription).
Args("--location", be.Location).
Args("--resource-group", backend.ResourceGroup).
Args("--subscription", backend.Subscription).
Args("--location", backend.Location).
Args(strings.Split(groupArgs, " ")...).
Args(r.FlagSets().Default().Visited().Args()...).
Run(); err != nil {
@ -135,12 +135,12 @@ func NewCommand(l log.Logger, az *az.AZ, op *onepassword.OnePassword, cache cach
}
// Create a new resource group
inst.l.Info("creating storage account:", be.StorageAccount)
inst.l.Info("creating storage account:", backend.StorageAccount)
if err := shell.New(ctx, inst.l, "az", "storage", "account", "create").
Args("--name", be.StorageAccount).
Args("--resource-group", be.ResourceGroup).
Args("--subscription", be.Subscription).
Args("--location", be.Location).
Args("--name", backend.StorageAccount).
Args("--resource-group", backend.ResourceGroup).
Args("--subscription", backend.Subscription).
Args("--location", backend.Location).
Args(strings.Split(storageArgs, " ")...).
Args(r.FlagSets().Default().Visited().Args()...).
Run(); err != nil {
@ -148,23 +148,16 @@ func NewCommand(l log.Logger, az *az.AZ, op *onepassword.OnePassword, cache cach
}
// retrieve storage key
inst.l.Info("retrieving storage key")
sk, err := shell.New(ctx, inst.l, "az", "storage", "account", "keys", "list").
Args("--resource-group", be.ResourceGroup).
Args("--subscription", be.Subscription).
Args("--account-name", be.StorageAccount).
Args("-o", "tsv", "--query", "'[0].value'").
Output()
storageAccountKey, err := inst.getStorageAccountKey(ctx, backend)
if err != nil {
return err
}
sks := strings.ReplaceAll(strings.TrimSpace(string(sk)), "\n", "")
inst.l.Info("creating storage container:", be.Container)
inst.l.Info("creating storage container:", backend.Container)
return shell.New(ctx, inst.l, "az", "storage", "container", "create").
Args("--account-name", be.StorageAccount).
Args("--account-key", sks).
Args("--name", be.Container).
Args("--account-name", backend.StorageAccount).
Args("--account-key", storageAccountKey).
Args("--name", backend.Container).
Run()
},
},
@ -172,15 +165,21 @@ func NewCommand(l log.Logger, az *az.AZ, op *onepassword.OnePassword, cache cach
Name: "login",
Description: "Log into your object storage backend",
Execute: func(ctx context.Context, r *readline.Readline) error {
be, sks, err := inst.backendKey(ctx, r.Args().At(0))
backend, err := inst.cfg.Backend(r.Args().At(0))
if err != nil {
return err
}
return shell.New(ctx, inst.l, "pulumi", "login", fmt.Sprintf("azblob://%s", be.Container)).
Env("AZURE_STORAGE_ACCOUNT=" + be.StorageAccount).
Env("AZURE_STORAGE_KEY=" + sks).
Env("ARM_SUBSCRIPTION_ID=" + be.Subscription).
storageAccountKey, err := inst.getStorageAccountKey(ctx, backend)
if err != nil {
return err
}
return shell.New(ctx, inst.l, "pulumi", "login", fmt.Sprintf("azblob://%s", backend.Container)).
Env("AZURE_STORAGE_ACCOUNT=" + backend.StorageAccount).
Env("AZURE_STORAGE_KEY=" + storageAccountKey).
Env("ARM_SUBSCRIPTION_ID=" + backend.Subscription).
Debug().
Run()
},
},
@ -503,7 +502,12 @@ func (c *Command) executeStack(ctx context.Context, r *readline.Readline) error
proj := r.Args().At(2)
stack := r.Args().At(3)
be, storageAccountKey, err := c.backendKey(ctx, e)
be, err := c.cfg.Backend(r.Args().At(0))
if err != nil {
return err
}
storageAccountKey, err := c.getStorageAccountKey(ctx, be)
if err != nil {
return err
}
@ -551,12 +555,7 @@ func (c *Command) completeProjects(ctx context.Context, t tree.Root, r *readline
}).([]goprompt.Suggest)
}
func (c *Command) backendKey(ctx context.Context, env string) (Backend, string, error) {
be, err := c.cfg.Backend(env)
if err != nil {
return Backend{}, "", err
}
func (c *Command) getStorageAccountKey(ctx context.Context, be Backend) (string, error) {
// retrieve storage key
c.l.Info("retrieving storage key")
sk, err := shell.New(ctx, c.l, "az", "storage", "account", "keys", "list").
@ -566,10 +565,10 @@ func (c *Command) backendKey(ctx context.Context, env string) (Backend, string,
Args("-o", "tsv", "--query", "'[0].value'").
Output()
if err != nil {
return Backend{}, "", err
return "", err
}
return be, strings.ReplaceAll(strings.TrimSpace(string(sk)), "\n", ""), nil
return strings.ReplaceAll(strings.TrimSpace(string(sk)), "\n", ""), nil
}
func (c *Command) completeStacks(ctx context.Context, t tree.Root, r *readline.Readline) []goprompt.Suggest {