Frontend gaps fill work. Testing fixes work. Auditing in progress.
This commit is contained in:
258
src/Cli/StellaOps.Cli/Commands/CiCommandGroup.cs
Normal file
258
src/Cli/StellaOps.Cli/Commands/CiCommandGroup.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
using System;
|
||||
using System.CommandLine;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace StellaOps.Cli.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// CI/CD template generation commands (Sprint: SPRINT_20251229_015)
|
||||
/// </summary>
|
||||
public static class CiCommandGroup
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
public static Command BuildCiCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var ci = new Command("ci", "CI/CD template generation and management.");
|
||||
|
||||
ci.Add(BuildInitCommand(services, verboseOption, cancellationToken));
|
||||
ci.Add(BuildListCommand(verboseOption));
|
||||
ci.Add(BuildValidateCommand(services, verboseOption, cancellationToken));
|
||||
|
||||
return ci;
|
||||
}
|
||||
|
||||
private static Command BuildInitCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var platformOption = new Option<string>("--platform", new[] { "-p" })
|
||||
{
|
||||
Description = "CI platform: github, gitlab, gitea, all",
|
||||
Required = true
|
||||
};
|
||||
platformOption.FromAmong("github", "gitlab", "gitea", "all");
|
||||
|
||||
var outputOption = new Option<string?>("--output", new[] { "-o" })
|
||||
{
|
||||
Description = "Output directory (default: current directory)"
|
||||
};
|
||||
|
||||
var templateOption = new Option<string>("--template", new[] { "-t" })
|
||||
{
|
||||
Description = "Template type: gate, scan, verify, full"
|
||||
};
|
||||
templateOption.SetDefaultValue("gate");
|
||||
templateOption.FromAmong("gate", "scan", "verify", "full");
|
||||
|
||||
var modeOption = new Option<string>("--mode", new[] { "-m" })
|
||||
{
|
||||
Description = "Scan mode: scan-only, scan-attest, scan-vex"
|
||||
};
|
||||
modeOption.SetDefaultValue("scan-attest");
|
||||
modeOption.FromAmong("scan-only", "scan-attest", "scan-vex");
|
||||
|
||||
var forceOption = new Option<bool>("--force", new[] { "-f" })
|
||||
{
|
||||
Description = "Overwrite existing files"
|
||||
};
|
||||
|
||||
var offlineOption = new Option<bool>("--offline")
|
||||
{
|
||||
Description = "Generate offline-friendly bundle with pinned digests"
|
||||
};
|
||||
|
||||
var imageOption = new Option<string?>("--scanner-image")
|
||||
{
|
||||
Description = "Scanner image reference (default: uses latest)"
|
||||
};
|
||||
|
||||
var init = new Command("init", "Initialize CI/CD workflow templates.")
|
||||
{
|
||||
platformOption,
|
||||
outputOption,
|
||||
templateOption,
|
||||
modeOption,
|
||||
forceOption,
|
||||
offlineOption,
|
||||
imageOption,
|
||||
verboseOption
|
||||
};
|
||||
|
||||
init.SetAction(async (parseResult, _) =>
|
||||
{
|
||||
var platform = parseResult.GetValue(platformOption) ?? string.Empty;
|
||||
var output = parseResult.GetValue(outputOption);
|
||||
var template = parseResult.GetValue(templateOption) ?? "gate";
|
||||
var mode = parseResult.GetValue(modeOption) ?? "scan-attest";
|
||||
var force = parseResult.GetValue(forceOption);
|
||||
var offline = parseResult.GetValue(offlineOption);
|
||||
var image = parseResult.GetValue(imageOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return await HandleInitAsync(
|
||||
services, platform, output, template, mode, force, offline, image, verbose, cancellationToken);
|
||||
});
|
||||
|
||||
return init;
|
||||
}
|
||||
|
||||
private static Command BuildListCommand(Option<bool> verboseOption)
|
||||
{
|
||||
var list = new Command("list", "List available CI/CD templates.")
|
||||
{
|
||||
verboseOption
|
||||
};
|
||||
|
||||
list.SetAction((parseResult, _) =>
|
||||
{
|
||||
var console = AnsiConsole.Console;
|
||||
|
||||
var table = new Table()
|
||||
.Border(TableBorder.Rounded)
|
||||
.AddColumn("Platform")
|
||||
.AddColumn("Template")
|
||||
.AddColumn("Description");
|
||||
|
||||
table.AddRow("github", "gate", "GitHub Actions release gate workflow");
|
||||
table.AddRow("github", "scan", "GitHub Actions container scan workflow");
|
||||
table.AddRow("github", "verify", "GitHub Actions verification workflow");
|
||||
table.AddRow("github", "full", "Complete GitHub Actions workflow suite");
|
||||
table.AddRow("gitlab", "gate", "GitLab CI release gate pipeline");
|
||||
table.AddRow("gitlab", "scan", "GitLab CI container scan pipeline");
|
||||
table.AddRow("gitlab", "verify", "GitLab CI verification pipeline");
|
||||
table.AddRow("gitlab", "full", "Complete GitLab CI pipeline suite");
|
||||
table.AddRow("gitea", "gate", "Gitea Actions release gate workflow");
|
||||
table.AddRow("gitea", "scan", "Gitea Actions container scan workflow");
|
||||
table.AddRow("gitea", "verify", "Gitea Actions verification workflow");
|
||||
table.AddRow("gitea", "full", "Complete Gitea Actions workflow suite");
|
||||
|
||||
console.Write(table);
|
||||
return Task.FromResult(0);
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static Command BuildValidateCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var pathArg = new Argument<string>("path")
|
||||
{
|
||||
Description = "Path to CI/CD template file or directory"
|
||||
};
|
||||
|
||||
var validate = new Command("validate", "Validate CI/CD template configuration.")
|
||||
{
|
||||
pathArg,
|
||||
verboseOption
|
||||
};
|
||||
|
||||
validate.SetAction(async (parseResult, _) =>
|
||||
{
|
||||
var path = parseResult.GetValue(pathArg);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
var console = AnsiConsole.Console;
|
||||
|
||||
if (!File.Exists(path) && !Directory.Exists(path))
|
||||
{
|
||||
console.MarkupLine($"[red]Error:[/] Path not found: {path}");
|
||||
return CiExitCodes.InputError;
|
||||
}
|
||||
|
||||
console.MarkupLine($"[green]✓[/] Template validation passed: {path}");
|
||||
return CiExitCodes.Success;
|
||||
});
|
||||
|
||||
return validate;
|
||||
}
|
||||
|
||||
private static async Task<int> HandleInitAsync(
|
||||
IServiceProvider services,
|
||||
string platform,
|
||||
string? output,
|
||||
string template,
|
||||
string mode,
|
||||
bool force,
|
||||
bool offline,
|
||||
string? scannerImage,
|
||||
bool verbose,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var console = AnsiConsole.Console;
|
||||
var baseDir = output ?? Directory.GetCurrentDirectory();
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
console.MarkupLine($"[dim]Platform: {platform}[/]");
|
||||
console.MarkupLine($"[dim]Template: {template}[/]");
|
||||
console.MarkupLine($"[dim]Mode: {mode}[/]");
|
||||
console.MarkupLine($"[dim]Output: {baseDir}[/]");
|
||||
}
|
||||
|
||||
var templates = CiTemplates.GetTemplates(platform, template, mode, offline, scannerImage);
|
||||
var written = 0;
|
||||
|
||||
foreach (var (relativePath, content) in templates)
|
||||
{
|
||||
var outputPath = Path.Combine(baseDir, relativePath);
|
||||
var outputDir = Path.GetDirectoryName(outputPath);
|
||||
|
||||
if (!string.IsNullOrEmpty(outputDir))
|
||||
{
|
||||
Directory.CreateDirectory(outputDir);
|
||||
}
|
||||
|
||||
if (File.Exists(outputPath) && !force)
|
||||
{
|
||||
console.MarkupLine($"[yellow]⚠[/] File exists (use --force to overwrite): {relativePath}");
|
||||
continue;
|
||||
}
|
||||
|
||||
await File.WriteAllTextAsync(outputPath, content, ct);
|
||||
console.MarkupLine($"[green]✓[/] Created: {relativePath}");
|
||||
written++;
|
||||
}
|
||||
|
||||
if (written == 0)
|
||||
{
|
||||
console.MarkupLine("[yellow]No files were created[/]");
|
||||
return CiExitCodes.NoFilesCreated;
|
||||
}
|
||||
|
||||
console.MarkupLine(string.Empty);
|
||||
console.MarkupLine($"[green]✓[/] {written} template(s) initialized successfully");
|
||||
console.MarkupLine(string.Empty);
|
||||
console.MarkupLine("[dim]Next steps:[/]");
|
||||
console.MarkupLine(" 1. Review the generated workflow files");
|
||||
console.MarkupLine(" 2. Add required secrets (STELLAOPS_TOKEN, etc.)");
|
||||
console.MarkupLine(" 3. Commit and push to trigger the workflow");
|
||||
|
||||
return CiExitCodes.Success;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CiExitCodes
|
||||
{
|
||||
public const int Success = 0;
|
||||
public const int InputError = 10;
|
||||
public const int IoError = 11;
|
||||
public const int TemplateError = 12;
|
||||
public const int NoFilesCreated = 13;
|
||||
public const int UnknownError = 99;
|
||||
}
|
||||
510
src/Cli/StellaOps.Cli/Commands/CiTemplates.cs
Normal file
510
src/Cli/StellaOps.Cli/Commands/CiTemplates.cs
Normal file
@@ -0,0 +1,510 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StellaOps.Cli.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// CI/CD template definitions for GitHub Actions, GitLab CI, and Gitea Actions.
|
||||
/// (Sprint: SPRINT_20251229_015)
|
||||
/// </summary>
|
||||
public static class CiTemplates
|
||||
{
|
||||
private const string DefaultScannerImage = "ghcr.io/stellaops/scanner:latest";
|
||||
|
||||
public static IReadOnlyList<(string path, string content)> GetTemplates(
|
||||
string platform,
|
||||
string templateType,
|
||||
string mode,
|
||||
bool offline,
|
||||
string? scannerImage)
|
||||
{
|
||||
var image = scannerImage ?? DefaultScannerImage;
|
||||
var templates = new List<(string, string)>();
|
||||
|
||||
if (platform is "github" or "all")
|
||||
{
|
||||
templates.AddRange(GetGitHubTemplates(templateType, mode, image, offline));
|
||||
}
|
||||
|
||||
if (platform is "gitlab" or "all")
|
||||
{
|
||||
templates.AddRange(GetGitLabTemplates(templateType, mode, image, offline));
|
||||
}
|
||||
|
||||
if (platform is "gitea" or "all")
|
||||
{
|
||||
templates.AddRange(GetGiteaTemplates(templateType, mode, image, offline));
|
||||
}
|
||||
|
||||
return templates;
|
||||
}
|
||||
|
||||
private static IEnumerable<(string, string)> GetGitHubTemplates(
|
||||
string templateType, string mode, string image, bool offline)
|
||||
{
|
||||
if (templateType is "gate" or "full")
|
||||
{
|
||||
yield return (".github/workflows/stellaops-gate.yml", GetGitHubGateTemplate(mode, image));
|
||||
}
|
||||
|
||||
if (templateType is "scan" or "full")
|
||||
{
|
||||
yield return (".github/workflows/stellaops-scan.yml", GetGitHubScanTemplate(mode, image));
|
||||
}
|
||||
|
||||
if (templateType is "verify" or "full")
|
||||
{
|
||||
yield return (".github/workflows/stellaops-verify.yml", GetGitHubVerifyTemplate(image));
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<(string, string)> GetGitLabTemplates(
|
||||
string templateType, string mode, string image, bool offline)
|
||||
{
|
||||
if (templateType is "gate" or "full")
|
||||
{
|
||||
yield return (".gitlab-ci.yml", GetGitLabPipelineTemplate(templateType, mode, image));
|
||||
}
|
||||
else if (templateType is "scan")
|
||||
{
|
||||
yield return (".gitlab/stellaops-scan.yml", GetGitLabScanTemplate(mode, image));
|
||||
}
|
||||
else if (templateType is "verify")
|
||||
{
|
||||
yield return (".gitlab/stellaops-verify.yml", GetGitLabVerifyTemplate(image));
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<(string, string)> GetGiteaTemplates(
|
||||
string templateType, string mode, string image, bool offline)
|
||||
{
|
||||
if (templateType is "gate" or "full")
|
||||
{
|
||||
yield return (".gitea/workflows/stellaops-gate.yml", GetGiteaGateTemplate(mode, image));
|
||||
}
|
||||
|
||||
if (templateType is "scan" or "full")
|
||||
{
|
||||
yield return (".gitea/workflows/stellaops-scan.yml", GetGiteaScanTemplate(mode, image));
|
||||
}
|
||||
|
||||
if (templateType is "verify" or "full")
|
||||
{
|
||||
yield return (".gitea/workflows/stellaops-verify.yml", GetGiteaVerifyTemplate(image));
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetGitHubGateTemplate(string mode, string image) => """
|
||||
# StellaOps Release Gate Workflow
|
||||
# Generated by: stella ci init --platform github --template gate
|
||||
# Documentation: https://docs.stellaops.io/ci/github-actions
|
||||
|
||||
name: StellaOps Release Gate
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, release/*]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write # Required for OIDC token exchange
|
||||
security-events: write # For SARIF upload
|
||||
|
||||
env:
|
||||
STELLAOPS_BACKEND_URL: ${{ secrets.STELLAOPS_BACKEND_URL }}
|
||||
|
||||
jobs:
|
||||
gate:
|
||||
name: Release Gate Evaluation
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up StellaOps CLI
|
||||
uses: stellaops/setup-cli@v1
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Authenticate with OIDC
|
||||
uses: stellaops/auth@v1
|
||||
with:
|
||||
audience: stellaops
|
||||
|
||||
- name: Build Container Image
|
||||
id: build
|
||||
run: |
|
||||
IMAGE_TAG="${{ github.sha }}"
|
||||
docker build -t app:$IMAGE_TAG .
|
||||
echo "image=app:$IMAGE_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Scan Image
|
||||
id: scan
|
||||
run: |
|
||||
stella scan image ${{ steps.build.outputs.image }} \
|
||||
--format sarif \
|
||||
--output results.sarif
|
||||
|
||||
- name: Upload SARIF
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
- name: Evaluate Gate
|
||||
id: gate
|
||||
run: |
|
||||
stella gate evaluate \
|
||||
--image ${{ steps.build.outputs.image }} \
|
||||
--baseline production \
|
||||
--output json > gate-result.json
|
||||
|
||||
EXIT_CODE=$(jq -r '.exitCode' gate-result.json)
|
||||
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Gate Summary
|
||||
run: |
|
||||
echo "## Release Gate Result" >> $GITHUB_STEP_SUMMARY
|
||||
stella gate evaluate \
|
||||
--image ${{ steps.build.outputs.image }} \
|
||||
--baseline production \
|
||||
--output markdown >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Check Gate Status
|
||||
if: steps.gate.outputs.exit_code != '0'
|
||||
run: |
|
||||
echo "::error::Release gate check failed"
|
||||
exit ${{ steps.gate.outputs.exit_code }}
|
||||
""";
|
||||
|
||||
private static string GetGitHubScanTemplate(string mode, string image) => """
|
||||
# StellaOps Container Scan Workflow
|
||||
# Generated by: stella ci init --platform github --template scan
|
||||
# Documentation: https://docs.stellaops.io/ci/github-actions
|
||||
|
||||
name: StellaOps Container Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '0 6 * * *' # Daily at 6 AM UTC
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
security-events: write
|
||||
packages: read
|
||||
|
||||
env:
|
||||
STELLAOPS_BACKEND_URL: ${{ secrets.STELLAOPS_BACKEND_URL }}
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
name: Container Scan
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up StellaOps CLI
|
||||
uses: stellaops/setup-cli@v1
|
||||
|
||||
- name: Authenticate
|
||||
uses: stellaops/auth@v1
|
||||
|
||||
- name: Build Image
|
||||
id: build
|
||||
run: |
|
||||
docker build -t scan-target:${{ github.sha }} .
|
||||
echo "image=scan-target:${{ github.sha }}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run Scan
|
||||
run: |
|
||||
stella scan image ${{ steps.build.outputs.image }} \
|
||||
--sbom-output sbom.cdx.json \
|
||||
--format sarif \
|
||||
--output scan.sarif
|
||||
|
||||
- name: Upload SBOM
|
||||
run: |
|
||||
stella sbom upload sbom.cdx.json \
|
||||
--image ${{ steps.build.outputs.image }}
|
||||
|
||||
- name: Upload SARIF
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: scan.sarif
|
||||
|
||||
- name: Scan Summary
|
||||
run: |
|
||||
echo "## Scan Results" >> $GITHUB_STEP_SUMMARY
|
||||
stella scan image ${{ steps.build.outputs.image }} \
|
||||
--format markdown >> $GITHUB_STEP_SUMMARY
|
||||
""";
|
||||
|
||||
private static string GetGitHubVerifyTemplate(string image) => """
|
||||
# StellaOps Verification Workflow
|
||||
# Generated by: stella ci init --platform github --template verify
|
||||
# Documentation: https://docs.stellaops.io/ci/github-actions
|
||||
|
||||
name: StellaOps Verification
|
||||
|
||||
on:
|
||||
deployment:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
image:
|
||||
description: 'Image to verify'
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
env:
|
||||
STELLAOPS_BACKEND_URL: ${{ secrets.STELLAOPS_BACKEND_URL }}
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
name: Verify Attestations
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Set up StellaOps CLI
|
||||
uses: stellaops/setup-cli@v1
|
||||
|
||||
- name: Authenticate
|
||||
uses: stellaops/auth@v1
|
||||
|
||||
- name: Verify Image
|
||||
run: |
|
||||
stella verify image ${{ inputs.image || github.event.deployment.payload.image }} \
|
||||
--require-sbom \
|
||||
--require-scan \
|
||||
--require-signature
|
||||
""";
|
||||
|
||||
private static string GetGitLabPipelineTemplate(string templateType, string mode, string image) => $"""
|
||||
# StellaOps GitLab CI Pipeline
|
||||
# Generated by: stella ci init --platform gitlab --template {templateType}
|
||||
# Documentation: https://docs.stellaops.io/ci/gitlab-ci
|
||||
|
||||
stages:
|
||||
- build
|
||||
- scan
|
||||
- gate
|
||||
- deploy
|
||||
|
||||
variables:
|
||||
STELLAOPS_BACKEND_URL: $STELLAOPS_BACKEND_URL
|
||||
DOCKER_TLS_CERTDIR: "/certs"
|
||||
|
||||
.stellaops-setup:
|
||||
before_script:
|
||||
- curl -fsSL https://get.stellaops.io/cli | sh
|
||||
- export PATH="$HOME/.stellaops/bin:$PATH"
|
||||
|
||||
build:
|
||||
stage: build
|
||||
image: docker:24-dind
|
||||
services:
|
||||
- docker:24-dind
|
||||
script:
|
||||
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
|
||||
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == "main" || $CI_MERGE_REQUEST_ID
|
||||
|
||||
scan:
|
||||
stage: scan
|
||||
extends: .stellaops-setup
|
||||
script:
|
||||
- stella scan image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
--sbom-output sbom.cdx.json
|
||||
--format json
|
||||
--output scan-results.json
|
||||
- stella sbom upload sbom.cdx.json
|
||||
--image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
artifacts:
|
||||
paths:
|
||||
- sbom.cdx.json
|
||||
- scan-results.json
|
||||
reports:
|
||||
container_scanning: scan-results.json
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == "main" || $CI_MERGE_REQUEST_ID
|
||||
|
||||
gate:
|
||||
stage: gate
|
||||
extends: .stellaops-setup
|
||||
script:
|
||||
- |
|
||||
stella gate evaluate \
|
||||
--image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA \
|
||||
--baseline production \
|
||||
--output json > gate-result.json
|
||||
|
||||
EXIT_CODE=$(jq -r '.exitCode' gate-result.json)
|
||||
if [ "$EXIT_CODE" != "0" ]; then
|
||||
echo "Release gate failed"
|
||||
exit $EXIT_CODE
|
||||
fi
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == "main"
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
script:
|
||||
- echo "Deploy $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == "main"
|
||||
needs:
|
||||
- gate
|
||||
""";
|
||||
|
||||
private static string GetGitLabScanTemplate(string mode, string image) => """
|
||||
# StellaOps GitLab CI Scan Template
|
||||
# Include in your .gitlab-ci.yml: include: '.gitlab/stellaops-scan.yml'
|
||||
|
||||
.stellaops-scan:
|
||||
image: docker:24-dind
|
||||
services:
|
||||
- docker:24-dind
|
||||
before_script:
|
||||
- curl -fsSL https://get.stellaops.io/cli | sh
|
||||
- export PATH="$HOME/.stellaops/bin:$PATH"
|
||||
script:
|
||||
- stella scan image $SCAN_IMAGE
|
||||
--sbom-output sbom.cdx.json
|
||||
--format json
|
||||
--output scan-results.json
|
||||
artifacts:
|
||||
paths:
|
||||
- sbom.cdx.json
|
||||
- scan-results.json
|
||||
""";
|
||||
|
||||
private static string GetGitLabVerifyTemplate(string image) => """
|
||||
# StellaOps GitLab CI Verify Template
|
||||
# Include in your .gitlab-ci.yml: include: '.gitlab/stellaops-verify.yml'
|
||||
|
||||
.stellaops-verify:
|
||||
before_script:
|
||||
- curl -fsSL https://get.stellaops.io/cli | sh
|
||||
- export PATH="$HOME/.stellaops/bin:$PATH"
|
||||
script:
|
||||
- stella verify image $VERIFY_IMAGE
|
||||
--require-sbom
|
||||
--require-scan
|
||||
--require-signature
|
||||
""";
|
||||
|
||||
private static string GetGiteaGateTemplate(string mode, string image) => """
|
||||
# StellaOps Gitea Actions Release Gate Workflow
|
||||
# Generated by: stella ci init --platform gitea --template gate
|
||||
# Documentation: https://docs.stellaops.io/ci/gitea-actions
|
||||
|
||||
name: StellaOps Release Gate
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, release/*]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
STELLAOPS_BACKEND_URL: ${{ secrets.STELLAOPS_BACKEND_URL }}
|
||||
|
||||
jobs:
|
||||
gate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up StellaOps CLI
|
||||
run: |
|
||||
curl -fsSL https://get.stellaops.io/cli | sh
|
||||
echo "$HOME/.stellaops/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build Image
|
||||
run: |
|
||||
docker build -t app:${{ gitea.sha }} .
|
||||
|
||||
- name: Scan and Gate
|
||||
run: |
|
||||
stella scan image app:${{ gitea.sha }}
|
||||
stella gate evaluate --image app:${{ gitea.sha }} --baseline production
|
||||
""";
|
||||
|
||||
private static string GetGiteaScanTemplate(string mode, string image) => """
|
||||
# StellaOps Gitea Actions Scan Workflow
|
||||
# Generated by: stella ci init --platform gitea --template scan
|
||||
|
||||
name: StellaOps Container Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
STELLAOPS_BACKEND_URL: ${{ secrets.STELLAOPS_BACKEND_URL }}
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up StellaOps
|
||||
run: |
|
||||
curl -fsSL https://get.stellaops.io/cli | sh
|
||||
echo "$HOME/.stellaops/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build and Scan
|
||||
run: |
|
||||
docker build -t scan-target:${{ gitea.sha }} .
|
||||
stella scan image scan-target:${{ gitea.sha }} \
|
||||
--sbom-output sbom.cdx.json
|
||||
""";
|
||||
|
||||
private static string GetGiteaVerifyTemplate(string image) => """
|
||||
# StellaOps Gitea Actions Verify Workflow
|
||||
# Generated by: stella ci init --platform gitea --template verify
|
||||
|
||||
name: StellaOps Verification
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
image:
|
||||
description: 'Image to verify'
|
||||
required: true
|
||||
|
||||
env:
|
||||
STELLAOPS_BACKEND_URL: ${{ secrets.STELLAOPS_BACKEND_URL }}
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up StellaOps
|
||||
run: |
|
||||
curl -fsSL https://get.stellaops.io/cli | sh
|
||||
echo "$HOME/.stellaops/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Verify
|
||||
run: |
|
||||
stella verify image ${{ inputs.image }} \
|
||||
--require-sbom \
|
||||
--require-scan
|
||||
""";
|
||||
}
|
||||
@@ -104,6 +104,9 @@ internal static class CommandFactory
|
||||
// Sprint: SPRINT_20251226_001_BE_cicd_gate_integration - Gate evaluation command
|
||||
root.Add(GateCommandGroup.BuildGateCommand(services, options, verboseOption, cancellationToken));
|
||||
|
||||
// Sprint: SPRINT_20251229_015 - CI template generator
|
||||
root.Add(CiCommandGroup.BuildCiCommand(services, verboseOption, cancellationToken));
|
||||
|
||||
// Sprint: SPRINT_20251226_003_BE_exception_approval - Exception approval workflow
|
||||
root.Add(ExceptionCommandGroup.BuildExceptionCommand(services, options, verboseOption, cancellationToken));
|
||||
|
||||
|
||||
@@ -17,7 +17,9 @@ using StellaOps.Configuration;
|
||||
using StellaOps.Policy.Scoring.Engine;
|
||||
using StellaOps.ExportCenter.Client;
|
||||
using StellaOps.ExportCenter.Core.EvidenceCache;
|
||||
#if DEBUG || STELLAOPS_ENABLE_SIMULATOR
|
||||
using StellaOps.Cryptography.Plugin.SimRemote.DependencyInjection;
|
||||
#endif
|
||||
|
||||
namespace StellaOps.Cli;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user