Skip to content

Commit 9790b6a

Browse files
committed
Debug
1 parent 7ff180b commit 9790b6a

File tree

2 files changed

+111
-36
lines changed

2 files changed

+111
-36
lines changed

.github/workflows/continuous.yml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,18 @@ on:
1414
release:
1515
types: [created] # Run when a release is created
1616

17+
# Least privilege permissions for the workflow by default
18+
permissions:
19+
contents: read
20+
packages: none
21+
id-token: none
22+
attestations: none
23+
1724
jobs:
1825
build:
1926
name: Build & Push
2027
runs-on: ubuntu-24.04 # Use a modern, stable Ubuntu runner
21-
28+
2229
# This job needs to publish, attest, and sign—so grant only here
2330
permissions:
2431
contents: read # Allow only reading repo content
@@ -27,6 +34,7 @@ jobs:
2734
attestations: write # For attest-sbom (SBOM attestation)
2835

2936
steps:
37+
3038
# Pin every action by SHA for supply chain security!
3139
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
3240
with:
@@ -47,14 +55,12 @@ jobs:
4755
with:
4856
path: .trivy-cache
4957
key: ${{ runner.os }}-trivy-cache
50-
restore-keys: |
51-
${{ runner.os }}-trivy-cache
5258

5359
# Run your hardened Nuke pipeline, which does: build, test, SBOM, hash, scan, etc.
5460
- name: 'Run: Push'
5561
run: ./build.cmd Push
5662
env:
57-
FeedGitHubToken: ${{ secrets.FEED_GITHUB_TOKEN }}
63+
FeedGitHubToken: ${{ secrets.GITHUB_TOKEN }}
5864
NuGetApiKey: ${{ secrets.NUGET_API_KEY }}
5965

6066
# Report test coverage to Coveralls

build/Build.cs

Lines changed: 101 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,48 @@
1818
using Nuke.Common.Tools.ReportGenerator;
1919
using static Nuke.Common.Tools.Docker.DockerTasks;
2020
using static Nuke.Common.Tools.DotNet.DotNetTasks;
21+
using static Nuke.Common.Tools.Git.GitTasks;
2122
using static Nuke.Common.Tools.ReportGenerator.ReportGeneratorTasks;
2223
using static Serilog.Log;
2324

2425
[UnsetVisualStudioEnvironmentVariables]
2526
[DotNetVerbosityMapping]
2627
class Build : NukeBuild
2728
{
29+
public const string MainBranch = "main";
30+
public const string ProjectName = ProductName + ".Shared";
31+
public const string ProductName = "DendroDocs";
32+
public const string RepositoryOwner = "dendrodocs";
33+
public const string RepositoryUrl = "https://github.com/" + RepositoryOwner + "/dotnet-shared-lib";
34+
public const string SbomNamespaceBase = "https://sbom.dendrodocs.dev";
35+
public const string SbomManifestRelPath = "_manifest/spdx_2.2/manifest.spdx.json";
36+
public const string GithubFeedSource = "GitHub - " + RepositoryOwner;
37+
public const string NugetSourceUrl = "https://api.nuget.org/v3/index.json";
38+
public const string SbomNamespace = "https://sbom.dendrodocs.dev";
39+
40+
enum BuildFlows
41+
{
42+
Local,
43+
PrRemote, // PR from fork (remote)
44+
PrLocal, // PR from same repo/branch
45+
Push, // Push to main
46+
Release // Tag/release
47+
}
48+
2849
// Entrypoint for Nuke CLI
2950
public static int Main() => Execute<Build>(x => x.Push);
3051

3152
GitHubActions GitHubActions => GitHubActions.Instance;
3253

3354
// Pipeline state
34-
string BranchSpec => GitHubActions?.Ref;
55+
string GitHubRef => GitHubActions?.Ref;
56+
string GitHubEventName => GitHubActions?.EventName;
57+
string GitHubRepository => GitHubActions?.Repository;
58+
string GitHubHeadRepository => Environment.GetEnvironmentVariable("GITHUB_HEAD_REPOSITORY");
3559
string BuildNumber => GitHubActions?.RunNumber.ToString();
3660

61+
BuildFlows BuildFlow => GetBuildFlow();
62+
3763
[Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
3864
readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;
3965

@@ -42,7 +68,7 @@ class Build : NukeBuild
4268
readonly string NuGetApiKey;
4369

4470
[Parameter]
45-
readonly string GitHubUser = GitHubActions.Instance?.RepositoryOwner ?? "DendroDocs";
71+
readonly string GitHubUser = GitHubActions.Instance?.RepositoryOwner ?? RepositoryOwner;
4672

4773
[Parameter]
4874
[Secret]
@@ -72,26 +98,28 @@ class Build : NukeBuild
7298
string SemVer;
7399

74100
bool IsPullRequest => GitHubActions?.IsPullRequest ?? false;
75-
bool IsTag => BranchSpec is not null && BranchSpec.Contains("refs/tags", StringComparison.OrdinalIgnoreCase);
76-
bool IsVersionTag => GitVersion?.SemVer is not null && Regex.IsMatch(GitVersion.SemVer, @"^\d+\.\d+\.\d+(-.*)?$");
101+
bool IsTag => GitHubRef is not null && GitHubRef.Contains("refs/tags", StringComparison.OrdinalIgnoreCase);
77102

78103
// Clean ensures output dirs are reset, for reproducible builds
79104
Target Clean => _ => _
80105
.Executes(() =>
81106
{
82107
ArtifactsDirectory.CreateOrCleanDirectory();
83108
TestResultsDirectory.CreateOrCleanDirectory();
109+
SbomDirectory.CreateOrCleanDirectory();
110+
TrivyCacheDirectory.CreateDirectory();
84111
});
85112

86113
// CI safety: only build from a clean git state (prevents accidental, local-only changes from leaking into artifacts)
87114
Target VerifyCleanGit => _ => _
88115
.OnlyWhenStatic(() => !IsLocalBuild)
89116
.Executes(() =>
90117
{
91-
var result = ProcessTasks.StartProcess("git", "status --porcelain");
92-
result.AssertZeroExitCode();
93-
var output = result.Output.Select(x => x.Text).ToList();
94-
if (output.Any())
118+
var output = Git("status --porcelain")
119+
.Select(x => x.Text)
120+
.ToList();
121+
122+
if (output.Count > 0)
95123
throw new Exception("Repository is not clean. Commit or stash changes before running CI.");
96124
});
97125

@@ -103,7 +131,7 @@ class Build : NukeBuild
103131

104132
if (IsPullRequest)
105133
{
106-
Information("Branch spec {BranchSpec} is a pull request. Adding build number {BuildNumber}", BranchSpec, BuildNumber);
134+
Information("Branch spec {BranchSpec} is a pull request. Adding build number {BuildNumber}", GitHubRef, BuildNumber);
107135

108136
SemVer = string.Join('.', GitVersion.SemVer.Split('.').Take(3).Union([BuildNumber]));
109137
}
@@ -155,11 +183,26 @@ class Build : NukeBuild
155183

156184
// Generate an SBOM for all artifacts using Microsoft's sbom-tool
157185
Target SbomDeliverable => _ => _
186+
// Only run SBOM creation for main branch and releases, not PRs
187+
.OnlyWhenDynamic(() => BuildFlow == BuildFlows.Push || BuildFlow == BuildFlows.Release)
158188
.DependsOn(Pack)
159189
.Executes(() =>
160190
{
161-
SbomDirectory.CreateOrCleanDirectory();
162-
Sbom($"generate -b \"{ArtifactsDirectory}\" -bc . -m \"{SbomDirectory}\" -pn DendroDocs.Shared -pv {SemVer} -nsb https://sbom.dendrodocs.dev -ps DendroDocs -li true -pm true");
191+
var sbomArgs = new[]
192+
{
193+
"generate",
194+
"-b", $"\"{ArtifactsDirectory}\"", // Base path for the build output
195+
"-bc", ".", // Build config root
196+
"-m", $"\"{SbomDirectory}\"", // Output SBOM manifest dir
197+
"-pn", ProjectName, // Package name",
198+
"-pv", SemVer,
199+
"-nsb", SbomNamespace,
200+
"-ps", RepositoryOwner,
201+
"-li", "true", // Enable license info
202+
"-pm", "true" // Enable package manifest
203+
};
204+
205+
Sbom(arguments: string.Join(" ", sbomArgs));
163206
});
164207

165208
// Run Trivy via Docker, scanning the *source tree* for vulnerabilities, secrets, and misconfigurations
@@ -199,6 +242,7 @@ class Build : NukeBuild
199242
"--disable-telemetry",
200243
"--no-progress",
201244
"--skip-dirs", ".nuke/temp",
245+
"--exit-code", "1",
202246
"/src"));
203247
});
204248

@@ -304,7 +348,7 @@ class Build : NukeBuild
304348
// Push to NuGet.org with precondition checks and integrity verification
305349
Target PushNuget => _ => _
306350
.DependsOn(Proof)
307-
.OnlyWhenDynamic(() => !IsLocalBuild && IsTag && IsVersionTag)
351+
.OnlyWhenDynamic(() => BuildFlow == BuildFlows.Release)
308352
.ProceedAfterFailure()
309353
.Executes(() =>
310354
{
@@ -322,53 +366,78 @@ class Build : NukeBuild
322366
// Push to GitHub Packages, after all proof steps and only from trusted context
323367
Target PushGithub => _ => _
324368
.DependsOn(Proof)
325-
.OnlyWhenDynamic(() => !IsLocalBuild && !IsTag && IsPullRequest)
326-
.OnlyWhenDynamic(() => GitHubUser == "dendrodocs")
369+
.OnlyWhenDynamic(() => BuildFlow == BuildFlows.Push)
370+
.OnlyWhenDynamic(() => GitHubUser == RepositoryOwner)
327371
.ProceedAfterFailure()
328372
.Executes(() =>
329373
{
330-
try
331-
{
332-
DotNetNuGetAddSource(_ => _
333-
.SetName($"GitHub - {GitHubUser}")
334-
.SetUsername(GitHubUser)
335-
.SetPassword(FeedGitHubToken)
336-
.EnableStorePasswordInClearText()
337-
.SetSource($"https://nuget.pkg.github.com/{GitHubUser}/index.json")
338-
);
339-
}
340-
catch
341-
{
342-
Information("Source already added");
343-
}
344-
345374
VerifyPackageHashes();
346375

347376
DotNetNuGetPush(_ => _
348377
.SetApiKey(FeedGitHubToken)
349378
.SetTargetPath(ArtifactsDirectory / "*.nupkg")
350379
.EnableSkipDuplicate()
351-
.SetSource($"GitHub - {GitHubUser}")
380+
.SetSource($"https://nuget.pkg.github.com/{RepositoryOwner}/index.json")
352381
.EnableNoSymbols()
353382
);
354383
});
355384

356-
// Always verify artifact hashes before publishing—no accidental or malicious tampering!
385+
BuildFlows GetBuildFlow()
386+
{
387+
if (IsLocalBuild)
388+
{
389+
return BuildFlows.Local;
390+
}
391+
392+
// PR event
393+
if (GitHubEventName?.StartsWith("pull_request") == true)
394+
{
395+
// Forked repo PRs (external contributors)
396+
if (!string.IsNullOrWhiteSpace(GitHubHeadRepository) && !string.Equals(GitHubHeadRepository, GitHubRepository, StringComparison.OrdinalIgnoreCase))
397+
{
398+
return BuildFlows.PrRemote;
399+
}
400+
401+
// In-repo branch PRs
402+
return BuildFlows.PrLocal;
403+
}
404+
405+
// Tag/release
406+
if (IsTag || GitHubEventName == "release")
407+
{
408+
return BuildFlows.Release;
409+
}
410+
411+
// Branch push
412+
if (GitHubRef?.StartsWith("refs/heads/") == true)
413+
{
414+
return BuildFlows.Push;
415+
}
416+
417+
return BuildFlows.Local;
418+
}
419+
420+
// Always verify artifact hashes before publishing—no accidental or malicious tampering
357421
void VerifyPackageHashes()
358422
{
359423
var packages = Directory.GetFiles(ArtifactsDirectory, "*.nupkg");
360424
foreach (var package in packages)
361425
{
362426
var hashFile = package + ".sha256";
363427

364-
if (!File.Exists(hashFile)) throw new Exception($"Missing SHA256 file for {package}");
428+
if (!File.Exists(hashFile))
429+
{
430+
throw new Exception($"Missing SHA256 file for {package}");
431+
}
365432

366433
var computedHash = SHA256.HashData(File.ReadAllBytes(package));
367434
var expectedHash = File.ReadAllText(hashFile).Trim();
368435
var actualHash = BitConverter.ToString(computedHash).Replace("-", string.Empty).ToLowerInvariant();
369436

370437
if (!string.Equals(expectedHash, actualHash, StringComparison.OrdinalIgnoreCase))
438+
{
371439
throw new Exception($"SHA256 mismatch for {package}: expected {expectedHash}, got {actualHash}");
440+
}
372441
}
373442
}
374443
}

0 commit comments

Comments
 (0)