зеркало из https://github.com/Azure/azure-dev.git
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:
Родитель
67098119bb
Коммит
6cd82ae9ff
|
@ -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(),
|
||||
)
|
||||
|
|
20
cli/azd/test/functional/testdata/recordings/Test_CLI_Up_Down_ContainerAppDotNetPublish.dotnet.yaml
поставляемый
Normal file
20
cli/azd/test/functional/testdata/recordings/Test_CLI_Up_Down_ContainerAppDotNetPublish.dotnet.yaml
поставляемый
Normal file
|
@ -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: ""
|
2298
cli/azd/test/functional/testdata/recordings/Test_CLI_Up_Down_ContainerAppDotNetPublish.yaml
поставляемый
Normal file
2298
cli/azd/test/functional/testdata/recordings/Test_CLI_Up_Down_ContainerAppDotNetPublish.yaml
поставляемый
Normal file
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -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")
|
||||
|
|
Загрузка…
Ссылка в новой задаче