Co-authored-by: Victor Vazquez <vhvb1989@gmail.com>
This commit is contained in:
Wei Lim 2024-11-12 19:17:18 -08:00 коммит произвёл GitHub
Родитель d0937f92d3
Коммит 7b89277d41
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
11 изменённых файлов: 302 добавлений и 17 удалений

Просмотреть файл

@ -10,8 +10,15 @@ import (
"path/filepath"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v3"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices"
"github.com/azure/azure-dev/cli/azd/cmd/actions"
"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/pkg/account"
"github.com/azure/azure-dev/cli/azd/pkg/alpha"
"github.com/azure/azure-dev/cli/azd/pkg/azapi"
"github.com/azure/azure-dev/cli/azd/pkg/cloud"
"github.com/azure/azure-dev/cli/azd/pkg/contracts"
@ -23,17 +30,25 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/azure/azure-dev/cli/azd/pkg/output/ux"
"github.com/azure/azure-dev/cli/azd/pkg/project"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
type showFlags struct {
global *internal.GlobalCommandOptions
global *internal.GlobalCommandOptions
showSecrets bool
internal.EnvFlag
}
func (s *showFlags) Bind(local *pflag.FlagSet, global *internal.GlobalCommandOptions) {
s.EnvFlag.Bind(local, global)
local.BoolVar(
&s.showSecrets,
"show-secrets",
false,
"Unmask secrets in output.",
)
s.global = global
}
@ -63,6 +78,10 @@ type showAction struct {
infraResourceManager infra.ResourceManager
azdCtx *azdcontext.AzdContext
flags *showFlags
args []string
creds account.SubscriptionCredentialProvider
armClientOptions *arm.ClientOptions
featureManager *alpha.FeatureManager
lazyServiceManager *lazy.Lazy[project.ServiceManager]
lazyResourceManager *lazy.Lazy[project.ResourceManager]
portalUrlBase string
@ -77,8 +96,12 @@ func newShowAction(
infraResourceManager infra.ResourceManager,
projectConfig *project.ProjectConfig,
importManager *project.ImportManager,
featureManager *alpha.FeatureManager,
armClientOptions *arm.ClientOptions,
creds account.SubscriptionCredentialProvider,
azdCtx *azdcontext.AzdContext,
flags *showFlags,
args []string,
lazyServiceManager *lazy.Lazy[project.ServiceManager],
lazyResourceManager *lazy.Lazy[project.ResourceManager],
cloud *cloud.Cloud,
@ -92,7 +115,11 @@ func newShowAction(
resourceService: resourceService,
envManager: envManager,
infraResourceManager: infraResourceManager,
featureManager: featureManager,
armClientOptions: armClientOptions,
creds: creds,
azdCtx: azdCtx,
args: args,
flags: flags,
lazyServiceManager: lazyServiceManager,
lazyResourceManager: lazyResourceManager,
@ -101,7 +128,6 @@ func newShowAction(
}
func (s *showAction) Run(ctx context.Context) (*actions.ActionResult, error) {
s.console.ShowSpinner(ctx, "Gathering information about your app and its resources...", input.Step)
defer s.console.StopSpinner(ctx, "", input.Step)
@ -148,6 +174,7 @@ func (s *showAction) Run(ctx context.Context) (*actions.ActionResult, error) {
}
}
var subId, rgName string
if env, err := s.envManager.Get(ctx, environmentName); err != nil {
if errors.Is(err, environment.ErrNotFound) && s.flags.EnvironmentName != "" {
@ -167,6 +194,16 @@ func (s *showAction) Run(ctx context.Context) (*actions.ActionResult, error) {
envName := env.Name()
if s.featureManager.IsEnabled(composeFeature) && len(s.args) > 0 {
name := s.args[0]
err := s.showResource(ctx, name, env)
if err != nil {
return nil, err
}
return nil, nil
}
rgName, err = s.infraResourceManager.FindResourceGroupForEnvironment(ctx, subId, envName)
if err == nil {
for _, serviceConfig := range stableServices {
@ -235,6 +272,164 @@ func (s *showAction) Run(ctx context.Context) (*actions.ActionResult, error) {
return nil, nil
}
func (s *showAction) showResource(ctx context.Context, name string, env *environment.Environment) error {
id, err := infra.ResourceId(name, env)
if err != nil {
return fmt.Errorf("resolving '%s': %w", name, err)
}
subscriptionId := id.SubscriptionID
armOptions := s.armClientOptions
resourceOptions := showResourceOptions{
showSecrets: s.flags.showSecrets,
clientOpts: armOptions,
}
credential, err := s.creds.CredentialForSubscription(ctx, subscriptionId)
if err != nil {
return err
}
resType := id.ResourceType.Namespace + "/" + id.ResourceType.Type
var item ux.UxItem
switch {
case strings.EqualFold(resType, "Microsoft.App/containerApps"):
item, err = showContainerApp(ctx, credential, id, resourceOptions)
if err != nil {
return err
}
case strings.EqualFold(resType, "Microsoft.CognitiveServices/accounts/deployments"):
err = showModelDeployment(ctx, s.console, credential, id.Parent, resourceOptions)
if err != nil {
return err
}
default:
return fmt.Errorf("resource type '%s' is not currently supported in alpha", resType)
}
if item != nil {
s.console.MessageUxItem(ctx, item)
}
return nil
}
type showResourceOptions struct {
showSecrets bool
clientOpts *arm.ClientOptions
}
func showContainerApp(
ctx context.Context,
cred azcore.TokenCredential,
id *arm.ResourceID,
opts showResourceOptions) (*ux.ShowService, error) {
service := &ux.ShowService{
Name: id.Name,
Env: make(map[string]string),
}
client, err := armappcontainers.NewContainerAppsClient(id.SubscriptionID, cred, opts.clientOpts)
if err != nil {
return nil, fmt.Errorf("creating container-apps client: %w", err)
}
app, err := client.Get(ctx, id.ResourceGroupName, id.Name, nil)
if err != nil {
return nil, fmt.Errorf("getting container app: %w", err)
}
var secrets []*armappcontainers.ContainerAppSecret // secret name to value translations
if opts.showSecrets {
secretsRes, err := client.ListSecrets(ctx, id.ResourceGroupName, id.Name, nil)
if err != nil {
return nil, fmt.Errorf("listing secrets: %w", err)
}
secrets = secretsRes.Value
}
if len(app.Properties.Template.Containers) == 0 {
return service, nil
}
service.IngresUrl = fmt.Sprintf("https://%s", *app.Properties.Configuration.Ingress.Fqdn)
var container *armappcontainers.Container
if len(app.Properties.Template.Containers) == 1 {
container = app.Properties.Template.Containers[0]
} else {
for _, c := range app.Properties.Template.Containers {
if c.Name != nil && (strings.EqualFold(*c.Name, id.Name) || strings.EqualFold(*c.Name, "main")) {
container = c
break
}
}
if container == nil {
return nil, fmt.Errorf(
"container app %s has more than one container, and no containers match the name 'main' or '%s'",
id.Name,
id.Name)
}
}
envVar := container.Env
for _, env := range envVar {
if env.Name == nil {
continue
}
key := *env.Name
val := env.Value
if env.SecretRef != nil {
val = to.Ptr("*******")
// dereference the secret ref
for _, secret := range secrets {
if *env.SecretRef == *secret.Name {
val = secret.Value
break
}
}
}
service.Env[key] = *val
}
return service, nil
}
func showModelDeployment(
ctx context.Context,
console input.Console,
cred azcore.TokenCredential,
id *arm.ResourceID,
opts showResourceOptions) error {
client, err := armcognitiveservices.NewAccountsClient(id.SubscriptionID, cred, opts.clientOpts)
if err != nil {
return fmt.Errorf("creating accounts client: %w", err)
}
account, err := client.Get(ctx, id.ResourceGroupName, id.Name, nil)
if err != nil {
return fmt.Errorf("getting account: %w", err)
}
if account.Properties.Endpoint != nil {
console.Message(ctx, color.HiMagentaString("%s (Azure AI Services Model Deployment)", id.Name))
console.Message(ctx, " Endpoint:")
console.Message(ctx, color.HiBlueString(fmt.Sprintf(" AZURE_OPENAI_ENDPOINT=%s", *account.Properties.Endpoint)))
console.Message(ctx, " Access:")
console.Message(ctx, " Keyless (Microsoft Entra ID)")
//nolint:lll
console.Message(ctx, output.WithGrayFormat(" Hint: To access locally, use DefaultAzureCredential. To learn more, visit https://learn.microsoft.com/en-us/azure/ai-services/openai/supported-languages"))
console.Message(ctx, "")
}
return nil
}
func (s *showAction) serviceEndpoint(
ctx context.Context, subId string, serviceConfig *project.ServiceConfig, env *environment.Environment) string {
resourceManager, err := s.lazyResourceManager.GetValue()

Просмотреть файл

@ -6,6 +6,7 @@ Usage
Flags
-e, --environment string : The name of the environment to use.
--show-secrets : Unmask secrets in output.
Global Flags
-C, --cwd string : Sets the current working directory.

Просмотреть файл

@ -24,7 +24,7 @@ type resourceMeta struct {
UseEnvVars []string
}
func metadata(r *project.ResourceConfig) resourceMeta {
func Metadata(r *project.ResourceConfig) resourceMeta {
res := resourceMeta{}
// These are currently duplicated, static values maintained separately from the backend generation files
@ -60,7 +60,7 @@ func metadata(r *project.ResourceConfig) resourceMeta {
"MONGODB_URL",
}
case project.ResourceTypeOpenAiModel:
res.AzureResourceType = "Microsoft.CognitiveAccounts/accounts/deployments"
res.AzureResourceType = "Microsoft.CognitiveServices/accounts/deployments"
res.UseEnvVars = []string{
"AZURE_OPENAI_ENDPOINT",
}
@ -100,7 +100,7 @@ func (a *AddAction) previewProvision(
w := tabwriter.NewWriter(&previewWriter, 0, 0, 5, ' ', 0)
fmt.Fprintln(w, "b Name\tResource type")
meta := metadata(resourceToAdd)
meta := Metadata(resourceToAdd)
fmt.Fprintf(w, "+ %s\t%s\n", resourceToAdd.Name, meta.AzureResourceType)
w.Flush()
@ -111,7 +111,7 @@ func (a *AddAction) previewProvision(
if res, ok := prjConfig.Resources[use]; ok {
fmt.Fprintf(w, " %s -> %s\n", resourceToAdd.Name, output.WithBold("%s", use))
meta := metadata(res)
meta := Metadata(res)
for _, envVar := range meta.UseEnvVars {
fmt.Fprintf(w, "g + %s\n", envVar)
}
@ -120,7 +120,7 @@ func (a *AddAction) previewProvision(
}
}
} else {
meta := metadata(resourceToAdd)
meta := Metadata(resourceToAdd)
for _, usedBy := range usedBy {
fmt.Fprintf(w, " %s -> %s\n", usedBy, output.WithBold("%s", resourceToAdd.Name))

Просмотреть файл

@ -217,18 +217,19 @@ func (e *Environment) SetLocation(location string) {
e.DotenvSet(LocationEnvVarName, location)
}
func normalize(key string) string {
return strings.ReplaceAll(strings.ToUpper(key), "-", "_")
// Key returns the environment key name for the given name.
func Key(name string) string {
return strings.ReplaceAll(strings.ToUpper(name), "-", "_")
}
// GetServiceProperty is shorthand for Getenv(SERVICE_$SERVICE_NAME_$PROPERTY_NAME)
func (e *Environment) GetServiceProperty(serviceName string, propertyName string) string {
return e.Getenv(fmt.Sprintf("SERVICE_%s_%s", normalize(serviceName), propertyName))
return e.Getenv(fmt.Sprintf("SERVICE_%s_%s", Key(serviceName), propertyName))
}
// Sets the value of a service-namespaced property in the environment.
func (e *Environment) SetServiceProperty(serviceName string, propertyName string, value string) {
e.DotenvSet(fmt.Sprintf("SERVICE_%s_%s", normalize(serviceName), propertyName), value)
e.DotenvSet(fmt.Sprintf("SERVICE_%s_%s", Key(serviceName), propertyName), value)
}
// Creates a slice of key value pairs, based on the entries in the `.env` file like `KEY=VALUE` that

38
cli/azd/pkg/infra/util.go Normal file
Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package infra
import (
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/azure/azure-dev/cli/azd/pkg/environment"
)
// ResourceId returns the resource ID for the corresponding name.
//
// If the name is a resource ID string, it is immediately parsed without translation.
func ResourceId(name string, env *environment.Environment) (resId *arm.ResourceID, err error) {
resId, err = arm.ParseResourceID(name)
if err == nil {
return resId, nil
}
key := fmt.Sprintf("AZURE_RESOURCE_%s_ID", environment.Key(name))
resourceId, ok := env.LookupEnv(key)
if !ok {
return resId, fmt.Errorf("%s is not set as an output variable", key)
}
if resourceId == "" {
return resId, fmt.Errorf("%s is empty", key)
}
resId, err = arm.ParseResourceID(resourceId)
if err != nil {
return resId, fmt.Errorf("parsing %s: %w", key, err)
}
return resId, nil
}

Просмотреть файл

@ -5,6 +5,7 @@ package ux
import (
"fmt"
"slices"
"strings"
"github.com/azure/azure-dev/cli/azd/pkg/output"
@ -14,6 +15,38 @@ import (
type ShowService struct {
Name string
IngresUrl string
Env map[string]string
}
func (s *ShowService) ToString(currentIndentation string) string {
return fmt.Sprintf(
"%s\n"+
" Endpoint: %s\n"+
" Environment variables:\n"+
color.HiBlueString(formatEnv(" ", s.Env)),
color.HiMagentaString("%s (Container App)", s.Name),
output.WithLinkFormat(s.IngresUrl))
}
func (s *ShowService) MarshalJSON() ([]byte, error) {
return nil, fmt.Errorf("not implemented")
}
func formatEnv(prefix string, values map[string]string) string {
environ := make([]string, 0, len(values))
for k, v := range values {
environ = append(environ, k+"="+v)
}
slices.Sort(environ)
var sb strings.Builder
for _, env := range environ {
sb.WriteString(prefix)
sb.WriteString(env)
sb.WriteString("\n")
}
return sb.String()
}
type ShowEnvironment struct {

Просмотреть файл

@ -270,6 +270,8 @@ func mapHostUses(
svcSpec.Frontend.Backends = append(svcSpec.Frontend.Backends,
scaffold.ServiceReference{Name: use})
backendMapping[use] = res.Name // record the backend -> frontend mapping
case ResourceTypeOpenAiModel:
svcSpec.AIModels = append(svcSpec.AIModels, scaffold.AIModelReference{Name: use})
}
}

Просмотреть файл

@ -52,11 +52,19 @@ module resources 'resources.bicep' = {
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = resources.outputs.AZURE_CONTAINER_REGISTRY_ENDPOINT
output AZURE_KEY_VAULT_ENDPOINT string = resources.outputs.AZURE_KEY_VAULT_ENDPOINT
output AZURE_KEY_VAULT_NAME string = resources.outputs.AZURE_KEY_VAULT_NAME
{{- range .Services}}
output AZURE_RESOURCE_{{alphaSnakeUpper .Name}}_ID string = resources.outputs.AZURE_RESOURCE_{{alphaSnakeUpper .Name}}_ID
{{- end}}
{{- end}}
{{- if .AIModels}}
{{- range .AIModels}}
output AZURE_RESOURCE_{{alphaSnakeUpper .Name}}_ID string = resources.outputs.AZURE_RESOURCE_{{alphaSnakeUpper .Name}}_ID
{{- end}}
{{- end}}
{{- if .DbRedis}}
output AZURE_CACHE_REDIS_ID string = resources.outputs.AZURE_CACHE_REDIS_ID
output AZURE_RESOURCE_REDIS_ID string = resources.outputs.AZURE_RESOURCE_REDIS_ID
{{- end}}
{{- if .DbPostgres}}
output AZURE_POSTGRES_FLEXIBLE_SERVER_ID string = resources.outputs.AZURE_POSTGRES_FLEXIBLE_SERVER_ID
output AZURE_RESOURCE_{{alphaSnakeUpper .DbPostgres.DatabaseName}}_ID string = resources.outputs.AZURE_RESOURCE_{{alphaSnakeUpper .DbPostgres.DatabaseName}}_ID
{{- end}}
{{ end}}

Просмотреть файл

@ -450,14 +450,19 @@ module keyVault 'br/public:avm/res/key-vault/vault:0.6.1' = {
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerRegistry.outputs.loginServer
output AZURE_KEY_VAULT_ENDPOINT string = keyVault.outputs.uri
output AZURE_KEY_VAULT_NAME string = keyVault.outputs.name
{{- range .Services}}
output AZURE_RESOURCE_{{alphaSnakeUpper .Name}}_ID string = {{bicepName .Name}}.outputs.resourceId
{{- end}}
{{- end}}
{{- if .AIModels}}
output AZURE_COGNITIVE_ACCOUNT_ID string = account.outputs.resourceId
{{- range .AIModels}}
output AZURE_RESOURCE_{{alphaSnakeUpper .Name}}_ID string = '${account.outputs.resourceId}/deployments/{{.Name}}'
{{- end}}
{{- end}}
{{- if .DbRedis}}
output AZURE_CACHE_REDIS_ID string = redis.outputs.resourceId
output AZURE_RESOURCE_REDIS_ID string = redis.outputs.resourceId
{{- end}}
{{- if .DbPostgres}}
output AZURE_POSTGRES_FLEXIBLE_SERVER_ID string = postgreServer.outputs.resourceId
output AZURE_RESOURCE_{{alphaSnakeUpper .DbPostgres.DatabaseName}}_ID string = '${postgreServer.outputs.resourceId}/databases/{{.DbPostgres.DatabaseName}}'
{{- end}}
{{ end}}

2
go.mod
Просмотреть файл

@ -13,7 +13,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v2 v2.3.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization v1.0.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.1.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.4.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.6.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry v0.6.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cosmos/armcosmos/v2 v2.6.0

2
go.sum
Просмотреть файл

@ -79,6 +79,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthoriza
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.1.1/go.mod h1:WqyxV5S0VtXD2+2d6oPqOvyhGubCvzLCKSAKgQ004Uk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.4.1 h1:ynIxbR7wH5nBEJzprbeBFVBtoYTYcQbN39vM7eNS3Xc=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.4.1/go.mod h1:nUhnLNlOtAVpn/PRwJKIf3ulXLvdMiWlGk8nufEUaKc=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.6.0 h1:TiYjDq0LCNgtee1teMayYT5FjHmlunWUpthVANUXYPM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.6.0/go.mod h1:yErdzWZBzjNJCnbC1DcUcSVhjTgllT4PyOenFSeXSJI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry v0.6.0 h1:Z5/bDxQL2Zc9t6ZDwdRU60bpLHZvoKOeuaM7XVbf2z0=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry v0.6.0/go.mod h1:0FPu3oDRGPvuX1H8TtHJ5XGA0KrXLunomcixR+PQGGA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.2.0 h1:3L+gX5ssCABAToH0VQ64/oNz7rr+ShW+2sB+sonzIlY=