Skip to content

Commit

Permalink
ci: cd better (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
Alxandr authored Oct 10, 2024
1 parent ef80d98 commit 447247a
Show file tree
Hide file tree
Showing 23 changed files with 303 additions and 107 deletions.
25 changes: 25 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# top-most EditorConfig file
root = true

# For all files
[*]
insert_final_newline = true
tab_width = 2

[*.{props,targets,csproj}]
tab_width = 4

[*.cs]
tab_width = 4

# VSTHRD200: Use "Async" suffix for async methods
dotnet_diagnostic.VSTHRD200.severity = none

# SA1001: Commas should be spaced correctly
dotnet_diagnostic.SA1001.severity = none

# FAA0002: Replace Xunit assertion with Fluent Assertions equivalent
dotnet_diagnostic.FAA0002.severity = none

# CA1822: Mark members as static
dotnet_diagnostic.CA1822.severity = none
111 changes: 96 additions & 15 deletions .github/scripts/_meta.mts
Original file line number Diff line number Diff line change
@@ -1,11 +1,43 @@
import { globby } from "globby";
import path from "node:path";
import fs from "node:fs";
import fs from "node:fs/promises";
import { z } from "zod";
import { fromError } from "zod-validation-error";
import { $, within } from "zx";

let _queue: Promise<void> = Promise.resolve();
const enqueue = <T extends unknown>(fn: () => Promise<T>): Promise<T> => {
var task = _queue.then(() => within(fn));
_queue = task.then(
(_) => {},
(_) => {}
);
return task;
};

const configSchema = z.object({
name: z.string().optional(),
shortName: z.string().optional(),
image: z
.object({
name: z.string().optional(),
type: z.enum(["dotnet", "docker"]).default("dotnet"),
source: z.string().optional(),
})
.refine((v) => {
if (v.type === "docker" && !v.name) {
return { message: "Image name is required for docker images" };
}
})
.optional(),
});

export type VerticalType = "app" | "lib" | "pkg";

export type ImageInfo = {
readonly name: string;
readonly type: "dotnet" | "docker";
readonly source: string;
};

export type Vertical = {
Expand All @@ -15,7 +47,6 @@ export type Vertical = {
readonly path: string;
readonly relPath: string;
readonly image?: ImageInfo;
readonly rawConfig: Readonly<Record<string, unknown>>;
};

const vertialDirs = {
Expand All @@ -26,33 +57,74 @@ const vertialDirs = {

const last = (arr: string[]) => arr[arr.length - 1];

const readVertical = (type: VerticalType, dirPath: string): Vertical => {
const readVertical = async (
type: VerticalType,
dirPath: string
): Promise<Vertical> => {
const verticalPath = path.resolve(dirPath);
const dirName = path.basename(verticalPath);
const configPath = path.resolve(verticalPath, "conf.json");

let config: Readonly<Record<string, unknown>> = {};
let parsed: any = {};
try {
const json = fs.readFileSync(configPath, { encoding: "utf-8" });
config = JSON.parse(json);
const json = await fs.readFile(configPath, { encoding: "utf-8" });
const parsed = JSON.parse(json);
} catch (e) {}

const result = await configSchema.safeParseAsync(parsed);
if (!result.success) {
const error = fromError(result.error);
console.error(`Error parsing ${configPath}: ${error.toString()}`);
throw error;
}

const config = result.data;

let name = dirName;
if (typeof config.name === "string" && config.name) {
if (config.name) {
name = config.name;
}

let shortName = last(name.split("."));
if (typeof config.shortName === "string" && config.shortName) {
if (config.shortName) {
shortName = config.shortName;
}

let image: ImageInfo | undefined;
if ("image" in config) {
// TODO: validate?
image = {
name: (config.image as any).name,
};
if (type === "app") {
$.cwd = verticalPath;
const confImage = config.image ?? { type: "dotnet" };

switch (confImage.type) {
case "dotnet": {
if (!confImage.source) {
confImage.source = `src/${name}`;
}

if (!confImage.name) {
confImage.name = await enqueue(async () => {
$.cwd = verticalPath;
const result =
await $`dotnet msbuild ${confImage.source} -getProperty:ContainerName`;
return result.stdout.trim();
});
}
break;
}

case "docker": {
if (!confImage.source) {
confImage.source = "Dockerfile";
}
break;
}

default: {
throw new Error(`Unsupported image type: ${confImage.type}`);
}
}

image = confImage as ImageInfo;
}

return {
Expand All @@ -62,17 +134,26 @@ const readVertical = (type: VerticalType, dirPath: string): Vertical => {
path: verticalPath,
relPath: dirPath,
image,
rawConfig: config,
};
};

const apps = await globby(`${vertialDirs.app}/*`, { onlyDirectories: true });
const libs = await globby(`${vertialDirs.lib}/*`, { onlyDirectories: true });
const pkgs = await globby(`${vertialDirs.pkg}/*`, { onlyDirectories: true });
const verticals = [
const promises = [
...apps.map((app) => readVertical("app", app)),
...libs.map((lib) => readVertical("lib", lib)),
...pkgs.map((pkg) => readVertical("pkg", pkg)),
];
const verticals = await Promise.all(promises);

export const getApp = (name: string) => {
const app = verticals.find((v) => v.name === name);
if (!app) {
throw new Error(`App not found: ${name}`);
}

return app;
};

export { verticals };
38 changes: 38 additions & 0 deletions .github/scripts/build-push-app.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { $ } from "zx";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { getApp } from "./_meta.mts";
import * as actions from "@actions/core";

const argv = yargs(hideBin(process.argv))
.positional("name", {
type: "string",
required: true,
})
.option("tag", {
type: "string",
})
.parse();

const app = getApp(argv.name);
const tag =
argv.tag || (process.env.GITHUB_SHA ?? "").substring(0, 7) || "latest";

if (!app.image) {
throw new Error(`No image config found for ${app.name}`);
}

const imgCfg = app.image;
if (imgCfg.type !== "dotnet") {
throw new Error(`Unsupported image type (not implemented): ${imgCfg.type}`);
}

const source = imgCfg.source.replaceAll("\\", "/");
$.cwd = app.path;
await $`dotnet publish ${source} --os linux-musl -t:PublishContainer -p:ContainerImageTag=${tag} -bl`.verbose(
true
);

const fullImage = `ghcr.io/altinn/altinn-authorization-tmp/${imgCfg.name}:${tag}`;
actions.setOutput("image", fullImage);
actions.setOutput("tag", tag);
3 changes: 3 additions & 0 deletions .github/scripts/get-metadata.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { verticals } from "./_meta.mts";

console.log(verticals);
28 changes: 0 additions & 28 deletions .github/scripts/metadata.py

This file was deleted.

2 changes: 2 additions & 0 deletions .github/scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"tsx": "^4.7.1",
"typescript": "^5.4.3",
"yargs": "^17.7.2",
"zod": "^3.23.8",
"zod-validation-error": "^3.4.0",
"zx": "^8.0.0"
}
}
21 changes: 21 additions & 0 deletions .github/scripts/pnpm-lock.yaml

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

53 changes: 33 additions & 20 deletions .github/workflows/_deploy-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,16 @@ name: "Template: Deploy app"
on:
workflow_call:
inputs:
repository:
description: Docker image repository (part of the registry)
name:
required: true
description: Name of the app
type: string
default: altinn/altinn-authorization-tmp

path:
required: true
description: Path to the app
type: string

imageName:
required: true
description: Name of the image
type: string

jobs:
build-push:
name: Build and Push
Expand All @@ -31,10 +26,16 @@ jobs:
- uses: actions/checkout@v4
name: Checkout repository

- name: Set Short SHA
run: |
short_sha=$(git rev-parse --short ${{ github.sha }})
echo "short_sha=$short_sha" >> $GITHUB_ENV
- name: Install .NET
uses: actions/setup-dotnet@v4

- uses: pnpm/action-setup@v3
with:
version: 9
run_install: |
- cwd: .github/scripts
args: [--frozen-lockfile]
- args: [--global, tsx]
- uses: docker/login-action@v3
name: Login to ghcr
Expand All @@ -43,11 +44,23 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- uses: docker/build-push-action@v6
id: build
name: Build and Push
with:
file: ${{ inputs.path }}/Dockerfile
context: ./
push: true
tags: ghrc.io/${{ inputs.repository }}/${{ inputs.imageName }}:${{ env.short_sha }}
# - uses: docker/build-push-action@v6
# id: build
# name: Build and Push
# with:
# file: ${{ inputs.path }}/Dockerfile
# context: ./
# push: true
# tags: ghrc.io/${{ inputs.repository }}/${{ inputs.imageName }}:${{ env.short_sha }}

- name: Build and Push ${{ inputs.name }}
id: img
run: tsx ./.github/scripts/build-push-app.mts "${{ inputs.name }}"

- name: Print outputs
env:
IMAGE: ${{ steps.img.outputs.image }}
TAG: ${{ steps.img.outputs.tag }}
run: |
echo "Image: ${IMAGE}"
echo "Tag: ${TAG}"
Loading

0 comments on commit 447247a

Please sign in to comment.