Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jobs:
matrix:
features:
- ohmyposh
- microsoft-security-devops-cli
baseImage:
- debian:latest
- ubuntu:latest
Expand All @@ -34,6 +35,7 @@ jobs:
matrix:
features:
- ohmyposh
- microsoft-security-devops-cli
steps:
- uses: actions/checkout@v4

Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,40 @@ To use a custom theme file from your host machine, add a mount in your `devconta

The feature creates a placeholder file at `~/.ohmyposh.json` during installation. If you mount a custom theme to this location, it will be used automatically. Otherwise, the built-in theme specified in the options will be used.

### Microsoft Security DevOps CLI

Installs [Microsoft Security DevOps CLI](https://aka.ms/msdodocs) (`guardian` command) for running security analysis tools without requiring .NET installation.

**Usage:**

```json
{
"features": {
"ghcr.io/rosstaco/devcontainer-features/microsoft-security-devops-cli:1": {
"version": "latest"
}
}
}
```

**Options:**
- `version` - Version to install (default: "latest"). Use "latest" or a specific version like "0.215.0"
- `installPath` - Installation directory (default: "/usr/local/bin/guardian")

**Supported Architectures:**
- linux-x64 (x86_64)
- linux-arm64 (aarch64)

**Running Guardian:**

After installation, the `guardian` command is available in your PATH. To initialize guardian in your repository:

```bash
guardian init --force
```

Note: `guardian init` requires a git repository, so it must be run manually after the container starts (not during feature installation).

## Publishing

This repository uses a **GitHub Action** [workflow](.github/workflows/release.yaml) that publishes each Feature to GHCR (GitHub Container Registry).
Expand Down
202 changes: 202 additions & 0 deletions docs/microsoft-security-devops-cli-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
---
description: Implementation plan for adding Microsoft Security DevOps CLI (guardian) feature to devcontainer-features project
---

# Microsoft Security DevOps CLI Feature Implementation Plan

## Overview

Add a new devcontainer feature to install Microsoft Security DevOps CLI (`guardian` command) by downloading the architecture-specific nuget package, extracting binaries to a common location, and adding to PATH.

## Verified Information

- **Nuget API behavior**: `https://www.nuget.org/api/v2/package/Microsoft.Security.DevOps.Cli.linux-x64` without version parameter redirects to latest version (0.215.0 as of test)
- **Available architectures**: Only `linux-x64` and `linux-arm64` (no musl, arm, or other variants)
- **Command name**: `guardian` (Microsoft.Guardian.Cli binary name)
- **All binaries in tools/**: The nuget package contains all binaries in the `tools/` directory
- **Init command**: `guardian init --force` requires a git repository, so cannot be run during feature installation

## Implementation Steps

### 1. Create Feature Structure

Create `src/microsoft-security-devops-cli/` directory with:

#### `devcontainer-feature.json`
```json
{
"id": "microsoft-security-devops-cli",
"version": "1.0.0",
"name": "Microsoft Security DevOps CLI",
"description": "Installs Microsoft Security DevOps CLI (guardian) for running security analysis tools",
"documentationURL": "https://github.com/rosstaco/devcontainer-features/tree/main/src/microsoft-security-devops-cli",
"options": {
"version": {
"type": "string",
"default": "latest",
"description": "Version of Microsoft Security DevOps CLI to install. Use 'latest' or a specific version like '0.215.0'"
},
"installPath": {
"type": "string",
"default": "/usr/local/bin/guardian",
"description": "Directory where the guardian binaries will be installed"
}
},
"installsAfter": [
"ghcr.io/devcontainers/features/common-utils"
]
}
```

#### `install.sh`
Key requirements:
- Use `set -e` for error handling
- Detect architecture: `x86_64` → `linux-x64`, `aarch64|arm64` → `linux-arm64`, else error
- Read options: `VERSION="${VERSION:-latest}"`, `INSTALL_PATH="${INSTALLPATH:-/usr/local/bin/guardian}"`
- Build download URL:
- For "latest": `https://www.nuget.org/api/v2/package/Microsoft.Security.DevOps.Cli.${ARCH}`
- For specific version: `https://www.nuget.org/api/v2/package/Microsoft.Security.DevOps.Cli.${ARCH}/${VERSION}`
- Download to temp file (e.g., `/tmp/guardian-cli.nupkg`)
- Unzip to temp directory (e.g., `/tmp/guardian-extract`)
- Copy all files from `tools/*` to `$INSTALL_PATH`
- Set executable permissions: `chmod +x $INSTALL_PATH/*` or individually for binaries
- Verify installation: `guardian --version`
- Output completion message with hint about `guardian init --force` command
- Use colored output (GREEN, RED, YELLOW, NC) like ohmyposh pattern

### 2. Create Test Structure

Create `test/microsoft-security-devops-cli/` directory with:

#### `scenarios.json`
```json
{
"version": {
"image": "ubuntu:latest",
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plan's default test uses "ubuntu:latest" as the base image (line 75), but the actual test workflow in .github/workflows/test.yaml uses "mcr.microsoft.com/devcontainers/base:ubuntu" (line 3 in scenarios.json). While this might be intentional, the plan document should clarify which image is actually used for testing to avoid confusion.

Copilot uses AI. Check for mistakes.
"features": {
"microsoft-security-devops-cli": {
"version": "0.215.0"
}
}
},
"custom-install-path": {
"image": "ubuntu:latest",
"features": {
"microsoft-security-devops-cli": {
"installPath": "/usr/bin"
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plan document's test scenario on line 86 shows "installPath": "/usr/bin" but the actual implementation in scenarios.json (line 14) uses "installPath": "/usr/bin/guardian". The documentation should be updated to match the actual test scenario, or the scenario should be clarified to explain that guardian is a directory containing the binaries.

Copilot uses AI. Check for mistakes.
}
}
}
}
```

#### `test.sh`
Tests to implement:
```bash
#!/bin/bash
set -e
source dev-container-features-test-lib

check "guardian is installed" guardian --version
check "guardian is executable" which guardian
check "guardian binary in correct location" test -x /usr/local/bin/guardian/guardian
check "guardian can show help" guardian --help
Comment on lines +100 to +103
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plan document uses different command syntax than the actual implementation. Lines 63, 100, and 103 show guardian --version and guardian --help, but the actual test scripts use guardian version (without dashes). The plan document should be updated to reflect the correct command syntax used in the implementation.

Suggested change
check "guardian is installed" guardian --version
check "guardian is executable" which guardian
check "guardian binary in correct location" test -x /usr/local/bin/guardian/guardian
check "guardian can show help" guardian --help
check "guardian is installed" guardian version
check "guardian is executable" which guardian
check "guardian binary in correct location" test -x /usr/local/bin/guardian/guardian
check "guardian can show help" guardian help

Copilot uses AI. Check for mistakes.

reportResults
```

#### Additional test files (optional):
- `version.sh` - Test specific version installation
- `custom-install-path.sh` - Test custom installation path

### 3. Update Project Documentation

Update `README.md` to add new feature section after Oh My Posh:

```markdown
### Microsoft Security DevOps CLI

Installs [Microsoft Security DevOps CLI](https://aka.ms/msdodocs) (`guardian` command) for running security analysis tools without requiring .NET installation.

**Usage:**

```json
{
"features": {
"ghcr.io/rosstaco/devcontainer-features/microsoft-security-devops-cli:1": {
"version": "latest",
"installPath": "/usr/local/bin"
}
}
}
```

**Options:**
- `version` - Version to install (default: "latest"). Use "latest" or a specific version like "0.215.0"
- `installPath` - Installation directory (default: "/usr/local/bin")
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plan document shows an incorrect default installPath value. Line 136 shows the default as "/usr/local/bin", but the actual default in both devcontainer-feature.json (line 15) and install.sh (line 8) is "/usr/local/bin/guardian". This documentation should be corrected to match the implementation.

Suggested change
- `installPath` - Installation directory (default: "/usr/local/bin")
- `installPath` - Installation path (default: "/usr/local/bin/guardian")

Copilot uses AI. Check for mistakes.

**Supported Architectures:**
- linux-x64 (x86_64)
- linux-arm64 (aarch64)

**Running Guardian:**

After installation, the `guardian` command is available in your PATH. To initialize guardian in your repository:

```bash
guardian init --force
```

Note: `guardian init` requires a git repository, so it must be run manually after the container starts (not during feature installation).
```

### 4. Update Justfile (Optional)

Add build command for new feature:

```justfile
# Build Microsoft Security DevOps CLI feature
build-microsoft-security-devops-cli:
just build-feature microsoft-security-devops-cli

# Build all features
build-all:
just build-ohmyposh
just build-microsoft-security-devops-cli
```

## Key Design Decisions

1. **No curl/unzip checks**: Rely on `common-utils` feature via `installsAfter` to ensure dependencies are available
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The design decision documented on line 170 states "No curl/unzip checks" and recommends relying on common-utils feature via installsAfter. However, the actual implementation in install.sh (lines 18-47) does include checks and automatic installation of curl and unzip. This inconsistency between documentation and implementation should be resolved - either update the plan to reflect that dependencies are automatically installed, or remove the dependency installation code from the script.

Suggested change
1. **No curl/unzip checks**: Rely on `common-utils` feature via `installsAfter` to ensure dependencies are available
1. **Automatic curl/unzip checks and installation**: The feature checks for `curl` and `unzip` and installs them if missing, ensuring dependencies are available even if `common-utils` is not present.

Copilot uses AI. Check for mistakes.
2. **Simple version handling**: "latest" uses API without version parameter (auto-redirects), specific versions use full URL path
3. **Copy entire tools/ directory**: Install all binaries from nuget package's tools folder to support any dependencies
4. **No auto-init**: Don't run `guardian init --force` automatically as it requires a git repository
5. **Minimal options**: Only version and installPath, keeping it simple like the user requested
6. **Error on unsupported arch**: Clear error messages for architectures that don't have nuget packages

## Installation Flow

```
1. Feature detects architecture (x86_64 or aarch64)
2. Maps to nuget package variant (linux-x64 or linux-arm64)
3. Downloads .nupkg file from nuget.org API
4. Extracts to temporary directory
5. Copies tools/* binaries to install path
6. Sets executable permissions
7. Verifies guardian --version works
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plan document shows guardian --version on line 187, but the actual implementation verifies installation using command -v guardian (line 148 in install.sh) and the tests use guardian version without dashes. The plan should be updated to match the actual verification approach.

Suggested change
7. Verifies guardian --version works
7. Verifies guardian is installed using command -v guardian

Copilot uses AI. Check for mistakes.
8. Outputs completion message with init instructions
```

## Testing Strategy

1. **Default test**: Install latest version to /usr/local/bin on ubuntu:latest
2. **Version test**: Install specific version (0.215.0)
3. **Custom path test**: Install to /usr/bin instead of default
4. **Verification**: Each test confirms binary exists, is executable, in PATH, and runs successfully

## Future Enhancements (Not in Initial Implementation)

- Support for macOS architectures (osx-x64, osx-arm64) if needed
- Automatic guardian init if git repo detected
- Configuration file support
- Tool-specific version pinning
5 changes: 5 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ build-feature feature:
build-ohmyposh:
just build-feature ohmyposh

# Build Microsoft Security DevOps CLI feature
build-microsoft-security-devops-cli:
just build-feature microsoft-security-devops-cli

# Build all features
build-all:
just build-ohmyposh
just build-microsoft-security-devops-cli

# Clean copied features
clean:
Expand Down
22 changes: 22 additions & 0 deletions src/microsoft-security-devops-cli/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"id": "microsoft-security-devops-cli",
"version": "1.0.0",
"name": "Microsoft Security DevOps CLI",
"description": "Installs Microsoft Security DevOps CLI (guardian) for running security analysis tools",
"documentationURL": "https://github.com/rosstaco/devcontainer-features/tree/main/src/microsoft-security-devops-cli",
"options": {
"version": {
"type": "string",
"default": "latest",
"description": "Version of Microsoft Security DevOps CLI to install. Use 'latest' or a specific version like '0.215.0'"
},
"installPath": {
"type": "string",
"default": "/usr/local/bin/guardian",
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default installPath value /usr/local/bin/guardian is inconsistent with the documentation in README.md (line 67) and the plan document (line 41), which both state the default is /usr/local/bin. This inconsistency could confuse users about where binaries will be installed. Consider either: (1) updating the documentation to match this implementation with /usr/local/bin/guardian as the default, or (2) changing this default to /usr/local/bin and having the script create a guardian subdirectory when copying files. The current mismatch between code and documentation is misleading.

Suggested change
"default": "/usr/local/bin/guardian",
"default": "/usr/local/bin",

Copilot uses AI. Check for mistakes.
"description": "Directory where the guardian binaries will be installed"
}
},
"installsAfter": [
"ghcr.io/devcontainers/features/common-utils"
]
}
Loading