dotnet: Support `dotnet publish` to produce container image (#4573)

The `dotnet` tool has native support for producing (and pushing) a
container image. This does not require a local docker daemon and in
general "does the right thing" and is the prefered way for .NET
customers to produce container
images. https://learn.microsoft.com/dotnet/core/docker/publish-as-container
gives a good overview of how this support works and we've been using
it to build and push container images for Aspire apps.

This change updates things such that we can use this support when
building a non Aspire based .NET app. If a `Dockerfile` exists, we
respect it, but otherwise instead of trying to use Oryx to produce a
container image, we will use `dotnet publish`.

In addition, we now use 8080 as the default port of a `dotnet` based
application (with no Dockerfile) as that's the default port used by
the standard ASP.NET base image.

A new recorded test was added - it builds on top of the existing
`containerapp` sample, and the test aranges to remove the `Dockerfile`
file from the template before running `up`.

Fixes #2632
Fixes #4583
This commit is contained in:
Matt Ellis 2024-11-22 14:09:57 -08:00 коммит произвёл GitHub
Родитель 67098119bb
Коммит 6cd82ae9ff
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
18 изменённых файлов: 2516 добавлений и 33 удалений

Просмотреть файл

@ -238,7 +238,7 @@ func addServiceAsResource(
if _, err := os.Stat(filepath.Join(svc.RelativePath, "Dockerfile")); errors.Is(err, os.ErrNotExist) {
// default builder always specifies port 80
props.Port = 80
if svc.Language == project.ServiceLanguageJava {
if svc.Language == project.ServiceLanguageJava || svc.Language.IsDotNet() {
props.Port = 8080
}
}

Просмотреть файл

@ -46,7 +46,7 @@ func TestInitializer_prjConfigFromDetect(t *testing.T) {
Type: project.ResourceTypeHostContainerApp,
Name: "dotnet",
Props: project.ContainerAppProps{
Port: 80,
Port: 8080,
},
},
},

Просмотреть файл

@ -212,7 +212,7 @@ func PromptPort(
name string,
svc appdetect.Project) (int, error) {
if svc.Docker == nil || svc.Docker.Path == "" { // using default builder from azd
if svc.Language == appdetect.Java {
if svc.Language == appdetect.Java || svc.Language == appdetect.DotNet {
return 8080, nil
}
return 80, nil

Просмотреть файл

@ -35,7 +35,7 @@ func TestInitializer_infraSpecFromDetect(t *testing.T) {
Services: []scaffold.ServiceSpec{
{
Name: "dotnet",
Port: 80,
Port: 8080,
Backend: &scaffold.Backend{},
},
},

Просмотреть файл

@ -22,6 +22,7 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/tools"
"github.com/azure/azure-dev/cli/azd/pkg/tools/azcli"
"github.com/azure/azure-dev/cli/azd/pkg/tools/docker"
"github.com/azure/azure-dev/cli/azd/pkg/tools/dotnet"
"github.com/benbjohnson/clock"
"github.com/sethvargo/go-retry"
)
@ -32,6 +33,7 @@ type ContainerHelper struct {
remoteBuildManager *containerregistry.RemoteBuildManager
containerRegistryService azcli.ContainerRegistryService
docker *docker.Cli
dotNetCli *dotnet.Cli
clock clock.Clock
console input.Console
cloud *cloud.Cloud
@ -44,6 +46,7 @@ func NewContainerHelper(
containerRegistryService azcli.ContainerRegistryService,
remoteBuildManager *containerregistry.RemoteBuildManager,
docker *docker.Cli,
dotNetCli *dotnet.Cli,
console input.Console,
cloud *cloud.Cloud,
) *ContainerHelper {
@ -53,6 +56,7 @@ func NewContainerHelper(
remoteBuildManager: remoteBuildManager,
containerRegistryService: containerRegistryService,
docker: docker,
dotNetCli: dotNetCli,
clock: clock,
console: console,
cloud: cloud,
@ -194,6 +198,10 @@ func (ch *ContainerHelper) RequiredExternalTools(ctx context.Context, serviceCon
return []tools.ExternalTool{}
}
if useDotnetPublishForDockerBuild(serviceConfig) {
return []tools.ExternalTool{ch.dotNetCli}
}
return []tools.ExternalTool{ch.docker}
}
@ -270,6 +278,8 @@ func (ch *ContainerHelper) Deploy(
if serviceConfig.Docker.RemoteBuild {
remoteImage, err = ch.runRemoteBuild(ctx, serviceConfig, targetResource, progress)
} else if useDotnetPublishForDockerBuild(serviceConfig) {
remoteImage, err = ch.runDotnetPublish(ctx, serviceConfig, targetResource, progress)
} else {
remoteImage, err = ch.runLocalBuild(ctx, serviceConfig, packageOutput, progress)
}
@ -382,7 +392,8 @@ func (ch *ContainerHelper) runLocalBuild(
return remoteImage, nil
}
// runLocalBuild builds the image using a remote azure container registry and tags it. It returns the full remote image name.
// runRemoteBuild builds the image using a remote azure container registry and tags it.
// It returns the full remote image name.
func (ch *ContainerHelper) runRemoteBuild(
ctx context.Context,
serviceConfig *ServiceConfig,
@ -473,6 +484,41 @@ func (ch *ContainerHelper) runRemoteBuild(
return imageName, nil
}
// runDotnetPublish builds and publishes the container image using `dotnet publish`. It returns the full remote image name.
func (ch *ContainerHelper) runDotnetPublish(
ctx context.Context,
serviceConfig *ServiceConfig,
target *environment.TargetResource,
progress *async.Progress[ServiceProgress],
) (string, error) {
progress.SetProgress(NewServiceProgress("Logging into registry"))
dockerCreds, err := ch.Credentials(ctx, serviceConfig, target)
if err != nil {
return "", fmt.Errorf("logging in to registry: %w", err)
}
progress.SetProgress(NewServiceProgress("Publishing container image"))
imageName := fmt.Sprintf("%s:%s",
ch.DefaultImageName(serviceConfig),
ch.DefaultImageTag())
_, err = ch.dotNetCli.PublishContainer(
ctx,
serviceConfig.Path(),
"Release",
imageName,
dockerCreds.LoginServer,
dockerCreds.Username,
dockerCreds.Password)
if err != nil {
return "", fmt.Errorf("publishing container: %w", err)
}
return fmt.Sprintf("%s/%s", dockerCreds.LoginServer, imageName), nil
}
type dockerDeployResult struct {
RemoteImageTag string
}

Просмотреть файл

@ -18,6 +18,7 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/osutil"
"github.com/azure/azure-dev/cli/azd/pkg/tools/azcli"
"github.com/azure/azure-dev/cli/azd/pkg/tools/docker"
"github.com/azure/azure-dev/cli/azd/pkg/tools/dotnet"
"github.com/azure/azure-dev/cli/azd/test/mocks"
"github.com/azure/azure-dev/cli/azd/test/mocks/mockenv"
"github.com/benbjohnson/clock"
@ -62,7 +63,7 @@ func Test_ContainerHelper_LocalImageTag(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
env := environment.NewWithValues("dev", map[string]string{})
containerHelper := NewContainerHelper(env, nil, clock.NewMock(), nil, nil, nil, nil, cloud.AzurePublic())
containerHelper := NewContainerHelper(env, nil, clock.NewMock(), nil, nil, nil, nil, nil, cloud.AzurePublic())
serviceConfig.Docker = tt.dockerConfig
tag, err := containerHelper.LocalImageTag(*mockContext.Context, serviceConfig)
@ -111,7 +112,7 @@ func Test_ContainerHelper_RemoteImageTag(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
env := environment.NewWithValues("dev", map[string]string{})
containerHelper := NewContainerHelper(env, nil, clock.NewMock(), nil, nil, nil, nil, cloud.AzurePublic())
containerHelper := NewContainerHelper(env, nil, clock.NewMock(), nil, nil, nil, nil, nil, cloud.AzurePublic())
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -138,7 +139,7 @@ func Test_ContainerHelper_Resolve_RegistryName(t *testing.T) {
environment.ContainerRegistryEndpointEnvVarName: "contoso.azurecr.io",
})
envManager := &mockenv.MockEnvManager{}
containerHelper := NewContainerHelper(env, envManager, clock.NewMock(), nil, nil, nil, nil, cloud.AzurePublic())
containerHelper := NewContainerHelper(env, envManager, clock.NewMock(), nil, nil, nil, nil, nil, cloud.AzurePublic())
serviceConfig := createTestServiceConfig("./src/api", ContainerAppTarget, ServiceLanguageTypeScript)
registryName, err := containerHelper.RegistryName(*mockContext.Context, serviceConfig)
@ -150,7 +151,7 @@ func Test_ContainerHelper_Resolve_RegistryName(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
env := environment.NewWithValues("dev", map[string]string{})
envManager := &mockenv.MockEnvManager{}
containerHelper := NewContainerHelper(env, envManager, clock.NewMock(), nil, nil, nil, nil, cloud.AzurePublic())
containerHelper := NewContainerHelper(env, envManager, clock.NewMock(), nil, nil, nil, nil, nil, cloud.AzurePublic())
serviceConfig := createTestServiceConfig("./src/api", ContainerAppTarget, ServiceLanguageTypeScript)
serviceConfig.Docker.Registry = osutil.NewExpandableString("contoso.azurecr.io")
registryName, err := containerHelper.RegistryName(*mockContext.Context, serviceConfig)
@ -164,7 +165,7 @@ func Test_ContainerHelper_Resolve_RegistryName(t *testing.T) {
env := environment.NewWithValues("dev", map[string]string{})
env.DotenvSet("MY_CUSTOM_REGISTRY", "custom.azurecr.io")
envManager := &mockenv.MockEnvManager{}
containerHelper := NewContainerHelper(env, envManager, clock.NewMock(), nil, nil, nil, nil, cloud.AzurePublic())
containerHelper := NewContainerHelper(env, envManager, clock.NewMock(), nil, nil, nil, nil, nil, cloud.AzurePublic())
serviceConfig := createTestServiceConfig("./src/api", ContainerAppTarget, ServiceLanguageTypeScript)
serviceConfig.Docker.Registry = osutil.NewExpandableString("${MY_CUSTOM_REGISTRY}")
registryName, err := containerHelper.RegistryName(*mockContext.Context, serviceConfig)
@ -177,7 +178,7 @@ func Test_ContainerHelper_Resolve_RegistryName(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
env := environment.NewWithValues("dev", map[string]string{})
envManager := &mockenv.MockEnvManager{}
containerHelper := NewContainerHelper(env, envManager, clock.NewMock(), nil, nil, nil, nil, cloud.AzurePublic())
containerHelper := NewContainerHelper(env, envManager, clock.NewMock(), nil, nil, nil, nil, nil, cloud.AzurePublic())
serviceConfig := createTestServiceConfig("./src/api", ContainerAppTarget, ServiceLanguageTypeScript)
registryName, err := containerHelper.RegistryName(*mockContext.Context, serviceConfig)
@ -334,6 +335,7 @@ func Test_ContainerHelper_Deploy(t *testing.T) {
mockResults := setupDockerMocks(mockContext)
env := environment.NewWithValues("dev", map[string]string{})
dockerCli := docker.NewCli(mockContext.CommandRunner)
dotnetCli := dotnet.NewCli(mockContext.CommandRunner)
envManager := &mockenv.MockEnvManager{}
envManager.On("Save", *mockContext.Context, env).Return(nil)
@ -347,6 +349,7 @@ func Test_ContainerHelper_Deploy(t *testing.T) {
mockContainerRegistryService,
nil,
dockerCli,
dotnetCli,
mockContext.Console,
cloud.AzurePublic(),
)
@ -411,7 +414,7 @@ func Test_ContainerHelper_Deploy(t *testing.T) {
func Test_ContainerHelper_ConfiguredImage(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
env := environment.NewWithValues("dev", map[string]string{})
containerHelper := NewContainerHelper(env, nil, clock.NewMock(), nil, nil, nil, nil, cloud.AzurePublic())
containerHelper := NewContainerHelper(env, nil, clock.NewMock(), nil, nil, nil, nil, nil, cloud.AzurePublic())
tests := []struct {
name string
@ -597,7 +600,7 @@ func Test_ContainerHelper_Credential_Retry(t *testing.T) {
defaultCredentialsRetryDelay = 1 * time.Millisecond
containerHelper := NewContainerHelper(
env, envManager, clock.NewMock(), mockContainerService, nil, nil, nil, cloud.AzurePublic())
env, envManager, clock.NewMock(), mockContainerService, nil, nil, nil, nil, cloud.AzurePublic())
serviceConfig := createTestServiceConfig("path", ContainerAppTarget, ServiceLanguageDotNet)
serviceConfig.Docker.Registry = osutil.NewExpandableString("contoso.azurecr.io")

Просмотреть файл

@ -121,3 +121,7 @@ func validatePackageOutput(packagePath string) error {
return nil
}
func (slk ServiceLanguageKind) IsDotNet() bool {
return slk == ServiceLanguageDotNet || slk == ServiceLanguageCsharp || slk == ServiceLanguageFsharp
}

Просмотреть файл

@ -14,6 +14,7 @@ import (
"path/filepath"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/internal/appdetect"
"github.com/azure/azure-dev/cli/azd/internal/tracing"
@ -163,12 +164,8 @@ func (p *dockerProject) Requirements() FrameworkRequirements {
}
// Gets the required external tools for the project
func (p *dockerProject) RequiredExternalTools(_ context.Context, sc *ServiceConfig) []tools.ExternalTool {
if sc.Docker.RemoteBuild {
return []tools.ExternalTool{}
}
return []tools.ExternalTool{p.docker}
func (p *dockerProject) RequiredExternalTools(ctx context.Context, sc *ServiceConfig) []tools.ExternalTool {
return p.containerHelper.RequiredExternalTools(ctx, sc)
}
// Initializes the docker project
@ -199,7 +196,7 @@ func (p *dockerProject) Build(
restoreOutput *ServiceRestoreResult,
progress *async.Progress[ServiceProgress],
) (*ServiceBuildResult, error) {
if serviceConfig.Docker.RemoteBuild {
if serviceConfig.Docker.RemoteBuild || useDotnetPublishForDockerBuild(serviceConfig) {
return &ServiceBuildResult{Restore: restoreOutput}, nil
}
@ -275,12 +272,12 @@ func (p *dockerProject) Build(
strings.ToLower(serviceConfig.Name),
)
path := dockerOptions.Path
if !filepath.IsAbs(path) {
path = filepath.Join(serviceConfig.Path(), path)
dockerfilePath := dockerOptions.Path
if !filepath.IsAbs(dockerfilePath) {
dockerfilePath = filepath.Join(serviceConfig.Path(), dockerfilePath)
}
_, err = os.Stat(path)
_, err = os.Stat(dockerfilePath)
if errors.Is(err, os.ErrNotExist) && serviceConfig.Docker.Path == "" {
// Build the container from source when:
// 1. No Dockerfile path is specified, and
@ -341,13 +338,43 @@ func (p *dockerProject) Build(
}, nil
}
func useDotnetPublishForDockerBuild(serviceConfig *ServiceConfig) bool {
if serviceConfig.useDotNetPublishForDockerBuild != nil {
return *serviceConfig.useDotNetPublishForDockerBuild
}
serviceConfig.useDotNetPublishForDockerBuild = to.Ptr(false)
if serviceConfig.Language.IsDotNet() {
projectPath := serviceConfig.Path()
dockerOptions := getDockerOptionsWithDefaults(serviceConfig.Docker)
dockerfilePath := dockerOptions.Path
if !filepath.IsAbs(dockerfilePath) {
s, err := os.Stat(projectPath)
if err == nil && s.IsDir() {
dockerfilePath = filepath.Join(projectPath, dockerfilePath)
} else {
dockerfilePath = filepath.Join(filepath.Dir(projectPath), dockerfilePath)
}
}
if _, err := os.Stat(dockerfilePath); errors.Is(err, os.ErrNotExist) {
serviceConfig.useDotNetPublishForDockerBuild = to.Ptr(true)
}
}
return *serviceConfig.useDotNetPublishForDockerBuild
}
func (p *dockerProject) Package(
ctx context.Context,
serviceConfig *ServiceConfig,
buildOutput *ServiceBuildResult,
progress *async.Progress[ServiceProgress],
) (*ServicePackageResult, error) {
if serviceConfig.Docker.RemoteBuild {
if serviceConfig.Docker.RemoteBuild || useDotnetPublishForDockerBuild(serviceConfig) {
return &ServicePackageResult{Build: buildOutput}, nil
}

Просмотреть файл

@ -19,6 +19,7 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/exec"
"github.com/azure/azure-dev/cli/azd/pkg/osutil"
"github.com/azure/azure-dev/cli/azd/pkg/tools/docker"
"github.com/azure/azure-dev/cli/azd/pkg/tools/dotnet"
"github.com/azure/azure-dev/cli/azd/pkg/tools/npm"
"github.com/azure/azure-dev/cli/azd/test/mocks"
"github.com/azure/azure-dev/cli/azd/test/mocks/mockarmresources"
@ -100,6 +101,7 @@ services:
npmCli := npm.NewCli(mockContext.CommandRunner)
docker := docker.NewCli(mockContext.CommandRunner)
dotnetCli := dotnet.NewCli(mockContext.CommandRunner)
internalFramework := NewNpmProject(npmCli, env)
progressMessages := []string{}
@ -107,7 +109,8 @@ services:
framework := NewDockerProject(
env,
docker,
NewContainerHelper(env, envManager, clock.NewMock(), nil, nil, docker, mockContext.Console, cloud.AzurePublic()),
NewContainerHelper(
env, envManager, clock.NewMock(), nil, nil, docker, dotnetCli, mockContext.Console, cloud.AzurePublic()),
mockinput.NewMockConsole(),
mockContext.AlphaFeaturesManager,
mockContext.CommandRunner)
@ -194,6 +197,7 @@ services:
npmCli := npm.NewCli(mockContext.CommandRunner)
docker := docker.NewCli(mockContext.CommandRunner)
dotnetCli := dotnet.NewCli(mockContext.CommandRunner)
projectConfig, err := Parse(*mockContext.Context, testProj)
require.NoError(t, err)
@ -211,7 +215,8 @@ services:
framework := NewDockerProject(
env,
docker,
NewContainerHelper(env, envManager, clock.NewMock(), nil, nil, docker, mockContext.Console, cloud.AzurePublic()),
NewContainerHelper(
env, envManager, clock.NewMock(), nil, nil, docker, dotnetCli, mockContext.Console, cloud.AzurePublic()),
mockinput.NewMockConsole(),
mockContext.AlphaFeaturesManager,
mockContext.CommandRunner)
@ -444,6 +449,7 @@ func Test_DockerProject_Build(t *testing.T) {
}
dockerCli := docker.NewCli(mockContext.CommandRunner)
dotnetCli := dotnet.NewCli(mockContext.CommandRunner)
serviceConfig := createTestServiceConfig(tt.project, ContainerAppTarget, tt.language)
serviceConfig.Project.Path = temp
serviceConfig.Docker = tt.dockerOptions
@ -466,7 +472,8 @@ func Test_DockerProject_Build(t *testing.T) {
env,
dockerCli,
NewContainerHelper(
env, envManager, clock.NewMock(), nil, nil, dockerCli, mockContext.Console, cloud.AzurePublic()),
env, envManager, clock.NewMock(), nil, nil, dockerCli, dotnetCli, mockContext.Console,
cloud.AzurePublic()),
mockinput.NewMockConsole(),
mockContext.AlphaFeaturesManager,
mockContext.CommandRunner)
@ -582,13 +589,15 @@ func Test_DockerProject_Package(t *testing.T) {
env := environment.NewWithValues("test", map[string]string{})
dockerCli := docker.NewCli(mockContext.CommandRunner)
dotnetCli := dotnet.NewCli(mockContext.CommandRunner)
serviceConfig := createTestServiceConfig("./src/api", ContainerAppTarget, ServiceLanguageTypeScript)
dockerProject := NewDockerProject(
env,
dockerCli,
NewContainerHelper(
env, envManager, clock.NewMock(), nil, nil, dockerCli, mockContext.Console, cloud.AzurePublic()),
env, envManager, clock.NewMock(), nil, nil, dockerCli, dotnetCli, mockContext.Console,
cloud.AzurePublic()),
mockinput.NewMockConsole(),
mockContext.AlphaFeaturesManager,
mockContext.CommandRunner)

Просмотреть файл

@ -45,6 +45,9 @@ type ServiceConfig struct {
DotNetContainerApp *DotNetContainerAppOptions `yaml:"-,omitempty"`
// Custom configuration for the service target
Config map[string]any `yaml:"config,omitempty"`
// Computed lazily by useDotnetPublishForDockerBuild and cached. This is true when the project
// is a dotnet project and there is not an explicit Dockerfile in the project directory.
useDotNetPublishForDockerBuild *bool
*ext.EventDispatcher[ServiceLifecycleEventArgs] `yaml:"-"`
}

Просмотреть файл

@ -28,6 +28,7 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/osutil"
"github.com/azure/azure-dev/cli/azd/pkg/tools/azcli"
"github.com/azure/azure-dev/cli/azd/pkg/tools/docker"
"github.com/azure/azure-dev/cli/azd/pkg/tools/dotnet"
"github.com/azure/azure-dev/cli/azd/pkg/tools/kubectl"
"github.com/azure/azure-dev/cli/azd/test/mocks"
"github.com/azure/azure-dev/cli/azd/test/mocks/mockaccount"
@ -811,6 +812,7 @@ func createAksServiceTarget(
helmCli := helm.NewCli(mockContext.CommandRunner)
kustomizeCli := kustomize.NewCli(mockContext.CommandRunner)
dockerCli := docker.NewCli(mockContext.CommandRunner)
dotnetCli := dotnet.NewCli(mockContext.CommandRunner)
kubeLoginCli := kubelogin.NewCli(mockContext.CommandRunner)
credentialProvider := mockaccount.SubscriptionCredentialProviderFunc(
func(_ context.Context, _ string) (azcore.TokenCredential, error) {
@ -849,6 +851,7 @@ func createAksServiceTarget(
containerRegistryService,
remoteBuildManager,
dockerCli,
dotnetCli,
mockContext.Console,
cloud.AzurePublic(),
)

Просмотреть файл

@ -21,6 +21,7 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/infra"
"github.com/azure/azure-dev/cli/azd/pkg/tools/azcli"
"github.com/azure/azure-dev/cli/azd/pkg/tools/docker"
"github.com/azure/azure-dev/cli/azd/pkg/tools/dotnet"
"github.com/azure/azure-dev/cli/azd/test/mocks"
"github.com/azure/azure-dev/cli/azd/test/mocks/mockaccount"
"github.com/azure/azure-dev/cli/azd/test/mocks/mockazcli"
@ -132,6 +133,7 @@ func createContainerAppServiceTarget(
env *environment.Environment,
) ServiceTarget {
dockerCli := docker.NewCli(mockContext.CommandRunner)
dotnetCli := dotnet.NewCli(mockContext.CommandRunner)
credentialProvider := mockaccount.SubscriptionCredentialProviderFunc(
func(_ context.Context, _ string) (azcore.TokenCredential, error) {
return mockContext.Credentials, nil
@ -163,6 +165,7 @@ func createContainerAppServiceTarget(
containerRegistryService,
remoteBuildManager,
dockerCli,
dotnetCli,
mockContext.Console,
cloud.AzurePublic(),
)

Просмотреть файл

@ -0,0 +1,20 @@
version: "1.0"
tool: dotnet
interactions:
- id: 0
args:
- publish
- /private/var/folders/6n/sxbj12js5ksg6ztn0kslqp400000gn/T/Test_CLI_Up_Down_ContainerAppDotNetPublish3940537069/001/src/dotnet
- -r
- linux-x64
- -c
- Release
- /t:PublishContainer
- -p:ContainerRepository=containerapp/web-azdtest-de207ca
- -p:ContainerImageTag=azd-deploy-1732146560
- -p:ContainerRegistry=crtf5bjllihraai.azurecr.io
- --getProperty:GeneratedContainerConfiguration
exitCode: 0
stdout: |
{"config":{"ExposedPorts":{"8080/tcp":{}},"Labels":{"org.opencontainers.image.created":"2024-11-20T23:52:02.8460760Z","org.opencontainers.artifact.created":"2024-11-20T23:52:02.8460760Z","org.opencontainers.image.authors":"webapp","org.opencontainers.image.version":"1.0.0","org.opencontainers.image.base.name":"mcr.microsoft.com/dotnet/aspnet:8.0","net.dot.runtime.majorminor":"8.0","net.dot.sdk.version":"9.0.100-rc.2.24474.11","org.opencontainers.image.base.digest":"sha256:3e2d76f7d3310b31e6400154e8f718600138db5d5ec9c37748bbda6922182ead"},"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","APP_UID=1654","ASPNETCORE_HTTP_PORTS=8080","DOTNET_RUNNING_IN_CONTAINER=true","DOTNET_VERSION=8.0.11","ASPNET_VERSION=8.0.11"],"WorkingDir":"/app/","Entrypoint":["dotnet","/app/webapp.dll"],"User":"1654"},"created":"2024-11-20T23:52:04.0532130Z","rootfs":{"type":"layers","diff_ids":["sha256:c3548211b8264f8bfa47a6727043a64f1791b82ac965a284a7ea187e971a95e2","sha256:24716a9c28f5c0fcc7713347708e93e9531a58629fc22b47bd45f19b591c7453","sha256:2ffebcb9c50a8e37858d22d39df5e8a8004b2e12923ee60c6344162427424960","sha256:dec72eb2a8b47bbe9e0ffc56fb9c1a9e577e1b9febb385e836ee0977254b7c0d","sha256:20c9409270c12ce0c1bb3271d534508956fdc68f489365ffbb40a14532f86e88","sha256:9e43e3f4b30f02815d600b02af454c449b59d5c4dd5f3afa4e49c3ffd6ecd025","sha256:1f45fbd6c62e42e4da992eaf73808abd3b9fbf40cd08e7cebf3a33cf3eee0c10"]},"architecture":"amd64","os":"linux","history":[{"comment":"buildkit.dockerfile.v0","created":"2024-11-11T00:00:00.0000000Z","created_by":"ADD rootfs.tar.xz / # buildkit"},{"comment":"buildkit.dockerfile.v0","created":"2024-11-11T00:00:00.0000000Z","created_by":"CMD [\u0022bash\u0022]","empty_layer":true},{"comment":"buildkit.dockerfile.v0","created":"2024-11-12T18:41:09.3239341Z","created_by":"ENV APP_UID=1654 ASPNETCORE_HTTP_PORTS=8080 DOTNET_RUNNING_IN_CONTAINER=true","empty_layer":true},{"comment":"buildkit.dockerfile.v0","created":"2024-11-12T18:41:09.3239341Z","created_by":"RUN /bin/sh -c apt-get update \u0026\u0026 apt-get install -y --no-install-recommends ca-certificates libc6 libgcc-s1 libicu72 libssl3 libstdc\u002B\u002B6 tzdata zlib1g \u0026\u0026 rm -rf /var/lib/apt/lists/* # buildkit"},{"comment":"buildkit.dockerfile.v0","created":"2024-11-12T18:41:10.7740643Z","created_by":"RUN /bin/sh -c groupadd --gid=$APP_UID app \u0026\u0026 useradd -l --uid=$APP_UID --gid=$APP_UID --create-home app # buildkit"},{"comment":"buildkit.dockerfile.v0","created":"2024-11-12T18:41:16.8668835Z","created_by":"ENV DOTNET_VERSION=8.0.11","empty_layer":true},{"comment":"buildkit.dockerfile.v0","created":"2024-11-12T18:41:16.8668835Z","created_by":"COPY /dotnet /usr/share/dotnet # buildkit"},{"comment":"buildkit.dockerfile.v0","created":"2024-11-12T18:41:17.7626695Z","created_by":"RUN /bin/sh -c ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet # buildkit"},{"comment":"buildkit.dockerfile.v0","created":"2024-11-12T18:41:23.2518218Z","created_by":"ENV ASPNET_VERSION=8.0.11","empty_layer":true},{"comment":"buildkit.dockerfile.v0","created":"2024-11-12T18:41:23.2518218Z","created_by":"COPY /shared/Microsoft.AspNetCore.App /usr/share/dotnet/shared/Microsoft.AspNetCore.App # buildkit"},{"author":".NET SDK","created":"2024-11-20T23:52:04.0531950Z","created_by":".NET SDK Container Tooling, version 9.0.100-rc.2.24474.11\u002B315e1305dbe1a5ef5870faab5dc12d3a375f61eb"}]}
stderr: ""

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -24,7 +24,7 @@ resource app 'Microsoft.App/containerApps@2023-05-02-preview' = {
activeRevisionsMode: 'single'
ingress: {
external: true
targetPort: 3100
targetPort: 8080
transport: 'auto'
}
secrets: [

Просмотреть файл

@ -12,7 +12,7 @@ RUN dotnet publish -c Release -o out
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build-env /app/out .
EXPOSE 3100
ENV ASPNETCORE_URLS=http://+:3100
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENTRYPOINT ["dotnet", "webapp.dll"]

Просмотреть файл

@ -285,6 +285,73 @@ func Test_CLI_Up_Down_ContainerApp(t *testing.T) {
require.False(t, has, "WEBSITE_URL should have been removed from the environment as part of infrastructure removal")
}
func Test_CLI_Up_Down_ContainerAppDotNetPublish(t *testing.T) {
t.Parallel()
ctx, cancel := newTestContext(t)
defer cancel()
dir := tempDirWithDiagnostics(t)
t.Logf("DIR: %s", dir)
session := recording.Start(t)
envName := randomOrStoredEnvName(session)
t.Logf("AZURE_ENV_NAME: %s", envName)
cli := azdcli.NewCLI(t, azdcli.WithSession(session))
cli.WorkingDirectory = dir
cli.Env = append(cli.Env, os.Environ()...)
cli.Env = append(cli.Env, "AZURE_LOCATION=eastus2")
err := copySample(dir, "containerapp")
require.NoError(t, err, "failed expanding sample")
// Remove the Dockerfile so that we go down the `dotnet publish` path.
err = os.Remove(filepath.Join(dir, "src", "dotnet", "Dockerfile"))
require.NoError(t, err)
_, err = cli.RunCommandWithStdIn(ctx, stdinForInit(envName), "init")
require.NoError(t, err)
_, err = cli.RunCommandWithStdIn(ctx, stdinForProvision(), "provision")
require.NoError(t, err)
_, err = cli.RunCommand(ctx, "deploy", "--cwd", filepath.Join(dir, "src", "dotnet"))
require.NoError(t, err)
// The sample hosts a small application that just responds with a 200 OK with a body of "Hello, `azd`."
// (without the quotes). Validate that the application is working.
env, err := godotenv.Read(filepath.Join(dir, azdcontext.EnvironmentDirectoryName, envName, ".env"))
require.NoError(t, err)
url, has := env["WEBSITE_URL"]
require.True(t, has, "WEBSITE_URL should be in environment after deploy")
if session == nil {
err = probeServiceHealth(
t, ctx, http.DefaultClient, retry.NewConstant(5*time.Second), url, expectedTestAppResponse)
require.NoError(t, err)
} else {
session.Variables[recording.SubscriptionIdKey] = env[environment.SubscriptionIdEnvVarName]
err = probeServiceHealth(
t, ctx, session.ProxyClient, retry.NewConstant(1*time.Millisecond), url, expectedTestAppResponse)
require.NoError(t, err)
}
_, err = cli.RunCommand(ctx, "down", "--force", "--purge")
require.NoError(t, err)
// As part of deleting the infrastructure, outputs of the infrastructure such as "WEBSITE_URL" should
// have been removed from the environment.
env, err = godotenv.Read(filepath.Join(dir, azdcontext.EnvironmentDirectoryName, envName, ".env"))
require.NoError(t, err)
_, has = env["WEBSITE_URL"]
require.False(t, has, "WEBSITE_URL should have been removed from the environment as part of infrastructure removal")
}
func Test_CLI_Up_Down_ContainerApp_RemoteBuild(t *testing.T) {
t.Parallel()

Просмотреть файл

@ -56,8 +56,8 @@ func sanitizeContainerAppListSecrets(i *cassette.Interaction) error {
// Redis requirepass. Sanitize the password, remove other config.
if strings.Contains(val, "requirepass ") {
body.Value[i].Value = to.Ptr("requirepass SANITIZED")
continue
}
continue
}
body.Value[i].Value = to.Ptr("SANITIZED")