diff --git a/.github/workflows/build-test-release-tagged.yaml b/.github/workflows/build-test-release-tagged.yaml index a89aeb93..3762dff4 100644 --- a/.github/workflows/build-test-release-tagged.yaml +++ b/.github/workflows/build-test-release-tagged.yaml @@ -1,32 +1,30 @@ name: build-test-release-tagged - on: push: tags: - v* - jobs: build-test-release: permissions: contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Build all targets. - run: | - make build-all - - name: Run unit tests across all targets. - run: | - make test-all - - name: Prepare scenarios to be released. - run: | - sudo apt install zip - zip -r scenarios.zip scenarios - - name: Release Innovation Engine - uses: softprops/action-gh-release@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - generate_release_notes: true - files: | - ./bin/ie - ./scenarios.zip + - uses: actions/checkout@v4 + - name: Build all targets. + run: | + make build-all + - name: Run unit tests across all targets. + run: | + make test-all + - name: Prepare scenarios to be released. + run: | + sudo apt install zip + zip -r scenarios.zip scenarios + - name: Release Innovation Engine + uses: softprops/action-gh-release@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + generate_release_notes: true + files: | + ./bin/ie + ./scenarios.zip diff --git a/.github/workflows/build-test-release.yaml b/.github/workflows/build-test-release.yaml index 118b7270..52c9031f 100644 --- a/.github/workflows/build-test-release.yaml +++ b/.github/workflows/build-test-release.yaml @@ -5,11 +5,11 @@ on: - main jobs: build-test-release: - permissions: - contents: write + permissions: + contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Build all targets. run: | make build-all diff --git a/.github/workflows/scenario-testing.yaml b/.github/workflows/scenario-testing.yaml index 086f43a3..ec71e034 100644 --- a/.github/workflows/scenario-testing.yaml +++ b/.github/workflows/scenario-testing.yaml @@ -16,7 +16,7 @@ jobs: test-ie-installation: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Check ie installation run: | set -e @@ -38,14 +38,14 @@ jobs: # the testing subscription for any branch in this repository. environment: ScenarioTesting steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Build & test all targets. run: | make build-all make test-all WITH_COVERAGE=true make test-local-scenarios ENVIRONMENT=github-action - name: Upload test coverage - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: github.event_name == 'pull_request' with: name: coverage diff --git a/internal/engine/common/scenario.go b/internal/engine/common/scenario.go index d51e3344..f0552df2 100644 --- a/internal/engine/common/scenario.go +++ b/internal/engine/common/scenario.go @@ -6,13 +6,13 @@ import ( "net/http" "os" "path/filepath" - "regexp" "strings" "github.com/Azure/InnovationEngine/internal/lib" "github.com/Azure/InnovationEngine/internal/lib/fs" "github.com/Azure/InnovationEngine/internal/logging" "github.com/Azure/InnovationEngine/internal/parsers" + "github.com/Azure/InnovationEngine/internal/patterns" "github.com/yuin/goldmark/ast" ) @@ -29,6 +29,12 @@ type Scenario struct { Steps []Step Properties map[string]interface{} Environment map[string]string + Source []byte +} + +// Get the markdown source for the scenario as a string. +func (s *Scenario) GetSourceAsString() string { + return string(s.Source) } // Groups the codeblocks into steps based on the header of the codeblock. @@ -132,7 +138,7 @@ func CreateScenarioFromMarkdown( for key, value := range environmentVariableOverrides { environmentVariables[key] = value logging.GlobalLogger.Debugf("Attempting to override %s with %s", key, value) - exportRegex := regexp.MustCompile(fmt.Sprintf(`(?m)export %s\s*=\s*(.*?)(;|&&|$)`, key)) + exportRegex := patterns.ExportVariableRegex(key) for index, codeBlock := range codeBlocks { matches := exportRegex.FindAllStringSubmatch(codeBlock.Content, -1) @@ -206,6 +212,7 @@ func CreateScenarioFromMarkdown( Steps: steps, Properties: properties, MarkdownAst: markdown, + Source: source, }, nil } diff --git a/internal/engine/engine.go b/internal/engine/engine.go index fd531a79..a3c67a05 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -157,6 +157,7 @@ func (e *Engine) InteractWithScenario(scenario *common.Scenario) error { e.Configuration.Environment, stepsToExecute, lib.CopyMap(scenario.Environment), + scenario.GetSourceAsString(), ) if err != nil { return err @@ -181,9 +182,11 @@ func (e *Engine) InteractWithScenario(scenario *common.Scenario) error { switch e.Configuration.Environment { case environments.EnvironmentsAzure, environments.EnvironmentsOCD: + logging.GlobalLogger.Info( "Cleaning environment variable file located at /tmp/env-vars", ) + err := lib.CleanEnvironmentStateFile(lib.DefaultEnvironmentStateFile) if err != nil { logging.GlobalLogger.Errorf("Error cleaning environment variables: %s", err.Error()) diff --git a/internal/engine/environments/azure.go b/internal/engine/environments/azure.go index 15a50f16..7b55b091 100644 --- a/internal/engine/environments/azure.go +++ b/internal/engine/environments/azure.go @@ -3,9 +3,11 @@ package environments import ( "encoding/json" "fmt" + "strings" "github.com/Azure/InnovationEngine/internal/az" "github.com/Azure/InnovationEngine/internal/logging" + "github.com/Azure/InnovationEngine/internal/patterns" "github.com/Azure/InnovationEngine/internal/ui" ) @@ -23,12 +25,13 @@ type AzureStep struct { // The status of a one-click deployment or learn mode deployment. type AzureDeploymentStatus struct { - Steps []AzureStep `json:"steps"` - CurrentStep int `json:"currentStep"` - Status string `json:"status"` - ResourceURIs []string `json:"resourceURIs"` - Error string `json:"error"` - Output string `json:"output"` + Steps []AzureStep `json:"steps"` + CurrentStep int `json:"currentStep"` + Status string `json:"status"` + ResourceURIs []string `json:"resourceURIs"` + Error string `json:"error"` + Output string `json:"output"` + ConfiguredMarkdown string `json:"configuredMarkdown"` } func NewAzureDeploymentStatus() AzureDeploymentStatus { @@ -72,6 +75,52 @@ func (status *AzureDeploymentStatus) SetOutput(output string) { status.Output = output } +// Given a markdown string, replace the environment variables with the values +// provided in the environmentVariables map. Currently this is only used +// by the portal. +func (status *AzureDeploymentStatus) ConfigureMarkdownForDownload( + markdown string, + environmentVariables map[string]string, + environment string, +) { + if !IsAzureEnvironment(environment) { + return + } + + for key, value := range environmentVariables { + exportRegex := patterns.ExportVariableRegex(key) + + matches := exportRegex.FindAllStringSubmatch(markdown, -1) + + if len(matches) != 0 { + logging.GlobalLogger.Debugf( + "Found %d matches for the environment variable %s, Replacing them in markdown source.", + len(matches), + key, + ) + } + + for _, match := range matches { + oldLine := match[0] + oldValue := match[1] + + // Replace the old export with the new export statement + newLine := strings.Replace(oldLine, oldValue, value+" ", 1) + logging.GlobalLogger.Debugf("Replacing '%s' with '%s'", oldLine, newLine) + + // Update the code block with the new export statement + markdown = strings.Replace( + markdown, + oldLine, + newLine, + 1, + ) + } + } + + status.ConfiguredMarkdown = markdown +} + // Print out the status JSON for azure/cloudshell if in the correct environment. func ReportAzureStatus(status AzureDeploymentStatus, environment string) { if !IsAzureEnvironment(environment) { diff --git a/internal/engine/interactive/interactive.go b/internal/engine/interactive/interactive.go index 83d67e70..7a220b63 100644 --- a/internal/engine/interactive/interactive.go +++ b/internal/engine/interactive/interactive.go @@ -60,6 +60,7 @@ type InteractiveModeModel struct { scenarioCompleted bool components interactiveModeComponents ready bool + markdownSource string CommandLines []string } @@ -344,6 +345,18 @@ func (model InteractiveModeModel) Update(message tea.Msg) (tea.Model, tea.Cmd) { model.resourceGroupName, model.environment, ) + + environmentVariables, err := lib.LoadEnvironmentStateFile(lib.DefaultEnvironmentStateFile) + if err != nil { + logging.GlobalLogger.Errorf("Failed to load environment state file: %s", err) + model.azureStatus.SetError(err) + } + + model.azureStatus.ConfigureMarkdownForDownload( + model.markdownSource, + environmentVariables, + model.environment, + ) model.azureStatus.SetOutput(strings.Join(model.CommandLines, "\n")) commands = append( commands, @@ -570,6 +583,7 @@ func NewInteractiveModeModel( environment string, steps []common.Step, env map[string]string, + markdownSource string, ) (InteractiveModeModel, error) { // TODO: In the future we should just set the current step for the azure status // to one as the default. @@ -666,6 +680,7 @@ func NewInteractiveModeModel( environment: environment, scenarioCompleted: false, ready: false, + markdownSource: markdownSource, CommandLines: commandLines, }, nil } diff --git a/internal/patterns/regex.go b/internal/patterns/regex.go index a67b7241..bedf27a9 100644 --- a/internal/patterns/regex.go +++ b/internal/patterns/regex.go @@ -1,6 +1,9 @@ package patterns -import "regexp" +import ( + "fmt" + "regexp" +) var ( // An SSH command regex where there must be a username@host somewhere present in the command. @@ -18,4 +21,8 @@ var ( // ARM regex AzResourceURI = regexp.MustCompile(`\"id\": \"(/subscriptions/[^\"]+)\"`) AzResourceGroupName = regexp.MustCompile(`resourceGroups/([^\"\\/\ ]+)`) + + ExportVariableRegex = func(key string) *regexp.Regexp { + return regexp.MustCompile(fmt.Sprintf(`(?m)export %s\s*=\s*(.*?)(;|&&|$)`, key)) + } )