Skip to content

Feat/add guardian#2

Merged
rosstaco merged 5 commits intomainfrom
feat/add-guardian
Nov 11, 2025
Merged

Feat/add guardian#2
rosstaco merged 5 commits intomainfrom
feat/add-guardian

Conversation

@rosstaco
Copy link
Copy Markdown
Owner

@rosstaco rosstaco commented Nov 7, 2025

This pull request adds a new devcontainer feature for installing the Microsoft Security DevOps CLI (guardian command), along with documentation, build/test integration, and automated tests. The implementation supports version selection, custom install paths, and both x86_64 and arm64 Linux architectures.

Key additions and updates:

1. New Feature: Microsoft Security DevOps CLI

  • Adds the microsoft-security-devops-cli feature for devcontainers, allowing users to install the guardian CLI for security analysis. Supports version selection and custom installation paths, and works on linux-x64 and linux-arm64 architectures. Installation is handled via a robust shell script that manages dependencies, architecture detection, and PATH updates. [1] [2]

2. Documentation

  • Updates README.md with usage instructions, options, supported architectures, and post-install notes for the new feature.
  • Adds a detailed implementation plan in docs/microsoft-security-devops-cli-plan.md, covering design decisions, installation flow, and testing strategy.

3. Build and Test Integration

  • Integrates the new feature into the build system via the justfile and adds it to the test matrix in the GitHub Actions workflow. [1] [2] [3]

4. Automated Testing

  • Provides comprehensive automated tests for the feature, including default, version-specific, and custom install path scenarios. Test scripts verify installation, binary location, PATH integration, and CLI functionality. [1] [2] [3] [4]

These changes collectively introduce robust support for Microsoft Security DevOps CLI in devcontainers, with full documentation and CI coverage.

  • New feature: Adds microsoft-security-devops-cli for installing the guardian CLI with version and install path options, supporting x86_64 and arm64 Linux. [1] [2]
  • Documentation: Updates README.md and adds an implementation plan for the new feature. [1] [2]
  • Build/test integration: Updates justfile and GitHub Actions workflow to include the new feature in builds and tests. [1] [2] [3]
  • Automated testing: Adds scenario definitions and test scripts for default, versioned, and custom path installs. [1] [2] [3] [4]

Ross Miles added 3 commits November 6, 2025 04:29
…ller, docs, tests, and devcontainer integration
Add checks to detect and install curl and unzip (apt, apk, yum) at the
start of install.sh so required tools are available before download and
extraction steps.
…Image matrix

- Include microsoft-security-devops-cli in both autogenerated and scenarios test matrices
- Add debian:latest and ubuntu:latest to the baseImage matrix for broader coverage
@rosstaco rosstaco requested a review from Copilot November 7, 2025 03:01
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds a new devcontainer feature for installing Microsoft Security DevOps CLI (guardian command). The feature downloads architecture-specific NuGet packages, extracts the binaries, and configures the PATH to make the guardian command available in development containers.

Key changes:

  • New installation script with architecture detection, download, extraction, and PATH configuration
  • Feature configuration with version and installPath options
  • Comprehensive test suite covering default installation, version pinning, and custom paths
  • Documentation and CI/CD workflow updates

Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/microsoft-security-devops-cli/install.sh Main installation script that downloads NuGet package, extracts binaries, and configures PATH
src/microsoft-security-devops-cli/devcontainer-feature.json Feature metadata and configuration options
test/microsoft-security-devops-cli/test.sh Default feature test validating installation and functionality
test/microsoft-security-devops-cli/version.sh Test for specific version installation
test/microsoft-security-devops-cli/custom-install-path.sh Test for custom installation path
test/microsoft-security-devops-cli/scenarios.json Test scenarios configuration
docs/microsoft-security-devops-cli-plan.md Implementation plan and design decisions
README.md Documentation for the new feature
justfile Build targets for the new feature
.github/workflows/test.yaml CI/CD updates to test the new feature

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

},
"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.
Comment on lines +18 to +49
# Ensure curl is available
if ! command -v curl &> /dev/null; then
echo "Installing curl..."
export DEBIAN_FRONTEND=noninteractive
if command -v apt-get &> /dev/null; then
apt-get update && apt-get install -y curl
elif command -v apk &> /dev/null; then
apk add --no-cache curl
elif command -v yum &> /dev/null; then
yum install -y curl
else
echo -e "${RED}Could not install curl. Please install it manually.${NC}"
exit 1
fi
fi

if ! command -v unzip &> /dev/null; then
echo "Installing unzip..."
export DEBIAN_FRONTEND=noninteractive
if command -v apt-get &> /dev/null; then
apt-get update && apt-get install -y unzip
elif command -v apk &> /dev/null; then
apk add --no-cache unzip
elif command -v yum &> /dev/null; then
yum install -y unzip
else
echo -e "${RED}Could not install unzip. Please install it manually.${NC}"
exit 1
fi
fi


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 installation script includes dependency checks and installation for curl and unzip (lines 19-47), but the design document (line 170) explicitly states as a key design decision: 'No curl/unzip checks: Rely on common-utils feature via installsAfter to ensure dependencies are available'. This contradicts the documented design decision. Since the feature declares installsAfter: [\"ghcr.io/devcontainers/features/common-utils\"], these checks are redundant and add unnecessary complexity. Consider removing the dependency installation logic to align with the stated design decision.

Suggested change
# Ensure curl is available
if ! command -v curl &> /dev/null; then
echo "Installing curl..."
export DEBIAN_FRONTEND=noninteractive
if command -v apt-get &> /dev/null; then
apt-get update && apt-get install -y curl
elif command -v apk &> /dev/null; then
apk add --no-cache curl
elif command -v yum &> /dev/null; then
yum install -y curl
else
echo -e "${RED}Could not install curl. Please install it manually.${NC}"
exit 1
fi
fi
if ! command -v unzip &> /dev/null; then
echo "Installing unzip..."
export DEBIAN_FRONTEND=noninteractive
if command -v apt-get &> /dev/null; then
apt-get update && apt-get install -y unzip
elif command -v apk &> /dev/null; then
apk add --no-cache unzip
elif command -v yum &> /dev/null; then
yum install -y unzip
else
echo -e "${RED}Could not install unzip. Please install it manually.${NC}"
exit 1
fi
fi

Copilot uses AI. Check for mistakes.
Comment thread README.md Outdated

**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 7, 2025

Choose a reason for hiding this comment

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

The documentation states the default installPath is /usr/local/bin, but the actual default value in devcontainer-feature.json (line 15) is /usr/local/bin/guardian. This mismatch will mislead users about where the guardian binaries will be installed by default. Update this documentation to match the actual implementation: (default: \"/usr/local/bin/guardian\").

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

Copilot uses AI. Check for mistakes.
},
"installPath": {
"type": "string",
"default": "/usr/local/bin",
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 plan document specifies the default installPath as /usr/local/bin, but the actual implementation in devcontainer-feature.json uses /usr/local/bin/guardian. This inconsistency between the plan and implementation should be corrected to reflect the actual default value: \"default\": \"/usr/local/bin/guardian\".

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

Copilot uses AI. Check for mistakes.
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}"`
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 documented fallback value for INSTALL_PATH is /usr/local/bin, but the actual implementation uses /usr/local/bin/guardian. Update this line to match the implementation: INSTALL_PATH=\"${INSTALLPATH:-/usr/local/bin/guardian}\".

Suggested change
- Read options: `VERSION="${VERSION:-latest}"`, `INSTALL_PATH="${INSTALLPATH:-/usr/local/bin}"`
- Read options: `VERSION="${VERSION:-latest}"`, `INSTALL_PATH="${INSTALLPATH:-/usr/local/bin/guardian}"`

Copilot uses AI. Check for mistakes.

check "guardian is installed" guardian --version
check "guardian is executable" which guardian
check "guardian binary in correct location" test -x /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 plan document expects the guardian binary at /usr/local/bin/guardian, but the actual test implementation (test/microsoft-security-devops-cli/test.sh line 22) tests for /usr/local/bin/guardian/guardian (nested path). Since the implementation extracts the tools folder contents into INSTALL_PATH and the default is /usr/local/bin/guardian, the binary would be at /usr/local/bin/guardian/guardian. Update this test expectation to: test -x /usr/local/bin/guardian/guardian.

Suggested change
check "guardian binary in correct location" test -x /usr/local/bin/guardian
check "guardian binary in correct location" test -x /usr/local/bin/guardian/guardian

Copilot uses AI. Check for mistakes.
Ross Miles added 2 commits November 11, 2025 00:47
- Update README.md to show correct default installPath: /usr/local/bin/guardian
- Update plan document to reflect correct default and fallback values
- Update plan document test example to check for nested binary path
- Fix custom-install-path test scenario to use /usr/bin/guardian

Resolves all PR review comments about installPath inconsistencies.
Guardian version command returns exit code 1 instead of 0, causing
tests to fail with set -e. Add '|| true' to all guardian version
checks to handle the non-zero exit code while still validating
the output.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 11 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +105 to +106
# Set executable permissions recursively
find "$INSTALL_PATH" -type f -exec chmod +x {} \; 2>/dev/null || true
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.

[nitpick] The find command on line 106 uses chmod +x on all files, which may unnecessarily set execute permissions on non-binary files (like configuration files, documentation, etc.). Consider being more selective by only setting execute permissions on actual binary files, or at minimum, exclude known non-executable file types.

Suggested change
# Set executable permissions recursively
find "$INSTALL_PATH" -type f -exec chmod +x {} \; 2>/dev/null || true
# Set executable permissions only on likely executables
find "$INSTALL_PATH" -type f \( -perm -u=x -o -name "*.sh" -o -name "*.dll" -o -name "*.exe" -o -name "*.run" -o ! -name "*.*" \) -exec chmod +x {} \; 2>/dev/null || true

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +13
check "guardian is installed" bash -c "guardian version 2>&1 | grep -q 'Microsoft.Guardian.Cli' || true"

check "guardian is executable" which guardian

check "guardian version command works" bash -c "guardian version 2>&1 | grep -q '[0-9]\+\.[0-9]\+\.[0-9]\+' || true"
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 test checks on lines 9 and 13 use || true which makes the grep command always succeed, even if the expected pattern is not found. This defeats the purpose of set -e and will cause the test to pass even when guardian is not properly installed. Remove || true to allow proper test failure detection.

Suggested change
check "guardian is installed" bash -c "guardian version 2>&1 | grep -q 'Microsoft.Guardian.Cli' || true"
check "guardian is executable" which guardian
check "guardian version command works" bash -c "guardian version 2>&1 | grep -q '[0-9]\+\.[0-9]\+\.[0-9]\+' || true"
check "guardian is installed" bash -c "guardian version 2>&1 | grep -q 'Microsoft.Guardian.Cli'"
check "guardian is executable" which guardian
check "guardian version command works" bash -c "guardian version 2>&1 | grep -q '[0-9]\+\.[0-9]\+\.[0-9]\+'"

Copilot uses AI. Check for mistakes.

check "guardian installed in custom path" test -x /usr/bin/guardian/guardian

check "guardian is accessible" bash -c "guardian version 2>&1 | grep -q 'Microsoft.Guardian.Cli' || true"
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 test check on line 11 uses || true which makes the grep command always succeed, even if the expected pattern is not found. This defeats the purpose of set -e and will cause the test to pass even when guardian is not properly installed. Remove || true to allow proper test failure detection.

Suggested change
check "guardian is accessible" bash -c "guardian version 2>&1 | grep -q 'Microsoft.Guardian.Cli' || true"
check "guardian is accessible" bash -c "guardian version 2>&1 | grep -q 'Microsoft.Guardian.Cli'"

Copilot uses AI. Check for mistakes.

## 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.
"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.
Comment on lines +121 to +122
# Append to existing PATH
sed -i "s|^PATH=\"\(.*\)\"|PATH=\"\1:$INSTALL_PATH\"|" /etc/environment
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 sed command on line 122 will append $INSTALL_PATH to the PATH every time the script runs, even if it was already added in a previous run. The outer check on line 114 only checks the current shell's $PATH, not what's persisted in /etc/environment. This could lead to multiple duplicate entries. Consider checking if $INSTALL_PATH already exists in /etc/environment before modifying it, similar to the check done for rc files on line 132.

Suggested change
# Append to existing PATH
sed -i "s|^PATH=\"\(.*\)\"|PATH=\"\1:$INSTALL_PATH\"|" /etc/environment
# Only append if $INSTALL_PATH is not already present in /etc/environment's PATH
if ! grep -q "$INSTALL_PATH" /etc/environment; then
sed -i "s|^PATH=\"\(.*\)\"|PATH=\"\1:$INSTALL_PATH\"|" /etc/environment
fi

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +24
check "guardian is installed" bash -c "guardian version 2>&1 | grep -q 'Microsoft.Guardian.Cli' || true"

check "guardian is executable" which guardian

check "guardian binary in correct location" test -x /usr/local/bin/guardian/guardian

check "guardian can show version" bash -c "guardian version 2>&1 | grep -q '[0-9]\+\.[0-9]\+\.[0-9]\+' || true"
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 test checks on lines 9, 13, 18, and 24 use || true to make grep commands always succeed, which defeats the purpose of set -e. If the grep pattern doesn't match (i.e., guardian is not properly installed or doesn't show the expected output), the test will still pass because of || true. Remove || true to allow the test to properly fail when the expected output is not found.

Suggested change
check "guardian is installed" bash -c "guardian version 2>&1 | grep -q 'Microsoft.Guardian.Cli' || true"
check "guardian is executable" which guardian
check "guardian binary in correct location" test -x /usr/local/bin/guardian/guardian
check "guardian can show version" bash -c "guardian version 2>&1 | grep -q '[0-9]\+\.[0-9]\+\.[0-9]\+' || true"
check "guardian is installed" bash -c "guardian version 2>&1 | grep -q 'Microsoft.Guardian.Cli'"
check "guardian is executable" which guardian
check "guardian binary in correct location" test -x /usr/local/bin/guardian/guardian
check "guardian can show version" bash -c "guardian version 2>&1 | grep -q '[0-9]\+\.[0-9]\+\.[0-9]\+'"

Copilot uses AI. Check for mistakes.

**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.
Comment on lines +100 to +103
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
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.
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.
@rosstaco rosstaco merged commit a15d8eb into main Nov 11, 2025
16 checks passed
@rosstaco rosstaco deleted the feat/add-guardian branch November 11, 2025 03:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants