Skip to content

Commit 20887c4

Browse files
authored
update to support docker-in-mem generation (#5916)
* update to support docker-in-mem generation * cspell fix * raname variable and replace deprecated function
1 parent 2516bc0 commit 20887c4

File tree

4 files changed

+82
-15
lines changed

4 files changed

+82
-15
lines changed

cli/azd/pkg/apphost/generate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ func BicepTemplate(name string, manifest *Manifest, options AppHostOptions) (*me
307307
// use the filesystem coming from the manifest
308308
// the in-memory filesystem from the manifest is guaranteed to be initialized and contains all the bicep files
309309
// referenced by the Aspire manifest.
310-
fs := manifest.BicepFiles
310+
fs := manifest.Files
311311

312312
// bicepContext merges the bicepContext with the inputs from the manifest to execute the main.bicep template
313313
// this allows the template to access the auto-gen inputs from the generator

cli/azd/pkg/apphost/manifest.go

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ const (
3636
type Manifest struct {
3737
Schema string `json:"$schema"`
3838
Resources map[string]*Resource `json:"resources"`
39-
// BicepFiles holds any bicep files generated by Aspire next to the manifest file.
40-
BicepFiles *memfs.FS `json:"-"`
39+
// Files holds any files generated by Aspire next to the manifest file.
40+
Files *memfs.FS `json:"-"`
4141
// publish mode intention from the manifest
4242
publishMode apphostPublishMode `json:"-"`
4343
}
@@ -299,12 +299,12 @@ func ManifestFromAppHost(
299299
manifestDir = filepath.Dir(appHostProject)
300300
}
301301

302-
manifest.BicepFiles = memfs.New()
302+
manifest.Files = memfs.New()
303303

304304
for resourceName, res := range manifest.Resources {
305305
if res.Path != nil {
306306
if res.Type == "azure.bicep.v0" || res.Type == "azure.bicep.v1" {
307-
e := manifest.BicepFiles.MkdirAll(resourceName, osutil.PermissionDirectory)
307+
e := manifest.Files.MkdirAll(resourceName, osutil.PermissionDirectory)
308308
if e != nil {
309309
return nil, e
310310
}
@@ -318,7 +318,7 @@ func ManifestFromAppHost(
318318
}
319319
}
320320
*res.Path = filepath.Join(resourceName, filepath.Base(*res.Path))
321-
e = manifest.BicepFiles.WriteFile(*res.Path, content, osutil.PermissionFile)
321+
e = manifest.Files.WriteFile(*res.Path, content, osutil.PermissionFile)
322322
if e != nil {
323323
return nil, e
324324
}
@@ -337,7 +337,7 @@ func ManifestFromAppHost(
337337
"unexpected deployment type %q. Supported types: [azure.bicep.v0, azure.bicep.v1]", res.Deployment.Type)
338338
}
339339
// use a folder with the name of the resource
340-
e := manifest.BicepFiles.MkdirAll(resourceName, osutil.PermissionDirectory)
340+
e := manifest.Files.MkdirAll(resourceName, osutil.PermissionDirectory)
341341
if e != nil {
342342
return nil, e
343343
}
@@ -346,7 +346,7 @@ func ManifestFromAppHost(
346346
return nil, fmt.Errorf("reading bicep file from deployment property: %w", e)
347347
}
348348
*res.Deployment.Path = filepath.Join(resourceName, filepath.Base(*res.Deployment.Path))
349-
e = manifest.BicepFiles.WriteFile(*res.Deployment.Path, content, osutil.PermissionFile)
349+
e = manifest.Files.WriteFile(*res.Deployment.Path, content, osutil.PermissionFile)
350350
if e != nil {
351351
return nil, e
352352
}
@@ -372,6 +372,21 @@ func ManifestFromAppHost(
372372
if !filepath.IsAbs(res.Build.Context) {
373373
res.Build.Context = filepath.Join(manifestDir, res.Build.Context)
374374
}
375+
// make sure the dockerfile exists
376+
content, e := os.ReadFile(res.Build.Dockerfile)
377+
if e != nil {
378+
return nil, fmt.Errorf("expecting dockerfile content at %q: %w", res.Build.Dockerfile, e)
379+
}
380+
// copy the dockerfile (same strategy as bicep files)
381+
e = manifest.Files.MkdirAll(resourceName, osutil.PermissionDirectory)
382+
if e != nil {
383+
return nil, e
384+
}
385+
res.Build.Dockerfile = filepath.Join(resourceName, filepath.Base(res.Build.Dockerfile))
386+
e = manifest.Files.WriteFile(res.Build.Dockerfile, content, osutil.PermissionFile)
387+
if e != nil {
388+
return nil, e
389+
}
375390
for _, secret := range res.Build.Secrets {
376391
if secret.Source != nil && !filepath.IsAbs(*secret.Source) {
377392
*secret.Source = filepath.Join(manifestDir, *secret.Source)

cli/azd/pkg/project/dotnet_importer.go

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,26 @@ func (ai *DotNetImporter) Services(
323323

324324
if bContainer.Build != nil {
325325
defaultLanguage = ServiceLanguageDocker
326-
relPath, err := filepath.Rel(p.Path, filepath.Dir(bContainer.Build.Dockerfile))
326+
// dockerfiles are copied to the infra folder like bicep files to ensure
327+
// provision and deploy works after infra gen.
328+
bContainer.Build.Dockerfile = filepath.Join(
329+
svcConfig.Project.Path, svcConfig.Project.Infra.Path, name, filepath.Base(bContainer.Build.Dockerfile))
330+
331+
// If the dockerfile is not in disk, it could have been manually deleted (after infra gen) or
332+
// infra gen was never run. In any case, use the in-memory generated dockerfile to build the container.
333+
var inMemDockerfile []byte
334+
if _, err := os.Stat(bContainer.Build.Dockerfile); errors.Is(err, os.ErrNotExist) {
335+
// write file in the temp folder
336+
fileName := filepath.Base(bContainer.Build.Dockerfile)
337+
// read content from memory fs in the manifest
338+
data, err := fs.ReadFile(manifest.Files, filepath.Join(name, fileName))
339+
if err != nil {
340+
return nil, fmt.Errorf("reading dockerfile for service %s: %w", name, err)
341+
}
342+
inMemDockerfile = data
343+
}
344+
345+
relPath, err := filepath.Rel(p.Path, filepath.Dir(bContainer.Build.Context))
327346
if err != nil {
328347
return nil, err
329348
}
@@ -339,11 +358,12 @@ func (ai *DotNetImporter) Services(
339358
}
340359

341360
dOptions = DockerProjectOptions{
342-
Path: bContainer.Build.Dockerfile,
343-
Context: bContainer.Build.Context,
344-
BuildArgs: mapToExpandableStringSlice(bArgs, "="),
345-
BuildSecrets: bArgsArray,
346-
BuildEnv: reqEnv,
361+
Path: bContainer.Build.Dockerfile,
362+
Context: bContainer.Build.Context,
363+
BuildArgs: mapToExpandableStringSlice(bArgs, "="),
364+
BuildSecrets: bArgsArray,
365+
BuildEnv: reqEnv,
366+
InMemDockerfile: inMemDockerfile,
347367
}
348368
}
349369

cli/azd/pkg/project/framework_service_docker.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ type DockerProjectOptions struct {
4949
// Aspire would pass the secret keys, which are env vars that azd will set just to run docker build.
5050
BuildSecrets []string `yaml:"-" json:"-"`
5151
BuildEnv []string `yaml:"-" json:"-"`
52+
//InMemDockerfile allow projects to specify a dockerfile contents directly instead of a path on disk.
53+
// This is not supported from azure.yaml.
54+
// This is used by projects like Aspire that can generate a dockerfile on the fly and don't want to write it to disk.
55+
// When this is set, whatever value in Path is ignored and the dockerfile contents in this property is used instead.
56+
InMemDockerfile []byte `yaml:"-" json:"-"`
5257
}
5358

5459
type dockerBuildResult struct {
@@ -310,10 +315,36 @@ func (p *dockerProject) Build(
310315
MaxLineCount: 8,
311316
Title: "Docker Output",
312317
})
318+
319+
dockerFilePath := dockerOptions.Path
320+
if dockerOptions.InMemDockerfile != nil {
321+
// when using an in-memory dockerfile, we write it to a temp file and use that path for the build
322+
tempDir, err := os.MkdirTemp("", "dockerfile-for-"+serviceConfig.Name)
323+
if err != nil {
324+
return nil, fmt.Errorf("creating temp dir for dockerfile for service %s: %w", serviceConfig.Name, err)
325+
}
326+
// use the name of the original dockerfile path
327+
dockerfilePath = filepath.Join(tempDir, filepath.Base(dockerFilePath))
328+
err = os.WriteFile(dockerfilePath, dockerOptions.InMemDockerfile, osutil.PermissionFileOwnerOnly)
329+
if err != nil {
330+
return nil, fmt.Errorf("writing dockerfile for service %s: %w", serviceConfig.Name, err)
331+
}
332+
dockerFilePath = dockerfilePath
333+
334+
log.Println("using in-memory dockerfile for build", dockerfilePath)
335+
336+
// ensure we clean up the temp dockerfile after the build
337+
defer func() {
338+
if err := os.RemoveAll(tempDir); err != nil {
339+
log.Printf("removing temp dockerfile dir %s: %v", tempDir, err)
340+
}
341+
}()
342+
}
343+
313344
imageId, err := p.docker.Build(
314345
ctx,
315346
serviceConfig.Path(),
316-
dockerOptions.Path,
347+
dockerFilePath,
317348
dockerOptions.Platform,
318349
dockerOptions.Target,
319350
dockerOptions.Context,
@@ -329,6 +360,7 @@ func (p *dockerProject) Build(
329360
}
330361

331362
log.Printf("built image %s for %s", imageId, serviceConfig.Name)
363+
332364
return &ServiceBuildResult{
333365
Restore: restoreOutput,
334366
BuildOutputPath: imageId,

0 commit comments

Comments
 (0)