Skip to content

Commit

Permalink
Support Aspire journey for pipeline config (#3035)
Browse files Browse the repository at this point in the history
  • Loading branch information
vhvb1989 authored Dec 1, 2023
1 parent 862bbf1 commit 2ee35fe
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 9 deletions.
5 changes: 3 additions & 2 deletions cli/azd/pkg/apphost/service_ingress_configurer.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ func (adc *IngressSelector) SelectPublicServices(ctx context.Context) ([]string,
"it is running in. Selecting a service here will also allow it to be reached from the Internet.")

exposed, err := adc.console.MultiSelect(ctx, input.ConsoleOptions{
Message: "Select which services to expose to the Internet",
Options: services,
Message: "Select which services to expose to the Internet",
Options: services,
DefaultValue: []string{},
})
if err != nil {
return nil, err
Expand Down
18 changes: 13 additions & 5 deletions cli/azd/pkg/azdo/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ func CreatePipeline(
credentials *azcli.AzureCredentials,
env *environment.Environment,
console input.Console,
provisioningProvider provisioning.Options) (*build.BuildDefinition, error) {
provisioningProvider provisioning.Options,
additionalSecrets map[string]string) (*build.BuildDefinition, error) {

client, err := build.NewClient(ctx, connection)
if err != nil {
Expand All @@ -110,7 +111,7 @@ func CreatePipeline(
// Pipeline is already created. It uses the same connection but
// we need to update the variables and secrets as they
// might have been updated
buildDefinitionVariables, err := getDefinitionVariables(env, credentials, provisioningProvider)
buildDefinitionVariables, err := getDefinitionVariables(env, credentials, provisioningProvider, additionalSecrets)
if err != nil {
return nil, err
}
Expand All @@ -132,7 +133,7 @@ func CreatePipeline(
}

createDefinitionArgs, err := createAzureDevPipelineArgs(
ctx, projectId, name, repoName, credentials, env, queue, provisioningProvider)
ctx, projectId, name, repoName, credentials, env, queue, provisioningProvider, additionalSecrets)
if err != nil {
return nil, err
}
Expand All @@ -148,7 +149,8 @@ func CreatePipeline(
func getDefinitionVariables(
env *environment.Environment,
credentials *azcli.AzureCredentials,
provisioningProvider provisioning.Options) (*map[string]build.BuildDefinitionVariable, error) {
provisioningProvider provisioning.Options,
additionalSecrets map[string]string) (*map[string]build.BuildDefinitionVariable, error) {
rawCredential, err := json.Marshal(credentials)
if err != nil {
return nil, err
Expand Down Expand Up @@ -184,6 +186,11 @@ Visit %s for more information on configuring Terraform remote state`,
variables[key] = createBuildDefinitionVariable(value, false, true)
}
}

for key, value := range additionalSecrets {
variables[key] = createBuildDefinitionVariable(value, true, false)
}

return &variables, nil
}

Expand All @@ -197,6 +204,7 @@ func createAzureDevPipelineArgs(
env *environment.Environment,
queue *taskagent.TaskAgentQueue,
provisioningProvider provisioning.Options,
additionalSecrets map[string]string,
) (*build.CreateDefinitionArgs, error) {

repoType := "tfsgit"
Expand Down Expand Up @@ -233,7 +241,7 @@ func createAzureDevPipelineArgs(
trigger,
}

buildDefinitionVariables, err := getDefinitionVariables(env, credentials, provisioningProvider)
buildDefinitionVariables, err := getDefinitionVariables(env, credentials, provisioningProvider, additionalSecrets)
if err != nil {
return nil, err
}
Expand Down
21 changes: 20 additions & 1 deletion cli/azd/pkg/environment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ package environment

import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"regexp"
"strings"
Expand Down Expand Up @@ -57,19 +59,36 @@ type Environment struct {
Config config.Config
}

const AzdInitialEnvironmentConfigName = "AZD_INITIAL_ENVIRONMENT_CONFIG"

// New returns a new environment with the specified name.
func New(name string) *Environment {
env := &Environment{
name: name,
dotenv: make(map[string]string),
deletedKeys: make(map[string]struct{}),
Config: config.NewEmptyConfig(),
Config: getInitialConfig(),
}

env.SetEnvName(name)
return env
}

func getInitialConfig() config.Config {
initialConfig := os.Getenv(AzdInitialEnvironmentConfigName)
if initialConfig == "" {
return config.NewEmptyConfig()
}

var initConfig map[string]any
if err := json.Unmarshal([]byte(initialConfig), &initConfig); err != nil {
log.Println("Failed to unmarshal initial config", err, "Using empty config.")
return config.NewEmptyConfig()
}

return config.NewConfig(initConfig)
}

// NewWithValues returns an ephemeral environment (i.e. not backed by a data store) with a set
// of values. Useful for testing. The name parameter is added to the environment with the
// AZURE_ENV_NAME key, replacing an existing value in the provided values map. A nil values is
Expand Down
65 changes: 65 additions & 0 deletions cli/azd/pkg/environment/environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package environment

import (
"context"
"encoding/json"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -196,6 +197,70 @@ func TestRoundTripNumberWithLeadingZeros(t *testing.T) {
require.Equal(t, "01", env2.dotenv["TEST"])
}

const configSample = `{
"infra": {
"parameters": {
"bro": "xms"
}
},
"services": {
"app": {
"config": {
"exposedServices": [
"webapp"
]
}
}
}
}`

func TestInitialEnvState(t *testing.T) {

// expected config
var configEncode map[string]any
err := json.Unmarshal([]byte(configSample), &configEncode)
require.NoError(t, err)
configBytes, err := json.Marshal(configEncode)
require.NoError(t, err)

// Set up the environment variable
t.Setenv(AzdInitialEnvironmentConfigName, string(configBytes))

// Create the environment
env := New("test")

// pull config back and compare against expected
config := env.Config.Raw()
require.Equal(t, configEncode, config)
}

func TestInitialEnvStateWithError(t *testing.T) {

// Set up the environment variable
t.Setenv(AzdInitialEnvironmentConfigName, "not{}valid{}json")

// Create the environment
env := New("test")

// pull unexpectedConfig back and compare
unexpectedConfig := env.Config.Raw()
expected := config.NewEmptyConfig().Raw()
require.Equal(t, expected, unexpectedConfig)
}

func TestInitialEnvStateEmpty(t *testing.T) {

// expected config
expected := config.NewEmptyConfig().Raw()

// Create the environment
env := New("test")

// pull config back and compare against expected
config := env.Config.Raw()
require.Equal(t, expected, config)
}

func Test_fixupUnquotedDotenv(t *testing.T) {
test := map[string]string{
"TEST_SHOULD_NOT_QUOTE": "1",
Expand Down
2 changes: 2 additions & 0 deletions cli/azd/pkg/pipeline/azdo_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,7 @@ func (p *AzdoCiProvider) configurePipeline(
ctx context.Context,
repoDetails *gitRepositoryDetails,
provisioningProvider provisioning.Options,
additionalSecrets map[string]string,
) (CiPipeline, error) {
details := repoDetails.details.(*AzdoRepositoryDetails)

Expand All @@ -782,6 +783,7 @@ func (p *AzdoCiProvider) configurePipeline(
p.Env,
p.console,
provisioningProvider,
additionalSecrets,
)
if err != nil {
return nil, err
Expand Down
9 changes: 9 additions & 0 deletions cli/azd/pkg/pipeline/github_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,16 @@ func (p *GitHubCiProvider) configurePipeline(
ctx context.Context,
repoDetails *gitRepositoryDetails,
provisioningProvider provisioning.Options,
additionalSecrets map[string]string,
) (CiPipeline, error) {

repoSlug := repoDetails.owner + "/" + repoDetails.repoName
for key, value := range additionalSecrets {
if err := p.ghCli.SetSecret(ctx, repoSlug, key, value); err != nil {
return nil, fmt.Errorf("failed setting %s secret: %w", key, err)
}
}

return &workflow{
repoDetails: repoDetails,
}, nil
Expand Down
1 change: 1 addition & 0 deletions cli/azd/pkg/pipeline/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ type CiProvider interface {
ctx context.Context,
repoDetails *gitRepositoryDetails,
provisioningProvider provisioning.Options,
additionalSecrets map[string]string,
) (CiPipeline, error)
// configureConnection use the credential to set up the connection from the pipeline
// to Azure
Expand Down
15 changes: 14 additions & 1 deletion cli/azd/pkg/pipeline/pipeline_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package pipeline

import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
Expand Down Expand Up @@ -330,8 +331,20 @@ func (pm *PipelineManager) Configure(ctx context.Context) (result *PipelineConfi
return result, err
}

// Adding environment.AzdInitialEnvironmentConfigName as a secret to the pipeline as the base configuration for
// whenever a new environment is created. This means loading the local environment config into a pipeline secret which
// azd will use to restore the the config on CI
localEnvConfig, err := json.Marshal(pm.env.Config.Raw())
if err != nil {
return result, fmt.Errorf("failed to marshal environment config: %w", err)
}

additionalSecrets := map[string]string{
environment.AzdInitialEnvironmentConfigName: string(localEnvConfig),
}

// config pipeline handles setting or creating the provider pipeline to be used
ciPipeline, err := pm.ciProvider.configurePipeline(ctx, gitRepoInfo, infra.Options)
ciPipeline, err := pm.ciProvider.configurePipeline(ctx, gitRepoInfo, infra.Options, additionalSecrets)
if err != nil {
return result, err
}
Expand Down
2 changes: 2 additions & 0 deletions cli/azd/pkg/project/dotnet_importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,9 @@ func (ai *DotNetImporter) readManifest(ctx context.Context, svcConfig *ServiceCo
return cached, nil
}

ai.console.ShowSpinner(ctx, "Analyzing Aspire Application (this might take a moment...)", input.Step)
manifest, err := apphost.ManifestFromAppHost(ctx, svcConfig.Path(), ai.dotnetCli)
ai.console.StopSpinner(ctx, "", input.Step)
if err != nil {
return nil, err
}
Expand Down

0 comments on commit 2ee35fe

Please sign in to comment.