Skip to content

Commit 1c23678

Browse files
authored
Fix azd up --template so it can initialize a new project (#1394)
In #1296 we changed our logic such that we would inject `AzdContext` as a dependency of our actions, and expect our IoC container to wire things up. This had the side effect of breaking `azd up --template` to initialize (and then provision and deploy) a new project. The break comes from the fact that the IoC container will call `NewAzdContext` as part of building the `deploy` and `infraCreate` actions, which need to be created because they are dependencies of the `up` composite action. However, `NewAzdContext` should not be called before the project has actually been created (which will happen when the `up` composite action calls the `init` action), because it looks for an existing project and if it doesn't find one it fails. To work around this issue - I've made the infra create and deploy actions explicitly call `NewAzdContext` so the calls can happen at the right time. A regression test has been added (it's a little hacky because we don't actually care about running the `infra create` or `deploy` parts of `up` in this test, we just want to ensure that we correctly initialized via a template. Fixes #1329
1 parent 7f992b0 commit 1c23678

4 files changed

Lines changed: 74 additions & 24 deletions

File tree

cli/azd/CHANGELOG.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
# Release History
22

3-
## 0.5.0-beta.3 (Unreleased)
4-
5-
### Features Added
6-
7-
### Breaking Changes
3+
## 0.5.0-beta.3 (2023-01-13)
84

95
### Bugs Fixed
106

11-
### Other Changes
7+
- [[#1394]](https://github.com/Azure/azure-dev/pull/1394) Bug when running azd up with a template.
128

139
## 0.5.0-beta.2 (2023-01-12)
1410

cli/azd/cmd/deploy.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ After the deployment is complete, the endpoint is printed. To start the service,
8585
type deployAction struct {
8686
flags *deployFlags
8787
azCli azcli.AzCli
88-
azdCtx *azdcontext.AzdContext
8988
formatter output.Formatter
9089
writer io.Writer
9190
console input.Console
@@ -96,15 +95,13 @@ func newDeployAction(
9695
flags *deployFlags,
9796
azCli azcli.AzCli,
9897
commandRunner exec.CommandRunner,
99-
azdCtx *azdcontext.AzdContext,
10098
console input.Console,
10199
formatter output.Formatter,
102100
writer io.Writer,
103101
) actions.Action {
104102
return &deployAction{
105103
flags: flags,
106104
azCli: azCli,
107-
azdCtx: azdCtx,
108105
formatter: formatter,
109106
writer: writer,
110107
console: console,
@@ -118,12 +115,21 @@ type DeploymentResult struct {
118115
}
119116

120117
func (d *deployAction) Run(ctx context.Context) (*actions.ActionResult, error) {
121-
env, err := loadOrInitEnvironment(ctx, &d.flags.environmentName, d.azdCtx, d.console, d.azCli)
118+
// We call `NewAzdContext` here instead of having the value injected because we want to delay the
119+
// walk for the context until this command has started to execute (for example, in the case of `up`,
120+
// the context is not created until the init action actually runs, which is after the infraCreateAction
121+
// object is created.
122+
azdCtx, err := azdcontext.NewAzdContext()
123+
if err != nil {
124+
return nil, err
125+
}
126+
127+
env, err := loadOrInitEnvironment(ctx, &d.flags.environmentName, azdCtx, d.console, d.azCli)
122128
if err != nil {
123129
return nil, fmt.Errorf("loading environment: %w", err)
124130
}
125131

126-
projConfig, err := project.LoadProjectConfig(d.azdCtx.ProjectPath())
132+
projConfig, err := project.LoadProjectConfig(azdCtx.ProjectPath())
127133
if err != nil {
128134
return nil, fmt.Errorf("loading project: %w", err)
129135
}
@@ -169,7 +175,7 @@ func (d *deployAction) Run(ctx context.Context) (*actions.ActionResult, error) {
169175

170176
stepMessage := fmt.Sprintf("Deploying service %s", svc.Config.Name)
171177
d.console.ShowSpinner(ctx, stepMessage, input.Step)
172-
result, progress := svc.Deploy(ctx, d.azdCtx)
178+
result, progress := svc.Deploy(ctx, azdCtx)
173179

174180
// Report any progress to logs only. Changes for the console are managed by the console object.
175181
// This routine is required to drain all the string messages sent by the `progress`.

cli/azd/cmd/infra_create.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ func newInfraCreateCmd() *cobra.Command {
6565
type infraCreateAction struct {
6666
flags *infraCreateFlags
6767
azCli azcli.AzCli
68-
azdCtx *azdcontext.AzdContext
6968
formatter output.Formatter
7069
writer io.Writer
7170
console input.Console
@@ -75,7 +74,6 @@ type infraCreateAction struct {
7574
func newInfraCreateAction(
7675
flags *infraCreateFlags,
7776
azCli azcli.AzCli,
78-
azdCtx *azdcontext.AzdContext,
7977
console input.Console,
8078
formatter output.Formatter,
8179
writer io.Writer,
@@ -84,7 +82,6 @@ func newInfraCreateAction(
8482
return &infraCreateAction{
8583
flags: flags,
8684
azCli: azCli,
87-
azdCtx: azdCtx,
8885
formatter: formatter,
8986
writer: writer,
9087
console: console,
@@ -93,18 +90,27 @@ func newInfraCreateAction(
9390
}
9491

9592
func (i *infraCreateAction) Run(ctx context.Context) (*actions.ActionResult, error) {
93+
// We call `NewAzdContext` here instead of having the value injected because we want to delay the
94+
// walk for the context until this command has started to execute (for example, in the case of `up`,
95+
// the context is not created until the init action actually runs, which is after the infraCreateAction
96+
// object is created.
97+
azdCtx, err := azdcontext.NewAzdContext()
98+
if err != nil {
99+
return nil, err
100+
}
101+
96102
// Command title
97103
i.console.MessageUxItem(ctx, &ux.MessageTitle{
98104
Title: "Provisioning Azure resources (azd provision)",
99105
TitleNote: "Provisioning Azure resources can take some time"},
100106
)
101107

102-
env, err := loadOrInitEnvironment(ctx, &i.flags.environmentName, i.azdCtx, i.console, i.azCli)
108+
env, err := loadOrInitEnvironment(ctx, &i.flags.environmentName, azdCtx, i.console, i.azCli)
103109
if err != nil {
104110
return nil, fmt.Errorf("loading environment: %w", err)
105111
}
106112

107-
prj, err := project.LoadProjectConfig(i.azdCtx.ProjectPath())
113+
prj, err := project.LoadProjectConfig(azdCtx.ProjectPath())
108114
if err != nil {
109115
return nil, fmt.Errorf("loading project: %w", err)
110116
}

cli/azd/test/functional/cli_test.go

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,42 @@ func Test_CLI_Init_CanUseTemplate(t *testing.T) {
108108
require.FileExists(t, filepath.Join(dir, "README.md"))
109109
}
110110

111+
// Test_CLI_Up_CanUseTemplateWithoutExistingProject ensures that you can run `azd up --template <some-template>` in an
112+
// empty directory and the project will be initialize as expected.
113+
func Test_CLI_Up_CanUseTemplateWithoutExistingProject(t *testing.T) {
114+
// running this test in parallel is ok as it uses a t.TempDir()
115+
t.Parallel()
116+
ctx, cancel := newTestContext(t)
117+
defer cancel()
118+
119+
dir := tempDirWithDiagnostics(t)
120+
121+
cli := azdcli.NewCLI(t)
122+
cli.WorkingDirectory = dir
123+
cli.Env = append(os.Environ(), "AZURE_LOCATION=eastus2")
124+
125+
// Since we provide a bogus Azure Subscription ID, we expect that this overall command will fail (the provision step of
126+
// up will fail). That's fine - we only care about validating that we were allowed to run `azd up --template` in an
127+
// empty directory and that it brings down the template as expected.
128+
res, _ := cli.RunCommandWithStdIn(
129+
ctx,
130+
"TESTENV\n\nOther (enter manually)\nMY_SUB_ID\n",
131+
"up",
132+
"--template",
133+
"cosmos-dotnet-core-todo-app",
134+
)
135+
136+
require.Contains(t, res.Stdout, "Initializing a new project")
137+
138+
// While `init` uses git behind the scenes to pull a template, we don't want to bring the history over or initialize a
139+
// git
140+
// repository.
141+
require.NoDirExists(t, filepath.Join(dir, ".git"))
142+
143+
// Ensure the project was initialized from the template by checking that a file from the template is present.
144+
require.FileExists(t, filepath.Join(dir, "README.md"))
145+
}
146+
111147
func Test_CLI_InfraCreateAndDelete(t *testing.T) {
112148
// running this test in parallel is ok as it uses a t.TempDir()
113149
t.Parallel()
@@ -287,26 +323,28 @@ func Test_CLI_ProjectIsNeeded(t *testing.T) {
287323
cli.WorkingDirectory = dir
288324

289325
tests := []struct {
290-
command string
291-
args []string
326+
command string
327+
args []string
328+
errorToStdOut bool
292329
}{
293-
{command: "deploy"},
330+
{command: "deploy", errorToStdOut: true},
294331
{command: "down"},
295332
{command: "env get-values"},
296333
{command: "env list"},
297334
{command: "env new", args: []string{"testEnvironmentName"}},
298335
{command: "env refresh"},
299336
{command: "env select", args: []string{"testEnvironmentName"}},
300337
{command: "env set", args: []string{"testKey", "testValue"}},
301-
{command: "infra create"},
338+
{command: "infra create", errorToStdOut: true},
302339
{command: "infra delete"},
303340
{command: "monitor"},
304341
{command: "pipeline config"},
305-
{command: "provision"},
342+
{command: "provision", errorToStdOut: true},
306343
{command: "restore"},
307344
}
308345

309-
for _, test := range tests {
346+
for _, tt := range tests {
347+
test := tt
310348
args := []string{"--cwd", dir}
311349
args = append(args, strings.Split(test.command, " ")...)
312350
if len(test.args) > 0 {
@@ -316,7 +354,11 @@ func Test_CLI_ProjectIsNeeded(t *testing.T) {
316354
t.Run(test.command, func(t *testing.T) {
317355
result, err := cli.RunCommand(ctx, args...)
318356
assert.Error(t, err)
319-
assert.Contains(t, result.Stderr, azdcontext.ErrNoProject.Error())
357+
if test.errorToStdOut {
358+
assert.Contains(t, result.Stdout, azdcontext.ErrNoProject.Error())
359+
} else {
360+
assert.Contains(t, result.Stderr, azdcontext.ErrNoProject.Error())
361+
}
320362
})
321363
}
322364
}

0 commit comments

Comments
 (0)