Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Post run diagnostics #39

Merged
merged 14 commits into from
Oct 4, 2023
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use flake
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,6 @@ Thumbs.db

# Ignore built ts files
__tests__/runner/*
lib/**/*
lib/**/*

.direnv
1 change: 1 addition & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,4 @@ inputs:
runs:
using: "node16"
main: 'dist/index.js'
post: 'dist/index.js'
7,393 changes: 7,220 additions & 173 deletions dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/licenses.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ node-fetch
MIT
The MIT License (MIT)

Copyright (c) 2016 - 2020 Node Fetch Team
Copyright (c) 2016 David Frank
grahamc marked this conversation as resolved.
Show resolved Hide resolved

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
38 changes: 38 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# This flake was initially generated by fh, the CLI for FlakeHub (version 0.1.5)
{
description = "Development environment for the Nix Installer action for GitHub.";

inputs = {
flake-schemas.url = "https://flakehub.com/f/DeterminateSystems/flake-schemas/*.tar.gz";
nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/*.tar.gz";
};

outputs = { self, flake-schemas, nixpkgs }:
let
supportedSystems = [ "x86_64-linux" "aarch64-darwin" ];
forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems (system: f {
pkgs = import nixpkgs { inherit system; };
});
in
{
schemas = flake-schemas.schemas;

devShells = forEachSupportedSystem ({ pkgs }: {
default = pkgs.mkShell {
packages = with pkgs; [
nodejs-18_x
nixpkgs-fmt
];
};
});
};
}
118 changes: 109 additions & 9 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as actions_core from "@actions/core";
import * as github from "@actions/github";
import { mkdtemp, chmod, access } from "node:fs/promises";
import { spawn } from "node:child_process";
import { randomUUID } from "node:crypto";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { pipeline } from "node:stream";
Expand Down Expand Up @@ -40,6 +42,12 @@ class NixInstallerAction {
trust_runner_user: boolean | null;
nix_installer_url: URL;

// Connects the installation diagnostic report to the post-run diagnostic report.
// This is for monitoring the real impact of Nix updates, to avoid breaking large
// swaths of users at once with botched Nix releases. For example:
// https://github.com/NixOS/nix/issues/9052.
correlation: string | undefined;

constructor() {
this.platform = get_nix_platform();
this.nix_package_url = action_input_string_or_null("nix-package-url");
Expand Down Expand Up @@ -78,13 +86,18 @@ class NixInstallerAction {
"diagnostic-endpoint",
);
this.trust_runner_user = action_input_bool("trust-runner-user");
this.nix_installer_url = resolve_nix_installer_url(this.platform);
this.correlation = process.env["STATE_correlation"];
this.nix_installer_url = resolve_nix_installer_url(
this.platform,
this.correlation,
);
}

private executionEnvironment(): ExecuteEnvironment {
const execution_env: ExecuteEnvironment = {};

execution_env.NIX_INSTALLER_NO_CONFIRM = "true";
execution_env.NIX_INSTALLER_DIAGNOSTIC_ATTRIBUTION = this.correlation;

if (this.backtrace !== null) {
execution_env.RUST_BACKTRACE = this.backtrace;
Expand Down Expand Up @@ -398,6 +411,78 @@ class NixInstallerAction {
return local_path;
}
}

async report_overall(): Promise<void> {
if (this.diagnostic_endpoint == null) {
return;
}

try {
await fetch(this.diagnostic_endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"post-github-workflow-run-report": true,
correlation: this.correlation,
conclusion: await this.get_workflow_conclusion(),
}),
});
} catch (error) {
actions_core.debug(
`Error submitting post-run diagnostics report: ${error}`,
);
}
}

private async get_workflow_conclusion(): Promise<
undefined | "success" | "failure" | "cancelled" | "unavailable" | "no-jobs"
> {
if (this.github_token == null) {
return undefined;
}

try {
const octokit = github.getOctokit(this.github_token);
const jobs = await octokit.paginate(
octokit.rest.actions.listJobsForWorkflowRun,
{
owner: github.context.repo.owner,
repo: github.context.repo.repo,
run_id: github.context.runId,
},
);

actions_core.debug(`awaited jobs: ${jobs}`);
const job = jobs
.filter((candidate) => candidate.name === github.context.job)
.at(0);
if (job === undefined) {
return "no-jobs";
}

const outcomes = (job.steps || []).map((j) => j.conclusion || "unknown");

// Possible values: success, failure, cancelled, or skipped
// from: https://docs.github.com/en/actions/learn-github-actions/contexts

if (outcomes.includes("failure")) {
// Any failures fails the job
return "failure";
}
if (outcomes.includes("cancelled")) {
// Any cancellations cancels the job
return "cancelled";
}

// Assume success if no jobs failed or were canceled
return "success";
} catch (error) {
actions_core.debug(`Error determining final disposition: ${error}`);
return "unavailable";
}
}
}

type ExecuteEnvironment = {
Expand All @@ -413,6 +498,7 @@ type ExecuteEnvironment = {
NIX_INSTALLER_PROXY?: string;
NIX_INSTALLER_SSL_CERT_FILE?: string;
NIX_INSTALLER_DIAGNOSTIC_ENDPOINT?: string;
NIX_INSTALLER_DIAGNOSTIC_ATTRIBUTION?: string;
NIX_INSTALLER_ENCRYPT?: string;
NIX_INSTALLER_CASE_SENSITIVE?: string;
NIX_INSTALLER_VOLUME_LABEL?: string;
Expand Down Expand Up @@ -456,7 +542,10 @@ function get_default_planner(): string {
}
}

function resolve_nix_installer_url(platform: string): URL {
function resolve_nix_installer_url(
platform: string,
correlation?: string,
): URL {
// Only one of these are allowed.
const nix_installer_branch = action_input_string_or_null(
"nix-installer-branch",
Expand All @@ -467,36 +556,36 @@ function resolve_nix_installer_url(platform: string): URL {
);
const nix_installer_tag = action_input_string_or_null("nix-installer-tag");
const nix_installer_url = action_input_string_or_null("nix-installer-url");

const url_suffix = `ci=github&correlation=${correlation}`;
let resolved_nix_installer_url = null;
let num_set = 0;

if (nix_installer_branch !== null) {
num_set += 1;
resolved_nix_installer_url = new URL(
`https://install.determinate.systems/nix/branch/${nix_installer_branch}/nix-installer-${platform}?ci=github`,
`https://install.determinate.systems/nix/branch/${nix_installer_branch}/nix-installer-${platform}?${url_suffix}`,
);
} else if (nix_installer_pr !== null) {
num_set += 1;
resolved_nix_installer_url = new URL(
`https://install.determinate.systems/nix/pr/${nix_installer_pr}/nix-installer-${platform}?ci=github`,
`https://install.determinate.systems/nix/pr/${nix_installer_pr}/nix-installer-${platform}?${url_suffix}`,
);
} else if (nix_installer_revision !== null) {
num_set += 1;
resolved_nix_installer_url = new URL(
`https://install.determinate.systems/nix/rev/${nix_installer_revision}/nix-installer-${platform}?ci=github`,
`https://install.determinate.systems/nix/rev/${nix_installer_revision}/nix-installer-${platform}?${url_suffix}`,
);
} else if (nix_installer_tag !== null) {
num_set += 1;
resolved_nix_installer_url = new URL(
`https://install.determinate.systems/nix/tag/${nix_installer_tag}/nix-installer-${platform}?ci=github`,
`https://install.determinate.systems/nix/tag/${nix_installer_tag}/nix-installer-${platform}?${url_suffix}`,
);
} else if (nix_installer_url !== null) {
num_set += 1;
resolved_nix_installer_url = new URL(nix_installer_url);
} else {
resolved_nix_installer_url = new URL(
`https://install.determinate.systems/nix/nix-installer-${platform}?ci=github`,
`https://install.determinate.systems/nix/nix-installer-${platform}?${url_suffix}`,
);
}

Expand Down Expand Up @@ -541,9 +630,20 @@ function action_input_bool(name: string): boolean {

async function main(): Promise<void> {
try {
if (!process.env["STATE_correlation"]) {
const correlation = `GH-${randomUUID()}`;
actions_core.saveState("correlation", correlation);
process.env["STATE_correlation"] = correlation;
}
const installer = new NixInstallerAction();

await installer.install();
const isPost = !!process.env["STATE_isPost"];
if (!isPost) {
actions_core.saveState("isPost", "true");
await installer.install();
} else {
installer.report_overall();
}
} catch (error) {
if (error instanceof Error) actions_core.setFailed(error);
}
Expand Down