diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..fa46afd --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,60 @@ +name: Release + +on: + push: + branches: + - main + +env: + CARGO_TERM_COLOR: always + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + name: Release Please + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + steps: + - uses: googleapis/release-please-action@7987652d64b4581673a76e33ad5e98e3dd56571f # v4.1.3 + id: release + with: + release-type: rust + + build: + name: Build (Linux) + needs: release-please + if: ${{ needs.release-please.outputs.release_created }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc97ebc # stable + + - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 + + - name: Build + run: cargo build --release --verbose + + - name: Run tests + run: cargo test --verbose + + - name: Prepare release assets + run: | + set -euo pipefail + cd target/release + cp ado-aw ado-aw-linux-x64 + sha256sum ado-aw-linux-x64 > ado-aw-linux-x64.sha256 + + - name: Upload release assets + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release upload ${{ needs.release-please.outputs.tag_name }} \ + target/release/ado-aw-linux-x64 \ + target/release/ado-aw-linux-x64.sha256 \ + --clobber diff --git a/AGENTS.md b/AGENTS.md index d466053..822893d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -573,6 +573,17 @@ Generates environment variable entries for the copilot AWF step when `read-only- If no `read-only-service-connection` is configured, this marker is replaced with an empty string, and ADO access tokens are omitted from the copilot invocation. +## {{ compiler_version }} + +Should be replaced with the version of the `ado-aw` compiler that generated the pipeline (derived from `CARGO_PKG_VERSION` at compile time). This version is used to construct the GitHub Releases download URL for the `ado-aw` binary. + +The generated pipelines download the compiler binary from: +``` +https://github.com/githubnext/ado-aw/releases/download/v{VERSION}/ado-aw-linux-x64 +``` + +A SHA256 checksum file (`ado-aw-linux-x64.sha256`) is also downloaded and verified to ensure binary integrity. This replaces the previous approach of downloading from an internal ADO pipeline artifact. + ### 1ES-Specific Template Markers The following markers are specific to the 1ES target (`target: 1es`) and are not used in standalone pipelines: @@ -944,7 +955,7 @@ mcp-servers: Network isolation is provided by AWF (Agentic Workflow Firewall), which provides L7 (HTTP/HTTPS) egress control using Squid proxy and Docker containers. AWF restricts network access to a whitelist of approved domains. -The AWF binary is downloaded from an internal ADO pipeline (pipeline 2450, branch `ms/main`, artifact `gh-aw-firewall-linux-x64`). Docker is sourced via the `DockerInstaller@0` ADO task. +The `ado-aw` compiler binary is distributed via [GitHub Releases](https://github.com/githubnext/ado-aw/releases) with SHA256 checksum verification. The AWF binary is downloaded from an internal ADO pipeline (pipeline 2450, branch `ms/main`, artifact `gh-aw-firewall-linux-x64`). Docker is sourced via the `DockerInstaller@0` ADO task. ### Default Allowed Domains diff --git a/src/compile/onees.rs b/src/compile/onees.rs index f702de3..01e7693 100644 --- a/src/compile/onees.rs +++ b/src/compile/onees.rs @@ -114,7 +114,9 @@ displayName: "Finalize""#, ); // Replace all template markers + let compiler_version = env!("CARGO_PKG_VERSION"); let replacements: Vec<(&str, &str)> = vec![ + ("{{ compiler_version }}", compiler_version), ("{{ pool }}", &pool), ("{{ schedule }}", &schedule), ("{{ pr_trigger }}", &pr_trigger), diff --git a/src/compile/standalone.rs b/src/compile/standalone.rs index 257179c..275325b 100644 --- a/src/compile/standalone.rs +++ b/src/compile/standalone.rs @@ -120,7 +120,9 @@ impl Compiler for StandaloneCompiler { ); // Replace template markers + let compiler_version = env!("CARGO_PKG_VERSION"); let replacements: Vec<(&str, &str)> = vec![ + ("{{ compiler_version }}", compiler_version), ("{{ pool }}", &pool), ("{{ setup_job }}", &setup_job), ("{{ teardown_job }}", &teardown_job), diff --git a/templates/1es-base.yml b/templates/1es-base.yml index 430370f..2034b7e 100644 --- a/templates/1es-base.yml +++ b/templates/1es-base.yml @@ -54,16 +54,23 @@ extends: {{ prepare_steps }} - - task: DownloadPipelineArtifact@2 - displayName: "Download agentic pipeline compiler" - inputs: - source: "specific" - project: "4x4" - pipeline: 2437 - runVersion: "latestFromBranch" - branchName: "refs/heads/main" - artifact: "agentic-pipeline-compiler-linux-x64" - targetPath: "$(Pipeline.Workspace)/agentic-pipeline-compiler" + - bash: | + COMPILER_VERSION="{{ compiler_version }}" + DOWNLOAD_DIR="$(Pipeline.Workspace)/agentic-pipeline-compiler" + DOWNLOAD_URL="https://github.com/githubnext/ado-aw/releases/download/v${COMPILER_VERSION}/ado-aw-linux-x64" + CHECKSUM_URL="${DOWNLOAD_URL}.sha256" + + mkdir -p "$DOWNLOAD_DIR" + echo "Downloading ado-aw v${COMPILER_VERSION} from GitHub Releases..." + curl -fsSL -o "$DOWNLOAD_DIR/ado-aw-linux-x64" "$DOWNLOAD_URL" + curl -fsSL -o "$DOWNLOAD_DIR/ado-aw-linux-x64.sha256" "$CHECKSUM_URL" + + echo "Verifying checksum..." + cd "$DOWNLOAD_DIR" + sha256sum --check ado-aw-linux-x64.sha256 + mv ado-aw-linux-x64 ado-aw + chmod +x ado-aw + displayName: "Download agentic pipeline compiler (v{{ compiler_version }})" - bash: | AGENTIC_PIPELINES_PATH="$(Pipeline.Workspace)/agentic-pipeline-compiler/ado-aw" @@ -160,16 +167,23 @@ extends: echo "##vso[task.prependpath]$(Agent.TempDirectory)/tools/agency.linux-x64" displayName: Add agency to PATH - - task: DownloadPipelineArtifact@2 - displayName: "Download agentic pipeline compiler" - inputs: - source: "specific" - project: "4x4" - pipeline: 2437 - runVersion: "latestFromBranch" - branchName: "refs/heads/main" - artifact: "agentic-pipeline-compiler-linux-x64" - targetPath: "$(Pipeline.Workspace)/agentic-pipeline-compiler" + - bash: | + COMPILER_VERSION="{{ compiler_version }}" + DOWNLOAD_DIR="$(Pipeline.Workspace)/agentic-pipeline-compiler" + DOWNLOAD_URL="https://github.com/githubnext/ado-aw/releases/download/v${COMPILER_VERSION}/ado-aw-linux-x64" + CHECKSUM_URL="${DOWNLOAD_URL}.sha256" + + mkdir -p "$DOWNLOAD_DIR" + echo "Downloading ado-aw v${COMPILER_VERSION} from GitHub Releases..." + curl -fsSL -o "$DOWNLOAD_DIR/ado-aw-linux-x64" "$DOWNLOAD_URL" + curl -fsSL -o "$DOWNLOAD_DIR/ado-aw-linux-x64.sha256" "$CHECKSUM_URL" + + echo "Verifying checksum..." + cd "$DOWNLOAD_DIR" + sha256sum --check ado-aw-linux-x64.sha256 + mv ado-aw-linux-x64 ado-aw + chmod +x ado-aw + displayName: "Download agentic pipeline compiler (v{{ compiler_version }})" - bash: | mkdir -p {{ working_directory }}/safe_outputs @@ -297,16 +311,23 @@ extends: - download: current artifact: analyzed_outputs - - task: DownloadPipelineArtifact@2 - displayName: "Download agentic pipeline compiler" - inputs: - source: "specific" - project: "4x4" - pipeline: 2437 - runVersion: "latestFromBranch" - branchName: "refs/heads/main" - artifact: "agentic-pipeline-compiler-linux-x64" - targetPath: "$(Pipeline.Workspace)/agentic-pipeline-compiler" + - bash: | + COMPILER_VERSION="{{ compiler_version }}" + DOWNLOAD_DIR="$(Pipeline.Workspace)/agentic-pipeline-compiler" + DOWNLOAD_URL="https://github.com/githubnext/ado-aw/releases/download/v${COMPILER_VERSION}/ado-aw-linux-x64" + CHECKSUM_URL="${DOWNLOAD_URL}.sha256" + + mkdir -p "$DOWNLOAD_DIR" + echo "Downloading ado-aw v${COMPILER_VERSION} from GitHub Releases..." + curl -fsSL -o "$DOWNLOAD_DIR/ado-aw-linux-x64" "$DOWNLOAD_URL" + curl -fsSL -o "$DOWNLOAD_DIR/ado-aw-linux-x64.sha256" "$CHECKSUM_URL" + + echo "Verifying checksum..." + cd "$DOWNLOAD_DIR" + sha256sum --check ado-aw-linux-x64.sha256 + mv ado-aw-linux-x64 ado-aw + chmod +x ado-aw + displayName: "Download agentic pipeline compiler (v{{ compiler_version }})" - bash: | chmod +x $(Pipeline.Workspace)/agentic-pipeline-compiler/ado-aw diff --git a/templates/base.yml b/templates/base.yml index d6fb5ec..9c287fb 100644 --- a/templates/base.yml +++ b/templates/base.yml @@ -52,17 +52,23 @@ jobs: copilot -h displayName: "Output copilot version" - - task: DownloadPipelineArtifact@2 - displayName: "Download agentic pipeline compiler" - name: agenticpipelinecompilerdrop - inputs: - source: "specific" - project: "4x4" - pipeline: 2437 - runVersion: "latestFromBranch" - branchName: "refs/heads/main" - artifact: "agentic-pipeline-compiler-linux-x64" - targetPath: "$(Pipeline.Workspace)/agentic-pipeline-compiler" + - bash: | + COMPILER_VERSION="{{ compiler_version }}" + DOWNLOAD_DIR="$(Pipeline.Workspace)/agentic-pipeline-compiler" + DOWNLOAD_URL="https://github.com/githubnext/ado-aw/releases/download/v${COMPILER_VERSION}/ado-aw-linux-x64" + CHECKSUM_URL="${DOWNLOAD_URL}.sha256" + + mkdir -p "$DOWNLOAD_DIR" + echo "Downloading ado-aw v${COMPILER_VERSION} from GitHub Releases..." + curl -fsSL -o "$DOWNLOAD_DIR/ado-aw-linux-x64" "$DOWNLOAD_URL" + curl -fsSL -o "$DOWNLOAD_DIR/ado-aw-linux-x64.sha256" "$CHECKSUM_URL" + + echo "Verifying checksum..." + cd "$DOWNLOAD_DIR" + sha256sum --check ado-aw-linux-x64.sha256 + mv ado-aw-linux-x64 ado-aw + chmod +x ado-aw + displayName: "Download agentic pipeline compiler (v{{ compiler_version }})" - bash: | AGENTIC_PIPELINES_PATH="$(Pipeline.Workspace)/agentic-pipeline-compiler/ado-aw" @@ -302,17 +308,23 @@ jobs: copilot -h displayName: "Output copilot version" - - task: DownloadPipelineArtifact@2 - displayName: "Download agentic pipeline compiler" - name: agenticpipelinecompilerdrop - inputs: - source: "specific" - project: "4x4" - pipeline: 2437 - runVersion: "latestFromBranch" - branchName: "refs/heads/main" - artifact: "agentic-pipeline-compiler-linux-x64" - targetPath: "$(Pipeline.Workspace)/agentic-pipeline-compiler" + - bash: | + COMPILER_VERSION="{{ compiler_version }}" + DOWNLOAD_DIR="$(Pipeline.Workspace)/agentic-pipeline-compiler" + DOWNLOAD_URL="https://github.com/githubnext/ado-aw/releases/download/v${COMPILER_VERSION}/ado-aw-linux-x64" + CHECKSUM_URL="${DOWNLOAD_URL}.sha256" + + mkdir -p "$DOWNLOAD_DIR" + echo "Downloading ado-aw v${COMPILER_VERSION} from GitHub Releases..." + curl -fsSL -o "$DOWNLOAD_DIR/ado-aw-linux-x64" "$DOWNLOAD_URL" + curl -fsSL -o "$DOWNLOAD_DIR/ado-aw-linux-x64.sha256" "$CHECKSUM_URL" + + echo "Verifying checksum..." + cd "$DOWNLOAD_DIR" + sha256sum --check ado-aw-linux-x64.sha256 + mv ado-aw-linux-x64 ado-aw + chmod +x ado-aw + displayName: "Download agentic pipeline compiler (v{{ compiler_version }})" - task: DockerInstaller@0 displayName: "Install Docker" @@ -488,17 +500,23 @@ jobs: - download: current artifact: analyzed_outputs_$(Build.BuildId) - - task: DownloadPipelineArtifact@2 - displayName: "Download agentic pipeline compiler" - name: agenticpipelinecompilerdrop - inputs: - source: "specific" - project: "4x4" - pipeline: 2437 - runVersion: "latestFromBranch" - branchName: "refs/heads/main" - artifact: "agentic-pipeline-compiler-linux-x64" - targetPath: "$(Pipeline.Workspace)/agentic-pipeline-compiler" + - bash: | + COMPILER_VERSION="{{ compiler_version }}" + DOWNLOAD_DIR="$(Pipeline.Workspace)/agentic-pipeline-compiler" + DOWNLOAD_URL="https://github.com/githubnext/ado-aw/releases/download/v${COMPILER_VERSION}/ado-aw-linux-x64" + CHECKSUM_URL="${DOWNLOAD_URL}.sha256" + + mkdir -p "$DOWNLOAD_DIR" + echo "Downloading ado-aw v${COMPILER_VERSION} from GitHub Releases..." + curl -fsSL -o "$DOWNLOAD_DIR/ado-aw-linux-x64" "$DOWNLOAD_URL" + curl -fsSL -o "$DOWNLOAD_DIR/ado-aw-linux-x64.sha256" "$CHECKSUM_URL" + + echo "Verifying checksum..." + cd "$DOWNLOAD_DIR" + sha256sum --check ado-aw-linux-x64.sha256 + mv ado-aw-linux-x64 ado-aw + chmod +x ado-aw + displayName: "Download agentic pipeline compiler (v{{ compiler_version }})" - bash: | ls -la "$(Pipeline.Workspace)/agentic-pipeline-compiler" diff --git a/tests/compiler_tests.rs b/tests/compiler_tests.rs index d078353..0743338 100644 --- a/tests/compiler_tests.rs +++ b/tests/compiler_tests.rs @@ -107,6 +107,10 @@ fn test_compiled_yaml_structure() { template_content.contains("{{ agency_params }}"), "Template should contain agency_params marker" ); + assert!( + template_content.contains("{{ compiler_version }}"), + "Template should contain compiler_version marker" + ); // Verify template doesn't accidentally use ${{ }} where {{ }} should be used // (The ${{ }} syntax is for Azure DevOps pipeline expressions and should be preserved) @@ -130,6 +134,20 @@ fn test_compiled_yaml_structure() { !template_content.contains("name: AZS-1ES-L-MMS-ubuntu-22.04"), "Template should not contain hardcoded pool name 'AZS-1ES-L-MMS-ubuntu-22.04'" ); + + // Verify that the ado-aw compiler is downloaded from GitHub Releases, not ADO pipeline artifacts + assert!( + !template_content.contains("pipeline: 2437"), + "Template should not reference ADO pipeline 2437 for the compiler" + ); + assert!( + template_content.contains("github.com/githubnext/ado-aw/releases"), + "Template should download the compiler from GitHub Releases" + ); + assert!( + template_content.contains("sha256sum --check"), + "Template should verify checksum of downloaded compiler" + ); } /// Test that the example file is valid and can be parsed @@ -281,3 +299,57 @@ fn test_fixture_complete_agent() { assert!(content.contains("ado: true"), "Should have built-in MCP"); assert!(content.contains("command:"), "Should have custom MCP"); } + +/// Test that compiled output has no unreplaced template markers +#[test] +fn test_compiled_output_no_unreplaced_markers() { + let temp_dir = + std::env::temp_dir().join(format!("agentic-pipeline-markers-{}", std::process::id())); + fs::create_dir_all(&temp_dir).expect("Failed to create temp directory"); + + let fixture_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("fixtures") + .join("minimal-agent.md"); + + let output_path = temp_dir.join("minimal-agent.yml"); + + // Run the compiler binary + let binary_path = PathBuf::from(env!("CARGO_BIN_EXE_ado-aw")); + let output = std::process::Command::new(&binary_path) + .args(["compile", fixture_path.to_str().unwrap(), "-o", output_path.to_str().unwrap()]) + .output() + .expect("Failed to run compiler"); + + assert!( + output.status.success(), + "Compiler should succeed: {}", + String::from_utf8_lossy(&output.stderr) + ); + assert!(output_path.exists(), "Compiled YAML should exist"); + + let compiled = fs::read_to_string(&output_path).expect("Should read compiled YAML"); + + // Verify no unreplaced {{ markers }} remain (excluding ${{ }} which are ADO expressions) + for line in compiled.lines() { + let stripped = line.replace("${{", ""); + assert!( + !stripped.contains("{{ "), + "Compiled output should not contain unreplaced marker: {}", + line.trim() + ); + } + + // Verify the compiler version was correctly substituted + let version = env!("CARGO_PKG_VERSION"); + assert!( + compiled.contains(version), + "Compiled output should contain compiler version {version}" + ); + assert!( + compiled.contains("github.com/githubnext/ado-aw/releases"), + "Compiled output should reference GitHub Releases" + ); + + let _ = fs::remove_dir_all(&temp_dir); +}