Skip to content

Commit

Permalink
Add azd script run
Browse files Browse the repository at this point in the history
  • Loading branch information
jongio committed Jul 16, 2024
1 parent bace9a3 commit b633cdf
Show file tree
Hide file tree
Showing 7 changed files with 297 additions and 6 deletions.
1 change: 1 addition & 0 deletions cli/azd/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ func NewRootCmd(
templatesActions(root)
authActions(root)
hooksActions(root)
scriptsActions(root)

root.Add("version", &actions.ActionDescriptorOptions{
Command: &cobra.Command{
Expand Down
229 changes: 229 additions & 0 deletions cli/azd/cmd/scripts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package cmd

import (
"context"
"fmt"

"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/environment"
"github.com/azure/azure-dev/cli/azd/pkg/exec"
"github.com/azure/azure-dev/cli/azd/pkg/ext"
"github.com/azure/azure-dev/cli/azd/pkg/input"
"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/azure/azure-dev/cli/azd/pkg/tools"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

func scriptsActions(root *actions.ActionDescriptor) *actions.ActionDescriptor {
group := root.Add("script", &actions.ActionDescriptorOptions{
Command: &cobra.Command{
Use: "script",
Short: fmt.Sprintf("Develop, test and run scripts for an application. %s", output.WithWarningFormat("(Beta)")),
},
GroupingOptions: actions.CommandGroupOptions{
RootLevelHelp: actions.CmdGroupConfig,
},
})

group.Add("run", &actions.ActionDescriptorOptions{
Command: newScriptsRunCmd(),
FlagsResolver: newScriptsRunFlags,
ActionResolver: newScriptsRunAction,
})

return group
}

func newScriptsRunFlags(cmd *cobra.Command, global *internal.GlobalCommandOptions) *scriptsRunFlags {
flags := &scriptsRunFlags{}
flags.Bind(cmd.Flags(), global)

return flags
}

func newScriptsRunCmd() *cobra.Command {
return &cobra.Command{
Use: "run <name>",
Short: "Runs the specified script for the project",
Args: cobra.ExactArgs(1),
}
}

type scriptsRunFlags struct {
internal.EnvFlag
global *internal.GlobalCommandOptions
platform string
}

func (f *scriptsRunFlags) Bind(local *pflag.FlagSet, global *internal.GlobalCommandOptions) {
f.EnvFlag.Bind(local, global)
f.global = global

local.StringVar(&f.platform, "platform", "", "Forces scripts to run for the specified platform.")
}

type scriptsRunAction struct {
projectConfig *project.ProjectConfig
env *environment.Environment
envManager environment.Manager
commandRunner exec.CommandRunner
console input.Console
flags *scriptsRunFlags
args []string
}

func newScriptsRunAction(
projectConfig *project.ProjectConfig,
env *environment.Environment,
envManager environment.Manager,
commandRunner exec.CommandRunner,
console input.Console,
flags *scriptsRunFlags,
args []string,
) actions.Action {
return &scriptsRunAction{
projectConfig: projectConfig,
env: env,
envManager: envManager,
commandRunner: commandRunner,
console: console,
flags: flags,
args: args,
}
}

const noScriptFoundMessage = " (No script found)"

func (sra *scriptsRunAction) Run(ctx context.Context) (*actions.ActionResult, error) {
scriptName := sra.args[0]

// Command title
sra.console.MessageUxItem(ctx, &ux.MessageTitle{
Title: "Running scripts (azd scripts run)",
TitleNote: fmt.Sprintf(
"Finding and executing %s scripts for environment %s",
output.WithHighLightFormat(scriptName),
output.WithHighLightFormat(sra.env.Name()),
),
})

// Project level scripts
if err := sra.processScripts(
ctx,
sra.projectConfig.Path,
scriptName,
fmt.Sprintf("Running %s command script for project", scriptName),
fmt.Sprintf("Project: %s Script Output", scriptName),
sra.projectConfig.Scripts, // Use projectConfig.Scripts directly
); err != nil {
return nil, err
}

return &actions.ActionResult{
Message: &actions.ResultMessage{
Header: "Your scripts have been run successfully",
},
}, nil
}

func (sra *scriptsRunAction) processScripts(
ctx context.Context,
cwd string,
scriptName string,
spinnerMessage string,
previewMessage string,
scripts map[string]*ext.HookConfig,
) error {
sra.console.ShowSpinner(ctx, spinnerMessage, input.Step)

script, ok := scripts[scriptName]
if !ok {
sra.console.StopSpinner(ctx, spinnerMessage+noScriptFoundMessage, input.StepWarning)
return nil
}

if err := sra.prepareScript(scriptName, script); err != nil {
return err
}

err := sra.execScript(ctx, previewMessage, cwd, script)
if err != nil {
sra.console.StopSpinner(ctx, spinnerMessage, input.StepFailed)
return fmt.Errorf("failed running script %s, %w", scriptName, err)
}

sra.console.StopSpinner(ctx, spinnerMessage, input.StepDone)

return nil
}

func (sra *scriptsRunAction) execScript(
ctx context.Context,
previewMessage string,
cwd string,
script *ext.HookConfig,
) error {
scripts := map[string]*ext.HookConfig{
script.Name: script,
}

scriptsManager := ext.NewHooksManager(cwd)
scriptsRunner := ext.NewHooksRunner(scriptsManager, sra.commandRunner, sra.envManager, sra.console, cwd, scripts, sra.env)

previewer := sra.console.ShowPreviewer(ctx, &input.ShowPreviewerOptions{
Prefix: " ",
Title: previewMessage,
MaxLineCount: 8,
})
defer sra.console.StopPreviewer(ctx, false)

runOptions := &tools.ExecOptions{StdOut: previewer}
err := scriptsRunner.RunHooks(ctx, ext.HookTypeNone, runOptions, script.Name)
if err != nil {
return err
}

return nil
}

func (sra *scriptsRunAction) prepareScript(name string, script *ext.HookConfig) error {
if sra.flags.platform != "" {
platformType := ext.HookPlatformType(sra.flags.platform)
switch platformType {
case ext.HookPlatformWindows:
if script.Windows == nil {
return fmt.Errorf("script is not configured for Windows")
} else {
*script = *script.Windows
}
case ext.HookPlatformPosix:
if script.Posix == nil {
return fmt.Errorf("script is not configured for Posix")
} else {
*script = *script.Posix
}
default:
return fmt.Errorf("platform %s is not valid. Supported values are windows & posix", sra.flags.platform)
}
}

script.Name = name
script.Interactive = true

sra.configureScriptFlags(script.Windows)
sra.configureScriptFlags(script.Posix)

return nil
}

func (sra *scriptsRunAction) configureScriptFlags(script *ext.HookConfig) {
if script == nil {
return
}

script.Interactive = true
}
20 changes: 20 additions & 0 deletions cli/azd/cmd/testdata/TestUsage-azd-script-run.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

Runs the specified script for the project

Usage
azd script run <name> [flags]

Flags
--docs : Opens the documentation for azd script run in your web browser.
-e, --environment string : The name of the environment to use.
-h, --help : Gets help for run.
--platform string : Forces scripts to run for the specified platform.

Global Flags
-C, --cwd string : Sets the current working directory.
--debug : Enables debugging and diagnostics logging.
--no-prompt : Accepts the default value instead of prompting, or it fails if there is no default.

Find a bug? Want to let us know how we're doing? Fill out this brief survey: https://aka.ms/azure-dev/hats.


23 changes: 23 additions & 0 deletions cli/azd/cmd/testdata/TestUsage-azd-script.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

Develop, test and run scripts for an application. (Beta)

Usage
azd script [command]

Available Commands
run : Runs the specified script for the project

Flags
--docs : Opens the documentation for azd script in your web browser.
-h, --help : Gets help for script.

Global Flags
-C, --cwd string : Sets the current working directory.
--debug : Enables debugging and diagnostics logging.
--no-prompt : Accepts the default value instead of prompting, or it fails if there is no default.

Use azd script [command] --help to view examples and more information about a specific command.

Find a bug? Want to let us know how we're doing? Fill out this brief survey: https://aka.ms/azure-dev/hats.


1 change: 1 addition & 0 deletions cli/azd/cmd/testdata/TestUsage-azd.snap
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Commands
hooks : Develop, test and run hooks for an application. (Beta)
init : Initialize a new application.
restore : Restores the application's dependencies. (Beta)
script : Develop, test and run scripts for an application. (Beta)
template : Find and view template details. (Beta)

Manage Azure resources and app deployments
Expand Down
1 change: 1 addition & 0 deletions cli/azd/pkg/project/project_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type ProjectConfig struct {
Infra provisioning.Options `yaml:"infra,omitempty"`
Pipeline PipelineOptions `yaml:"pipeline,omitempty"`
Hooks map[string]*ext.HookConfig `yaml:"hooks,omitempty"`
Scripts map[string]*ext.HookConfig `yaml:"scripts,omitempty"`
State *state.Config `yaml:"state,omitempty"`
Platform *platform.Config `yaml:"platform,omitempty"`
Workflows workflow.WorkflowMap `yaml:"workflows,omitempty"`
Expand Down
28 changes: 22 additions & 6 deletions schemas/v1.0/azure.yaml.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"type": "string",
"title": "Path to the service source code directory"
},
"image": {
"image": {
"type": "string",
"title": "Optional. The source image to be used for the container image instead of building from source.",
"description": "If omitted, container image will be built from source specified in the 'project' property. Setting both 'project' and 'image' is invalid."
Expand Down Expand Up @@ -170,7 +170,7 @@
}
},
"allOf": [
{
{
"if": {
"properties": {
"host": {
Expand Down Expand Up @@ -344,6 +344,14 @@
}
}
},
"scripts": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/hook"
},
"title": "Scripts for various events",
"description": "Users can define multiple hooks for different events. Each key represents a script name, and the value should follow the hook definition."
},
"hooks": {
"type": "object",
"title": "Command level hooks",
Expand Down Expand Up @@ -559,7 +567,13 @@
"description": "Optional. Provides additional configuration for deploying to sovereign clouds such as Azure Government. The default cloud is AzureCloud.",
"additionalProperties": false,
"properties": {
"name": { "enum": [ "AzureCloud", "AzureChinaCloud", "AzureUSGovernment" ] }
"name": {
"enum": [
"AzureCloud",
"AzureChinaCloud",
"AzureUSGovernment"
]
}
}
}
},
Expand Down Expand Up @@ -733,7 +747,7 @@
"description": "When set will be appended to the root of your ingress resource path."
}
}
},
},
"helm": {
"type": "object",
"title": "Optional. The helm configuration",
Expand Down Expand Up @@ -992,15 +1006,17 @@
},
"aiDeploymentConfig": {
"allOf": [
{ "$ref": "#/definitions/aiComponentConfig" },
{
"$ref": "#/definitions/aiComponentConfig"
},
{
"type": "object",
"properties": {
"environment": {
"type": "object",
"title": "A map of key/value pairs to set as environment variables for the deployment.",
"description": "Optional. Values support OS & AZD environment variable substitution.",
"additionalProperties":{
"additionalProperties": {
"type": "string"
}
}
Expand Down

0 comments on commit b633cdf

Please sign in to comment.