Skip to content

Commit

Permalink
synth: Warn when AppHost has changed since generation
Browse files Browse the repository at this point in the history
This changes adds a commented to files generated by `azd infra synth`
to include a hash of the manifest that was used to generate the
infrastructure. When we use these files later, we compare the hash
recorded in the file to the hash of the current manifest and issue a
warning.
  • Loading branch information
ellismg committed May 6, 2024
1 parent d5268ac commit 7e2ba6c
Show file tree
Hide file tree
Showing 29 changed files with 99 additions and 1 deletion.
3 changes: 3 additions & 0 deletions cli/azd/pkg/apphost/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ func ContainerAppManifestTemplateForProject(

tmplCtx := generator.containerAppTemplateContexts[projectName]
tmplCtx.AutoConfigureDataProtection = options.AutoConfigureDataProtection
tmplCtx.ManifestHash = manifest.Hash

err := genTemplates.ExecuteTemplate(&buf, "containerApp.tmpl.yaml", tmplCtx)
if err != nil {
Expand Down Expand Up @@ -237,6 +238,7 @@ func BicepTemplate(name string, manifest *Manifest, options AppHostOptions) (*me
}
type bicepContext struct {
genBicepTemplateContext
ManifestHash string
WithMetadataParameters []autoGenInput
MainToResourcesParams []genInput
}
Expand Down Expand Up @@ -267,6 +269,7 @@ func BicepTemplate(name string, manifest *Manifest, options AppHostOptions) (*me
}
}
context := bicepContext{
ManifestHash: manifest.Hash,
genBicepTemplateContext: generator.bicepContext,
WithMetadataParameters: parameters,
MainToResourcesParams: mapToResourceParams,
Expand Down
11 changes: 11 additions & 0 deletions cli/azd/pkg/apphost/generate_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package apphost

import (
"bytes"
"context"
_ "embed"
"encoding/json"
"fmt"
"io/fs"
"os"
Expand Down Expand Up @@ -64,6 +66,15 @@ func mockPublishManifest(mockCtx *mocks.MockContext, manifest []byte, files map[
})
}

func TestGenerateHash(t *testing.T) {
buf := bytes.Buffer{}
e := json.NewEncoder(&buf)
e.SetIndent("", "")
e.SetEscapeHTML(false)
e.Encode(json.RawMessage(aspireContainerManifest))

Check failure on line 74 in cli/azd/pkg/apphost/generate_test.go

View workflow job for this annotation

GitHub Actions / azd-lint (ubuntu-latest)

Error return value of `e.Encode` is not checked (errcheck)
t.Log(buf.String())
}

func TestAspireEscaping(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Skipping due to EOL issues on Windows with the baselines")
Expand Down
1 change: 1 addition & 0 deletions cli/azd/pkg/apphost/generate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ type genBicepTemplateContext struct {
}

type genContainerAppManifestTemplateContext struct {
ManifestHash string
Name string
Ingress *genContainerAppIngress
Env map[string]string
Expand Down
15 changes: 15 additions & 0 deletions cli/azd/pkg/apphost/manifest.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package apphost

import (
"bytes"
"context"
"crypto/sha256"
"encoding/json"
"fmt"
"os"
Expand All @@ -19,6 +21,8 @@ type Manifest struct {
Resources map[string]*Resource `json:"resources"`
// BicepFiles holds any bicep files generated by Aspire next to the manifest file.
BicepFiles *memfs.FS `json:"-"`
// Hash is a hash of the manifest file used to create this instance and is computed when we load it from disk.
Hash string `json:"-"`
}

type Resource struct {
Expand Down Expand Up @@ -169,6 +173,17 @@ func ManifestFromAppHost(
return nil, fmt.Errorf("unmarshalling manifest: %w", err)
}

// compute the manifest hash, which is just the SHA256 of the manifest JSON. We do a small amount of normalization here
// by running the value through the JSON encoder with indentation disabled, which removes any extraneous whitespace.
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetIndent("", "")
enc.SetEscapeHTML(false)
if err := enc.Encode(json.RawMessage(manifestData)); err != nil {
return nil, fmt.Errorf("encoding manifest: %w", err)
}
manifest.Hash = fmt.Sprintf("%x", sha256.Sum256(bytes.TrimSpace(buf.Bytes())))

// Make all paths absolute, to simplify logic for consumers.
// Note that since we created a temp dir, and `dotnet run --publisher` returns relative paths to the temp dir,
// the resulting path may be a symlinked path that isn't safe for Rel comparisons with the azd root directory.
Expand Down
2 changes: 2 additions & 0 deletions cli/azd/pkg/apphost/testdata/TestAspireArgsGeneration.snap
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Generated by azd infra synth from manifest hash v1:169ad3894b94e8a1966adfddf59d21f8359d846ca3451702c894d3c8a4e91950.

location: {{ .Env.AZURE_LOCATION }}
identity:
type: UserAssigned
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Generated by azd infra synth from manifest hash v1:f89b137a4893864099c87d37311eb5fd292286fa57c5aaa66d30275e7a835875.

location: {{ .Env.AZURE_LOCATION }}
identity:
type: UserAssigned
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Generated by azd infra synth from manifest hash v1:f89b137a4893864099c87d37311eb5fd292286fa57c5aaa66d30275e7a835875.

targetScope = 'subscription'

@minLength(1)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Generated by azd infra synth from manifest hash v1:f89b137a4893864099c87d37311eb5fd292286fa57c5aaa66d30275e7a835875.

@description('The location used for all deployed resources')
param location string = resourceGroup().location
@description('Id of the user or app to assign application roles')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Generated by azd infra synth from manifest hash v1:39beffe8d8a0d660eb13006948b7c1308db8494b81ce7140f58796d4a57f77e5.

targetScope = 'subscription'

@minLength(1)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Generated by azd infra synth from manifest hash v1:39beffe8d8a0d660eb13006948b7c1308db8494b81ce7140f58796d4a57f77e5.

@description('The location used for all deployed resources')
param location string = resourceGroup().location

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Generated by azd infra synth from manifest hash v1:81ea7a48a1a1e7d6c9bc7c906f3ee9f19ac63d85fa4dc49514ec81a70e643bf3.

targetScope = 'subscription'

@minLength(1)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Generated by azd infra synth from manifest hash v1:81ea7a48a1a1e7d6c9bc7c906f3ee9f19ac63d85fa4dc49514ec81a70e643bf3.

@description('The location used for all deployed resources')
param location string = resourceGroup().location
@description('Id of the user or app to assign application roles')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Generated by azd infra synth from manifest hash v1:81ea7a48a1a1e7d6c9bc7c906f3ee9f19ac63d85fa4dc49514ec81a70e643bf3.

location: {{ .Env.AZURE_LOCATION }}
identity:
type: UserAssigned
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Generated by azd infra synth from manifest hash v1:81ea7a48a1a1e7d6c9bc7c906f3ee9f19ac63d85fa4dc49514ec81a70e643bf3.

targetScope = 'subscription'

@minLength(1)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Generated by azd infra synth from manifest hash v1:81ea7a48a1a1e7d6c9bc7c906f3ee9f19ac63d85fa4dc49514ec81a70e643bf3.

location: {{ .Env.AZURE_LOCATION }}
identity:
type: UserAssigned
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Generated by azd infra synth from manifest hash v1:81ea7a48a1a1e7d6c9bc7c906f3ee9f19ac63d85fa4dc49514ec81a70e643bf3.

@description('The location used for all deployed resources')
param location string = resourceGroup().location

Expand Down
2 changes: 2 additions & 0 deletions cli/azd/pkg/apphost/testdata/TestAspireEscaping-api.snap
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Generated by azd infra synth from manifest hash v1:7d53e72d3270b4ff67d8d01b60f7df160dcb8b6471952115eefe2d47e7fe1593.

location: {{ .Env.AZURE_LOCATION }}
identity:
type: UserAssigned
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Generated by azd infra synth from manifest hash v1:28bf91b7e5f690b0ec4d3bebf091c118821c2141e6ebfb96e540d88365631d85.

targetScope = 'subscription'

@minLength(1)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Generated by azd infra synth from manifest hash v1:28bf91b7e5f690b0ec4d3bebf091c118821c2141e6ebfb96e540d88365631d85.

@description('The location used for all deployed resources')
param location string = resourceGroup().location

Expand Down
5 changes: 5 additions & 0 deletions cli/azd/pkg/project/service_models.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ type ServiceDeployResult struct {
TargetResourceId string `json:"targetResourceId"`
Kind ServiceTargetKind `json:"kind"`
Endpoints []string `json:"endpoints"`
Warnings []string `json:"warnings"`
Details interface{} `json:"details"`
}

Expand All @@ -116,6 +117,10 @@ func (spr *ServiceDeployResult) ToString(currentIndentation string) string {
}
}

for _, warning := range spr.Warnings {
builder.WriteString(fmt.Sprintf("%s- %s: %s\n", currentIndentation, output.WithWarningFormat("Warning"), warning))
}

return builder.String()
}

Expand Down
17 changes: 17 additions & 0 deletions cli/azd/pkg/project/service_target_dotnet_containerapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"text/template"
"time"
Expand Down Expand Up @@ -96,6 +97,8 @@ func (at *dotnetContainerAppTarget) Package(
)
}

var manifestV1HashRegex = regexp.MustCompile(`# Generated by azd infra synth from manifest hash v1:([0-9a-f]{64})\.`)

// Deploys service container images to ACR and provisions the container app service.
func (at *dotnetContainerAppTarget) Deploy(
ctx context.Context,
Expand Down Expand Up @@ -211,6 +214,12 @@ func (at *dotnetContainerAppTarget) Deploy(
manifest = generatedManifest
}

var manifestHash string

if match := manifestV1HashRegex.FindStringSubmatch(manifest); match != nil {
manifestHash = match[1]
}

fns := &containerAppTemplateManifestFuncs{
ctx: ctx,
manifest: serviceConfig.DotNetContainerApp.Manifest,
Expand Down Expand Up @@ -297,6 +306,13 @@ func (at *dotnetContainerAppTarget) Deploy(
return
}

var warnings []string

if manifestHash != "" && serviceConfig.DotNetContainerApp.Manifest.Hash != manifestHash {
warnings = append(warnings,
fmt.Sprintf("AppHost has changed since infrastructure was generated, changes may not be reflected."))

Check failure on line 313 in cli/azd/pkg/project/service_target_dotnet_containerapp.go

View workflow job for this annotation

GitHub Actions / azd-lint (ubuntu-latest)

S1039: unnecessary use of fmt.Sprintf (gosimple)
}

task.SetResult(&ServiceDeployResult{
Package: packageOutput,
TargetResourceId: azure.ContainerAppRID(
Expand All @@ -306,6 +322,7 @@ func (at *dotnetContainerAppTarget) Deploy(
),
Kind: ContainerAppTarget,
Endpoints: endpoints,
Warnings: warnings,
})
},
)
Expand Down
4 changes: 3 additions & 1 deletion cli/azd/resources/apphost/templates/containerApp.tmpl.yamlt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{{define "containerApp.tmpl.yaml" -}}
{{- if .AutoConfigureDataProtection -}}
# Generated by azd infra synth from manifest hash v1:{{ .ManifestHash }}.

{{if .AutoConfigureDataProtection -}}
api-version: 2024-02-02-preview
{{end -}}
location: {{ "{{ .Env.AZURE_LOCATION }}" }}
Expand Down
2 changes: 2 additions & 0 deletions cli/azd/resources/apphost/templates/main.bicept
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{{define "main.bicep" -}}
// Generated by azd infra synth from manifest hash v1:{{ .ManifestHash }}.

targetScope = 'subscription'

@minLength(1)
Expand Down
2 changes: 2 additions & 0 deletions cli/azd/resources/apphost/templates/resources.bicept
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{{define "resources.bicep" -}}
// Generated by azd infra synth from manifest hash v1:{{ .ManifestHash }}.

@description('The location used for all deployed resources')
param location string = resourceGroup().location
{{- if .RequiresPrincipalId }}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Generated by azd infra synth from manifest hash v1:8610bd8ed48a7cc33856f87cf5821352dc59a7f4afe7a8685468647c2af5e5da.

location: {{ .Env.AZURE_LOCATION }}
identity:
type: UserAssigned
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Generated by azd infra synth from manifest hash v1:8610bd8ed48a7cc33856f87cf5821352dc59a7f4afe7a8685468647c2af5e5da.

location: {{ .Env.AZURE_LOCATION }}
identity:
type: UserAssigned
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Generated by azd infra synth from manifest hash v1:8610bd8ed48a7cc33856f87cf5821352dc59a7f4afe7a8685468647c2af5e5da.

location: {{ .Env.AZURE_LOCATION }}
identity:
type: UserAssigned
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Generated by azd infra synth from manifest hash v1:8610bd8ed48a7cc33856f87cf5821352dc59a7f4afe7a8685468647c2af5e5da.

targetScope = 'subscription'

@minLength(1)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Generated by azd infra synth from manifest hash v1:8610bd8ed48a7cc33856f87cf5821352dc59a7f4afe7a8685468647c2af5e5da.

@description('The location used for all deployed resources')
param location string = resourceGroup().location
@description('Id of the user or app to assign application roles')
Expand Down

0 comments on commit 7e2ba6c

Please sign in to comment.