Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.git
target
.github
30 changes: 30 additions & 0 deletions .github/composite/build-image/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Build client image
description: Builds the client image and pushes it to Docker Hub

inputs:
ENVIRONMENT:
description: "'staging' or 'production'"
required: true
SHA:
description: "Full commit SHA the image is built from"
required: true
PR_ID:
description: "Pull request to comment the pushed tags on"
required: false
default: ""
ARCH:
description: "Target architecture to build natively (amd64 or arm64). arm64 pushes to a separate -arm repository."
required: false
default: amd64

runs:
using: composite
steps:
- name: Build and push image
shell: bash
run: |
bun run .github/scripts/src/build-image \
--environment "${{ inputs.ENVIRONMENT }}" \
--sha "${{ inputs.SHA }}" \
--prId "${{ inputs.PR_ID }}" \
--arch "${{ inputs.ARCH }}"
9 changes: 9 additions & 0 deletions .github/composite/create-tag/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: Create new tag
description: Creates new tag and triggers continuous deployment.

runs:
using: composite
steps:
- name: Create new tag
shell: bash
run: bun run .github/scripts/src/create-tag
23 changes: 23 additions & 0 deletions .github/composite/deploy/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Deploy client
description: Updates the client image tag in k8s-manifests

inputs:
RELEASE_TAG:
description: "Deploy the version named by this tag, for example 1.2.3"
required: false
default: ""
SHA:
description: "Deploy the image built from this commit. Set exactly one of RELEASE_TAG or SHA."
required: false
default: ""
ENVIRONMENT:
description: "Environment to deploy to: staging or production"
required: false
default: "production"

runs:
using: composite
steps:
- name: Update k8s-manifests
shell: bash
run: bun run .github/scripts/src/deploy --environment "${{ inputs.ENVIRONMENT }}" --releaseTag "${{ inputs.RELEASE_TAG }}" --sha "${{ inputs.SHA }}"
9 changes: 9 additions & 0 deletions .github/composite/pretest/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: Pre-test
description: Run backend format, lint, and compile checks (no tests)

runs:
using: composite
steps:
- name: Run pre-test checks
shell: bash
run: bun run .github/scripts/src/pretest
21 changes: 21 additions & 0 deletions .github/composite/promote-image/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Promote client image to release tag
description: Re-tags the image built from the tagged commit with the release version

inputs:
RELEASE_TAG:
description: "For example, 1.2.3"
required: true
SHA:
description: "Full SHA of the tagged commit"
required: true
ARCH:
description: "Architecture to promote (amd64 or arm64). Must match the runner's architecture."
required: false
default: amd64

runs:
using: composite
steps:
- name: Promote image
shell: bash
run: bun run .github/scripts/src/promote-image --releaseTag "${{ inputs.RELEASE_TAG }}" --sha "${{ inputs.SHA }}" --arch "${{ inputs.ARCH }}"
9 changes: 9 additions & 0 deletions .github/composite/test/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: Test
description: Run the backend test suite

runs:
using: composite
steps:
- name: Run tests
shell: bash
run: bun run .github/scripts/src/test
14 changes: 14 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule: {interval: weekly}
- package-ecosystem: bun
directory: /.github/scripts
schedule: {interval: weekly}
- package-ecosystem: docker
directory: /
schedule: {interval: weekly}
- package-ecosystem: maven
directory: /
schedule: {interval: weekly}
1 change: 1 addition & 0 deletions .github/scripts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
1 change: 1 addition & 0 deletions .github/scripts/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bun.lock
4 changes: 4 additions & 0 deletions .github/scripts/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"experimentalTernaries": true,
"semi": true
}
1,056 changes: 1,056 additions & 0 deletions .github/scripts/bun.lock

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions .github/scripts/eslint.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import js from "@eslint/js";
import perfectionist from "eslint-plugin-perfectionist";
import globals from "globals";
import tseslint from "typescript-eslint";

export default tseslint.config(
{ ignores: ["dist"] },
{
plugins: {
perfectionist,
},
rules: {
"perfectionist/sort-imports": "error",
},
},
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ["**/*.{ts,tsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.node,
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
"@typescript-eslint/no-namespace": ["off"],
"@typescript-eslint/no-non-null-assertion": ["error"],
"@typescript-eslint/no-empty-object-type": ["off"],
"@typescript-eslint/no-floating-promises": ["error"],
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
},
},
{},
);
32 changes: 32 additions & 0 deletions .github/scripts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "scripts",
"version": "1.0.0",
"description": "@patina-network/hello-world-grpc-client-java CI Scripts",
"private": true,
"scripts": {
"typecheck": "tsc --noEmit",
"test": "bun run typecheck && bun run prettier && bun run lint",
"fix": "bun run eslint:fix && bun run prettier:fix",
"lint": "bun run eslint",
"eslint": "eslint .",
"eslint:fix": "eslint . --fix",
"prettier": "prettier --check .",
"prettier:fix": "prettier --write ."
},
"dependencies": {
"@tahminator/pipeline": "1.0.93",
"yargs": "^18.2.0"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/bun": "^1.4.2",
"@types/yargs": "^17.0.35",
"eslint": "^10.12.0",
"eslint-plugin-perfectionist": "^5.12.1",
"globals": "^17.13.0",
"jiti": "^2.7.0",
"prettier": "^3.9.9",
"typescript": "^5.9.3",
"typescript-eslint": "^8.71.1"
}
}
81 changes: 81 additions & 0 deletions .github/scripts/src/build-image/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {
DockerClient,
type Environment,
GitHubClient,
} from "@tahminator/pipeline";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";

import {
ARCHITECTURES,
dockerRepository,
GITHUB_OWNER,
GITHUB_REPOSITORY,
requiredEnv,
shortSha,
} from "../consts";

const { environment, sha, prId, arch } = await yargs(hideBin(process.argv))
.option("environment", {
choices: ["staging", "production"] satisfies Environment[],
describe: "staging images are tagged staging-<sha>; production are <sha>",
demandOption: true,
})
.option("sha", {
type: "string",
describe: "Full commit SHA the image is built from",
demandOption: true,
})
.option("prId", {
type: "string",
describe: "Pull request to comment the pushed tags on; empty skips it",
default: "",
})
.option("arch", {
choices: ARCHITECTURES,
describe:
"Target architecture, built natively on a matching runner. arm64 pushes to a separate -arm repository",
default: "amd64" as const,
})
.strict()
.parse();

async function main() {
const dockerHubUsername = requiredEnv("DOCKER_HUB_USERNAME");
const dockerHubPat = requiredEnv("DOCKER_HUB_PAT");

const repository = dockerRepository(arch);
const image = `${dockerHubUsername}/${repository}`;
const short = shortSha(sha);
const tags =
environment === "staging" ?
[`staging-${short}`, `sha-${sha}`]
: [short, `sha-${sha}`];

await using dockerClient = await DockerClient.create(
dockerHubUsername,
dockerHubPat,
);

await dockerClient.buildImage({
dockerRepository: repository,
dockerFileLocation: "Dockerfile",
tags,
platforms: [`linux/${arch}`],
});

if (prId) {
const ghClient = await GitHubClient.createWithDefaultCiToken();
await ghClient.sendPrMessage({
prId: Number(prId),
owner: GITHUB_OWNER,
repository: GITHUB_REPOSITORY,
message: `The java client image has been uploaded to https://hub.docker.com/r/${image}/tags under the following tags:

${tags.map((t) => `- \`${repository}:${t}\``).join("\n")}
`,
});
}
}

void main();
25 changes: 25 additions & 0 deletions .github/scripts/src/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export const GITHUB_OWNER = "Patina-Network";
export const GITHUB_REPOSITORY = "hello-world-grpc-client-java";
const DOCKER_REPOSITORY = "hello-world-client-java";

export const ARCHITECTURES = ["amd64", "arm64"] as const;
export type Architecture = (typeof ARCHITECTURES)[number];

export function dockerRepository(arch: Architecture = "amd64") {
return arch === "arm64" ? `${DOCKER_REPOSITORY}-arm` : DOCKER_REPOSITORY;
}

export function shortSha(sha: string) {
if (!/^[0-9a-f]{40}$/.test(sha)) {
throw new Error(`Expected a full commit SHA, got "${sha}"`);
}
return sha.slice(0, 7);
}

export function requiredEnv(name: string) {
const v = process.env[name];
if (!v) {
throw new Error(`Missing ${name} from env`);
}
return v;
}
36 changes: 36 additions & 0 deletions .github/scripts/src/create-tag/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
GitHubClient,
VersioningClient,
VersionUpdatingStrategy,
} from "@tahminator/pipeline";

import { requiredEnv } from "../consts";

async function main() {
const ghClient = await GitHubClient.createWithGithubAppToken({
appId: requiredEnv("_GITHUB_APP_APP_ID"),
installationId: requiredEnv("_GITHUB_APP_INSTALLATION_ID"),
privateKey: requiredEnv("_GITHUB_APP_PEM_CONTENT"),
});

const versioningClient = new VersioningClient(
ghClient,
VersionUpdatingStrategy.NONE,
);

await ghClient.createTag({
nextTag: await versioningClient.next(),
onPreTagCreate: async (tag) => {
await versioningClient.update(tag);
},
});
}

main()
.then(() => {
process.exit(0);
})
.catch((e) => {
console.error(e);
process.exit(1);
});
Loading
Loading