Skip to content

Commit

Permalink
add show for resources (#4534)
Browse files Browse the repository at this point in the history
Co-authored-by: Victor Vazquez <[email protected]>
  • Loading branch information
weikanglim and vhvb1989 authored Nov 13, 2024
1 parent d0937f9 commit 7b89277
Show file tree
Hide file tree
Showing 11 changed files with 302 additions and 17 deletions.
199 changes: 197 additions & 2 deletions cli/azd/cmd/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}

Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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)

Expand Down Expand Up @@ -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 != "" {
Expand All @@ -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 {
Expand Down Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions cli/azd/cmd/testdata/TestUsage-azd-show.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 5 additions & 5 deletions cli/azd/internal/cmd/add/add_preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
}
Expand Down Expand Up @@ -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()
Expand All @@ -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)
}
Expand All @@ -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))
Expand Down
9 changes: 5 additions & 4 deletions cli/azd/pkg/environment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 38 additions & 0 deletions cli/azd/pkg/infra/util.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 7b89277

Please sign in to comment.