save progress

This commit is contained in:
master
2026-01-09 18:27:36 +02:00
parent e608752924
commit a21d3dbc1f
361 changed files with 63068 additions and 1192 deletions

View File

@@ -0,0 +1,240 @@
// <copyright file="AzureDevOpsGenerator.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
using System.Text;
namespace StellaOps.Tools.WorkflowGenerator;
/// <summary>
/// Generates Azure DevOps pipeline YAML.
/// Sprint: SPRINT_20260109_010_003 Task: Implement generators
/// </summary>
public sealed class AzureDevOpsGenerator : IWorkflowGenerator
{
public CiPlatform Platform => CiPlatform.AzureDevOps;
public string PlatformName => "Azure DevOps";
public string DefaultFileName => "azure-pipelines.yml";
public string Generate(WorkflowOptions options)
{
var sb = new StringBuilder();
// Header comment
if (options.IncludeComments)
{
sb.AppendLine("# StellaOps Security Scan Pipeline");
sb.AppendLine("# Generated by StellaOps Workflow Generator");
sb.AppendLine("# https://stellaops.io/docs/ci-integration");
sb.AppendLine();
}
// Pipeline name
sb.AppendLine($"name: {options.Name}");
sb.AppendLine();
// Triggers
GenerateTriggers(sb, options);
sb.AppendLine();
// Variables
sb.AppendLine("variables:");
sb.AppendLine($" STELLAOPS_VERSION: '{options.Scan.CliVersion}'");
sb.AppendLine($" SARIF_FILE: '{options.Upload.SarifFileName}'");
if (options.Scan.GenerateSbom)
{
sb.AppendLine($" SBOM_FILE: '{options.Upload.SbomFileName}'");
}
foreach (var (key, value) in options.EnvironmentVariables)
{
sb.AppendLine($" {key}: '{value}'");
}
sb.AppendLine();
// Pool
sb.AppendLine("pool:");
sb.AppendLine($" vmImage: '{options.Runner ?? "ubuntu-latest"}'");
sb.AppendLine();
// Stages
sb.AppendLine("stages:");
sb.AppendLine(" - stage: Security");
sb.AppendLine(" displayName: 'Security Scan'");
sb.AppendLine(" jobs:");
sb.AppendLine(" - job: StellaOpsScan");
sb.AppendLine(" displayName: 'StellaOps Security Scan'");
sb.AppendLine(" steps:");
sb.AppendLine();
// Checkout
sb.AppendLine(" - checkout: self");
sb.AppendLine(" fetchDepth: 0");
sb.AppendLine();
// Install CLI
sb.AppendLine(" - task: Bash@3");
sb.AppendLine(" displayName: 'Install StellaOps CLI'");
sb.AppendLine(" inputs:");
sb.AppendLine(" targetType: 'inline'");
sb.AppendLine(" script: |");
if (options.Scan.CliVersion == "latest")
{
sb.AppendLine(" curl -fsSL https://get.stellaops.io | sh");
}
else
{
sb.AppendLine(" curl -fsSL https://get.stellaops.io | sh -s -- --version $(STELLAOPS_VERSION)");
}
sb.AppendLine();
// Run scan
sb.AppendLine(" - task: Bash@3");
sb.AppendLine(" displayName: 'Run StellaOps Scan'");
sb.AppendLine(" inputs:");
sb.AppendLine(" targetType: 'inline'");
sb.AppendLine(" script: |");
sb.AppendLine($" stella scan {BuildScanArgs(options)} --output sarif=$(SARIF_FILE)");
if (options.Scan.GenerateSbom)
{
sb.AppendLine(" stella sbom --output $(SBOM_FILE)");
}
if (options.Scan.FailOnSeverity is null)
{
sb.AppendLine(" continueOnError: true");
}
sb.AppendLine();
// Publish SARIF
if (options.Upload.UploadSarif)
{
sb.AppendLine(" - task: PublishBuildArtifacts@1");
sb.AppendLine(" displayName: 'Publish SARIF Results'");
sb.AppendLine(" inputs:");
sb.AppendLine(" pathToPublish: '$(SARIF_FILE)'");
sb.AppendLine(" artifactName: 'CodeAnalysisLogs'");
sb.AppendLine();
// Upload to Advanced Security if available
sb.AppendLine(" - task: AdvancedSecurity-Codeql-Autobuild@1");
sb.AppendLine(" displayName: 'Upload to Advanced Security'");
sb.AppendLine(" condition: always()");
sb.AppendLine(" continueOnError: true");
sb.AppendLine();
}
// Publish SBOM
if (options.Upload.UploadSbom && options.Scan.GenerateSbom)
{
sb.AppendLine(" - task: PublishBuildArtifacts@1");
sb.AppendLine(" displayName: 'Publish SBOM'");
sb.AppendLine(" inputs:");
sb.AppendLine(" pathToPublish: '$(SBOM_FILE)'");
sb.AppendLine(" artifactName: 'SBOM'");
}
return sb.ToString();
}
public ValidationResult Validate(WorkflowOptions options)
{
var errors = new List<string>();
if (options.Platform != CiPlatform.AzureDevOps)
errors.Add($"Platform must be AzureDevOps, got {options.Platform}");
var baseValidation = options.Validate();
if (!baseValidation.IsValid)
errors.AddRange(baseValidation.Errors);
return new ValidationResult(errors.Count == 0, errors);
}
private static void GenerateTriggers(StringBuilder sb, WorkflowOptions options)
{
var triggers = options.Triggers;
// CI trigger (push)
if (triggers.PushBranches.Length > 0)
{
sb.AppendLine("trigger:");
sb.AppendLine(" branches:");
sb.AppendLine(" include:");
foreach (var branch in triggers.PushBranches)
{
sb.AppendLine($" - {branch}");
}
}
else
{
sb.AppendLine("trigger: none");
}
sb.AppendLine();
// PR trigger
if (triggers.PullRequestBranches.Length > 0)
{
sb.AppendLine("pr:");
sb.AppendLine(" branches:");
sb.AppendLine(" include:");
foreach (var branch in triggers.PullRequestBranches)
{
sb.AppendLine($" - {branch}");
}
}
else
{
sb.AppendLine("pr: none");
}
// Schedule trigger
if (!string.IsNullOrEmpty(triggers.Schedule))
{
sb.AppendLine();
sb.AppendLine("schedules:");
sb.AppendLine($" - cron: '{triggers.Schedule}'");
sb.AppendLine(" displayName: 'Scheduled Security Scan'");
sb.AppendLine(" branches:");
sb.AppendLine(" include:");
sb.AppendLine(" - main");
sb.AppendLine(" always: true");
}
}
private static string BuildScanArgs(WorkflowOptions options)
{
var args = new List<string>();
var scan = options.Scan;
if (scan.ImageRef is not null)
{
args.Add($"image:{scan.ImageRef}");
}
else if (scan.ScanPath is not null)
{
args.Add(scan.ScanPath);
}
args.Add($"--severity {scan.MinSeverity}");
if (!scan.ScanVulnerabilities)
args.Add("--skip-vulnerabilities");
if (!scan.ScanSecrets)
args.Add("--skip-secrets");
if (scan.IncludeReachability)
args.Add("--reachability");
if (scan.FailOnSeverity is not null)
args.Add($"--exit-code-on {scan.FailOnSeverity}");
foreach (var arg in scan.AdditionalArgs)
{
args.Add(arg);
}
return string.Join(" ", args);
}
}

View File

@@ -0,0 +1,24 @@
// <copyright file="CiPlatform.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
namespace StellaOps.Tools.WorkflowGenerator;
/// <summary>
/// Target CI/CD platform.
/// Sprint: SPRINT_20260109_010_003 Task: Create models
/// </summary>
public enum CiPlatform
{
/// <summary>GitHub Actions.</summary>
GitHubActions,
/// <summary>GitLab CI/CD.</summary>
GitLabCi,
/// <summary>Azure DevOps Pipelines.</summary>
AzureDevOps,
/// <summary>Gitea Actions.</summary>
GiteaActions
}

View File

@@ -0,0 +1,229 @@
// <copyright file="GitHubActionsGenerator.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
using System.Text;
namespace StellaOps.Tools.WorkflowGenerator;
/// <summary>
/// Generates GitHub Actions workflow YAML.
/// Sprint: SPRINT_20260109_010_003 Task: Implement generators
/// </summary>
public sealed class GitHubActionsGenerator : IWorkflowGenerator
{
public CiPlatform Platform => CiPlatform.GitHubActions;
public string PlatformName => "GitHub Actions";
public string DefaultFileName => "stellaops-scan.yml";
public string Generate(WorkflowOptions options)
{
var sb = new StringBuilder();
// Header comment
if (options.IncludeComments)
{
sb.AppendLine("# StellaOps Security Scan Workflow");
sb.AppendLine("# Generated by StellaOps Workflow Generator");
sb.AppendLine("# https://stellaops.io/docs/ci-integration");
sb.AppendLine();
}
// Workflow name
sb.AppendLine($"name: {options.Name}");
sb.AppendLine();
// Triggers
GenerateTriggers(sb, options);
sb.AppendLine();
// Permissions for security scanning
sb.AppendLine("permissions:");
sb.AppendLine(" contents: read");
sb.AppendLine(" security-events: write");
if (options.Upload.UploadSbom)
{
sb.AppendLine(" actions: read");
}
sb.AppendLine();
// Environment variables
if (options.EnvironmentVariables.Count > 0)
{
sb.AppendLine("env:");
foreach (var (key, value) in options.EnvironmentVariables)
{
sb.AppendLine($" {key}: {QuoteIfNeeded(value)}");
}
sb.AppendLine();
}
// Jobs
sb.AppendLine("jobs:");
sb.AppendLine(" security-scan:");
sb.AppendLine($" name: Security Scan");
sb.AppendLine($" runs-on: {options.Runner ?? "ubuntu-latest"}");
sb.AppendLine();
sb.AppendLine(" steps:");
// Checkout
sb.AppendLine(" - name: Checkout repository");
sb.AppendLine(" uses: actions/checkout@v4");
sb.AppendLine();
// Install StellaOps CLI
sb.AppendLine(" - name: Install StellaOps CLI");
sb.AppendLine(" run: |");
if (options.Scan.CliVersion == "latest")
{
sb.AppendLine(" curl -fsSL https://get.stellaops.io | sh");
}
else
{
sb.AppendLine($" curl -fsSL https://get.stellaops.io | sh -s -- --version {options.Scan.CliVersion}");
}
sb.AppendLine();
// Run scan
sb.AppendLine(" - name: Run StellaOps scan");
sb.AppendLine(" run: |");
sb.AppendLine($" stella scan {BuildScanArgs(options)} --output sarif={options.Upload.SarifFileName}");
if (options.Scan.GenerateSbom)
{
sb.AppendLine($" stella sbom --output {options.Upload.SbomFileName}");
}
sb.AppendLine();
// Upload SARIF
if (options.Upload.UploadSarif)
{
sb.AppendLine(" - name: Upload SARIF to GitHub Code Scanning");
sb.AppendLine(" uses: github/codeql-action/upload-sarif@v3");
sb.AppendLine(" with:");
sb.AppendLine($" sarif_file: {options.Upload.SarifFileName}");
if (options.Upload.Category is not null)
{
sb.AppendLine($" category: {options.Upload.Category}");
}
if (options.Upload.WaitForProcessing)
{
sb.AppendLine(" wait-for-processing: true");
}
sb.AppendLine();
}
// Upload SBOM artifact
if (options.Upload.UploadSbom && options.Scan.GenerateSbom)
{
sb.AppendLine(" - name: Upload SBOM artifact");
sb.AppendLine(" uses: actions/upload-artifact@v4");
sb.AppendLine(" with:");
sb.AppendLine(" name: sbom");
sb.AppendLine($" path: {options.Upload.SbomFileName}");
sb.AppendLine(" retention-days: 90");
}
return sb.ToString();
}
public ValidationResult Validate(WorkflowOptions options)
{
var errors = new List<string>();
if (options.Platform != CiPlatform.GitHubActions)
errors.Add($"Platform must be GitHubActions, got {options.Platform}");
var baseValidation = options.Validate();
if (!baseValidation.IsValid)
errors.AddRange(baseValidation.Errors);
return new ValidationResult(errors.Count == 0, errors);
}
private static void GenerateTriggers(StringBuilder sb, WorkflowOptions options)
{
sb.AppendLine("on:");
var triggers = options.Triggers;
// Push trigger
if (triggers.PushBranches.Length > 0)
{
sb.AppendLine(" push:");
sb.AppendLine(" branches:");
foreach (var branch in triggers.PushBranches)
{
sb.AppendLine($" - {branch}");
}
}
// Pull request trigger
if (triggers.PullRequestBranches.Length > 0)
{
sb.AppendLine(" pull_request:");
sb.AppendLine(" branches:");
foreach (var branch in triggers.PullRequestBranches)
{
sb.AppendLine($" - {branch}");
}
}
// Schedule trigger
if (!string.IsNullOrEmpty(triggers.Schedule))
{
sb.AppendLine(" schedule:");
sb.AppendLine($" - cron: '{triggers.Schedule}'");
}
// Manual trigger
if (triggers.ManualTrigger)
{
sb.AppendLine(" workflow_dispatch:");
}
}
private static string BuildScanArgs(WorkflowOptions options)
{
var args = new List<string>();
var scan = options.Scan;
if (scan.ImageRef is not null)
{
args.Add($"image:{scan.ImageRef}");
}
else if (scan.ScanPath is not null)
{
args.Add(scan.ScanPath);
}
args.Add($"--severity {scan.MinSeverity}");
if (!scan.ScanVulnerabilities)
args.Add("--skip-vulnerabilities");
if (!scan.ScanSecrets)
args.Add("--skip-secrets");
if (scan.IncludeReachability)
args.Add("--reachability");
if (scan.FailOnSeverity is not null)
args.Add($"--exit-code-on {scan.FailOnSeverity}");
foreach (var arg in scan.AdditionalArgs)
{
args.Add(arg);
}
return string.Join(" ", args);
}
private static string QuoteIfNeeded(string value)
{
if (value.Contains(' ') || value.Contains(':') || value.Contains('#'))
return $"'{value}'";
return value;
}
}

View File

@@ -0,0 +1,188 @@
// <copyright file="GitLabCiGenerator.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
using System.Text;
namespace StellaOps.Tools.WorkflowGenerator;
/// <summary>
/// Generates GitLab CI/CD pipeline YAML.
/// Sprint: SPRINT_20260109_010_003 Task: Implement generators
/// </summary>
public sealed class GitLabCiGenerator : IWorkflowGenerator
{
public CiPlatform Platform => CiPlatform.GitLabCi;
public string PlatformName => "GitLab CI/CD";
public string DefaultFileName => ".gitlab-ci.yml";
public string Generate(WorkflowOptions options)
{
var sb = new StringBuilder();
// Header comment
if (options.IncludeComments)
{
sb.AppendLine("# StellaOps Security Scan Pipeline");
sb.AppendLine("# Generated by StellaOps Workflow Generator");
sb.AppendLine("# https://stellaops.io/docs/ci-integration");
sb.AppendLine();
}
// Stages
sb.AppendLine("stages:");
sb.AppendLine(" - security");
sb.AppendLine();
// Variables
sb.AppendLine("variables:");
sb.AppendLine($" STELLAOPS_VERSION: \"{options.Scan.CliVersion}\"");
sb.AppendLine($" SARIF_FILE: \"{options.Upload.SarifFileName}\"");
if (options.Scan.GenerateSbom)
{
sb.AppendLine($" SBOM_FILE: \"{options.Upload.SbomFileName}\"");
}
foreach (var (key, value) in options.EnvironmentVariables)
{
sb.AppendLine($" {key}: \"{value}\"");
}
sb.AppendLine();
// Default image
sb.AppendLine("default:");
sb.AppendLine($" image: {options.Runner ?? "ubuntu:22.04"}");
sb.AppendLine();
// Security scan job
sb.AppendLine("stellaops-scan:");
sb.AppendLine(" stage: security");
// Rules (triggers)
GenerateRules(sb, options);
// Before script - install CLI
sb.AppendLine(" before_script:");
sb.AppendLine(" - apt-get update && apt-get install -y curl");
if (options.Scan.CliVersion == "latest")
{
sb.AppendLine(" - curl -fsSL https://get.stellaops.io | sh");
}
else
{
sb.AppendLine(" - curl -fsSL https://get.stellaops.io | sh -s -- --version $STELLAOPS_VERSION");
}
sb.AppendLine();
// Script - run scan
sb.AppendLine(" script:");
sb.AppendLine($" - stella scan {BuildScanArgs(options)} --output sarif=$SARIF_FILE");
if (options.Scan.GenerateSbom)
{
sb.AppendLine(" - stella sbom --output $SBOM_FILE");
}
sb.AppendLine();
// Artifacts
sb.AppendLine(" artifacts:");
sb.AppendLine(" reports:");
sb.AppendLine(" sast: $SARIF_FILE");
sb.AppendLine(" paths:");
sb.AppendLine(" - $SARIF_FILE");
if (options.Scan.GenerateSbom)
{
sb.AppendLine(" - $SBOM_FILE");
}
sb.AppendLine(" expire_in: 90 days");
sb.AppendLine();
// Allow failure if not blocking
if (options.Scan.FailOnSeverity is null)
{
sb.AppendLine(" allow_failure: true");
}
return sb.ToString();
}
public ValidationResult Validate(WorkflowOptions options)
{
var errors = new List<string>();
if (options.Platform != CiPlatform.GitLabCi)
errors.Add($"Platform must be GitLabCi, got {options.Platform}");
var baseValidation = options.Validate();
if (!baseValidation.IsValid)
errors.AddRange(baseValidation.Errors);
return new ValidationResult(errors.Count == 0, errors);
}
private static void GenerateRules(StringBuilder sb, WorkflowOptions options)
{
sb.AppendLine(" rules:");
var triggers = options.Triggers;
// Push to branches
foreach (var branch in triggers.PushBranches)
{
sb.AppendLine($" - if: $CI_COMMIT_BRANCH == \"{branch}\"");
}
// Merge requests
if (triggers.PullRequestBranches.Length > 0)
{
sb.AppendLine(" - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"");
}
// Scheduled
if (!string.IsNullOrEmpty(triggers.Schedule))
{
sb.AppendLine(" - if: $CI_PIPELINE_SOURCE == \"schedule\"");
}
// Manual
if (triggers.ManualTrigger)
{
sb.AppendLine(" - if: $CI_PIPELINE_SOURCE == \"web\"");
}
}
private static string BuildScanArgs(WorkflowOptions options)
{
var args = new List<string>();
var scan = options.Scan;
if (scan.ImageRef is not null)
{
args.Add($"image:{scan.ImageRef}");
}
else if (scan.ScanPath is not null)
{
args.Add(scan.ScanPath);
}
args.Add($"--severity {scan.MinSeverity}");
if (!scan.ScanVulnerabilities)
args.Add("--skip-vulnerabilities");
if (!scan.ScanSecrets)
args.Add("--skip-secrets");
if (scan.IncludeReachability)
args.Add("--reachability");
if (scan.FailOnSeverity is not null)
args.Add($"--exit-code-on {scan.FailOnSeverity}");
foreach (var arg in scan.AdditionalArgs)
{
args.Add(arg);
}
return string.Join(" ", args);
}
}

View File

@@ -0,0 +1,41 @@
// <copyright file="IWorkflowGenerator.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
namespace StellaOps.Tools.WorkflowGenerator;
/// <summary>
/// Generates CI/CD workflow definitions.
/// Sprint: SPRINT_20260109_010_003 Task: Create interfaces
/// </summary>
public interface IWorkflowGenerator
{
/// <summary>
/// Platform identifier.
/// </summary>
CiPlatform Platform { get; }
/// <summary>
/// Platform display name.
/// </summary>
string PlatformName { get; }
/// <summary>
/// Default filename for the workflow.
/// </summary>
string DefaultFileName { get; }
/// <summary>
/// Generate workflow YAML.
/// </summary>
/// <param name="options">Workflow options.</param>
/// <returns>Generated YAML content.</returns>
string Generate(WorkflowOptions options);
/// <summary>
/// Validate options for this platform.
/// </summary>
/// <param name="options">Options to validate.</param>
/// <returns>Validation result.</returns>
ValidationResult Validate(WorkflowOptions options);
}

View File

@@ -0,0 +1,89 @@
// <copyright file="ScanConfig.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
using System.Collections.Immutable;
namespace StellaOps.Tools.WorkflowGenerator;
/// <summary>
/// Scan configuration for the workflow.
/// Sprint: SPRINT_20260109_010_003 Task: Create models
/// </summary>
public sealed record ScanConfig
{
/// <summary>
/// StellaOps CLI version to use.
/// </summary>
public string CliVersion { get; init; } = "latest";
/// <summary>
/// Image to scan (container image reference).
/// </summary>
public string? ImageRef { get; init; }
/// <summary>
/// Path to scan (file system path).
/// </summary>
public string? ScanPath { get; init; } = ".";
/// <summary>
/// Minimum severity to report.
/// </summary>
public string MinSeverity { get; init; } = "medium";
/// <summary>
/// Enable vulnerability scanning.
/// </summary>
public bool ScanVulnerabilities { get; init; } = true;
/// <summary>
/// Enable secret scanning.
/// </summary>
public bool ScanSecrets { get; init; } = true;
/// <summary>
/// Enable SBOM generation.
/// </summary>
public bool GenerateSbom { get; init; } = true;
/// <summary>
/// Include reachability analysis.
/// </summary>
public bool IncludeReachability { get; init; } = false;
/// <summary>
/// Fail build on findings above this severity.
/// </summary>
public string? FailOnSeverity { get; init; }
/// <summary>
/// Additional CLI arguments.
/// </summary>
public ImmutableArray<string> AdditionalArgs { get; init; } = [];
/// <summary>
/// Default configuration for repository scanning.
/// </summary>
public static ScanConfig DefaultRepository => new()
{
ScanPath = ".",
MinSeverity = "medium",
ScanVulnerabilities = true,
ScanSecrets = true,
GenerateSbom = true
};
/// <summary>
/// Configuration for container image scanning.
/// </summary>
public static ScanConfig ContainerImage(string imageRef) => new()
{
ImageRef = imageRef,
ScanPath = null,
MinSeverity = "low",
ScanVulnerabilities = true,
ScanSecrets = false,
GenerateSbom = true
};
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<RootNamespace>StellaOps.Tools.WorkflowGenerator</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="YamlDotNet" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,50 @@
// <copyright file="TriggerConfig.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
using System.Collections.Immutable;
namespace StellaOps.Tools.WorkflowGenerator;
/// <summary>
/// Workflow trigger configuration.
/// Sprint: SPRINT_20260109_010_003 Task: Create models
/// </summary>
public sealed record TriggerConfig
{
/// <summary>
/// Trigger on push to branches.
/// </summary>
public ImmutableArray<string> PushBranches { get; init; } = ["main", "master"];
/// <summary>
/// Trigger on pull request to branches.
/// </summary>
public ImmutableArray<string> PullRequestBranches { get; init; } = ["main", "master"];
/// <summary>
/// Scheduled cron expression (optional).
/// </summary>
public string? Schedule { get; init; }
/// <summary>
/// Allow manual trigger (workflow_dispatch).
/// </summary>
public bool ManualTrigger { get; init; } = true;
/// <summary>
/// Default trigger configuration for security scanning.
/// </summary>
public static TriggerConfig Default => new();
/// <summary>
/// Weekly security scan schedule.
/// </summary>
public static TriggerConfig WeeklySchedule => new()
{
PushBranches = ["main"],
PullRequestBranches = [],
Schedule = "0 0 * * 0", // Weekly on Sunday
ManualTrigger = true
};
}

View File

@@ -0,0 +1,47 @@
// <copyright file="UploadConfig.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
namespace StellaOps.Tools.WorkflowGenerator;
/// <summary>
/// SARIF upload configuration.
/// Sprint: SPRINT_20260109_010_003 Task: Create models
/// </summary>
public sealed record UploadConfig
{
/// <summary>
/// Upload SARIF to code scanning platform.
/// </summary>
public bool UploadSarif { get; init; } = true;
/// <summary>
/// Upload SBOM as artifact.
/// </summary>
public bool UploadSbom { get; init; } = true;
/// <summary>
/// Wait for SARIF processing.
/// </summary>
public bool WaitForProcessing { get; init; } = false;
/// <summary>
/// Category for SARIF upload (used for deduplication).
/// </summary>
public string? Category { get; init; }
/// <summary>
/// SARIF file name.
/// </summary>
public string SarifFileName { get; init; } = "stellaops-results.sarif";
/// <summary>
/// SBOM file name.
/// </summary>
public string SbomFileName { get; init; } = "sbom.cdx.json";
/// <summary>
/// Default upload configuration.
/// </summary>
public static UploadConfig Default => new();
}

View File

@@ -0,0 +1,61 @@
// <copyright file="WorkflowGeneratorFactory.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
namespace StellaOps.Tools.WorkflowGenerator;
/// <summary>
/// Factory for creating workflow generators.
/// Sprint: SPRINT_20260109_010_003 Task: Create factory
/// </summary>
public sealed class WorkflowGeneratorFactory
{
private readonly Dictionary<CiPlatform, IWorkflowGenerator> _generators;
public WorkflowGeneratorFactory()
{
_generators = new Dictionary<CiPlatform, IWorkflowGenerator>
{
[CiPlatform.GitHubActions] = new GitHubActionsGenerator(),
[CiPlatform.GitLabCi] = new GitLabCiGenerator(),
[CiPlatform.AzureDevOps] = new AzureDevOpsGenerator(),
[CiPlatform.GiteaActions] = new GitHubActionsGenerator() // Gitea Actions is compatible with GitHub Actions
};
}
/// <summary>
/// Gets a generator for the specified platform.
/// </summary>
/// <param name="platform">Target CI/CD platform.</param>
/// <returns>Workflow generator.</returns>
/// <exception cref="ArgumentException">If platform is not supported.</exception>
public IWorkflowGenerator GetGenerator(CiPlatform platform)
{
if (_generators.TryGetValue(platform, out var generator))
return generator;
throw new ArgumentException($"Unsupported platform: {platform}", nameof(platform));
}
/// <summary>
/// Generates a workflow for the specified options.
/// </summary>
/// <param name="options">Workflow options.</param>
/// <returns>Generated workflow YAML.</returns>
public string Generate(WorkflowOptions options)
{
var generator = GetGenerator(options.Platform);
var validation = generator.Validate(options);
if (!validation.IsValid)
throw new InvalidOperationException(
$"Invalid options: {string.Join(", ", validation.Errors)}");
return generator.Generate(options);
}
/// <summary>
/// Gets all supported platforms.
/// </summary>
public IEnumerable<CiPlatform> SupportedPlatforms => _generators.Keys;
}

View File

@@ -0,0 +1,106 @@
// <copyright file="WorkflowOptions.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
namespace StellaOps.Tools.WorkflowGenerator;
/// <summary>
/// Options for workflow generation.
/// Sprint: SPRINT_20260109_010_003 Task: Create models
/// </summary>
public sealed record WorkflowOptions
{
/// <summary>
/// Target CI/CD platform.
/// </summary>
public required CiPlatform Platform { get; init; }
/// <summary>
/// Workflow name.
/// </summary>
public string Name { get; init; } = "StellaOps Security Scan";
/// <summary>
/// Trigger configuration.
/// </summary>
public TriggerConfig Triggers { get; init; } = TriggerConfig.Default;
/// <summary>
/// Scan configuration.
/// </summary>
public ScanConfig Scan { get; init; } = ScanConfig.DefaultRepository;
/// <summary>
/// Upload configuration.
/// </summary>
public UploadConfig Upload { get; init; } = UploadConfig.Default;
/// <summary>
/// Runner/image to use.
/// </summary>
public string? Runner { get; init; }
/// <summary>
/// Environment variables.
/// </summary>
public IDictionary<string, string> EnvironmentVariables { get; init; } = new Dictionary<string, string>();
/// <summary>
/// Include comments in generated YAML.
/// </summary>
public bool IncludeComments { get; init; } = true;
/// <summary>
/// Creates default options for GitHub Actions.
/// </summary>
public static WorkflowOptions GitHubActionsDefault => new()
{
Platform = CiPlatform.GitHubActions,
Runner = "ubuntu-latest"
};
/// <summary>
/// Creates default options for GitLab CI.
/// </summary>
public static WorkflowOptions GitLabCiDefault => new()
{
Platform = CiPlatform.GitLabCi
};
/// <summary>
/// Creates default options for Azure DevOps.
/// </summary>
public static WorkflowOptions AzureDevOpsDefault => new()
{
Platform = CiPlatform.AzureDevOps,
Runner = "ubuntu-latest"
};
/// <summary>
/// Validates the options.
/// </summary>
public ValidationResult Validate()
{
var errors = new List<string>();
if (string.IsNullOrWhiteSpace(Name))
errors.Add("Workflow name is required");
if (Scan.ImageRef is null && Scan.ScanPath is null)
errors.Add("Either ImageRef or ScanPath must be specified");
if (Scan.ImageRef is not null && Scan.ScanPath is not null)
errors.Add("Only one of ImageRef or ScanPath should be specified");
return new ValidationResult(errors.Count == 0, errors);
}
}
/// <summary>
/// Validation result.
/// </summary>
public sealed record ValidationResult(bool IsValid, IReadOnlyList<string> Errors)
{
public static ValidationResult Success => new(true, []);
public static ValidationResult Failure(params string[] errors) => new(false, errors);
}