first try

This commit is contained in:
2025-07-06 11:56:08 +02:00
parent 715cffde38
commit a4d88e5ea3
20 changed files with 36769 additions and 23 deletions

14
.editorconfig Normal file
View File

@ -0,0 +1,14 @@
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = tab
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
[*.{yml,yaml}]
indent_size = 2

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
github: [ nhedger ]

16
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,16 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

42
.github/workflows/integrate.yaml vendored Normal file
View File

@ -0,0 +1,42 @@
name: Integrate
on:
push:
branches: [ main ]
pull_request:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 16.x
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
run_install: true
- name: Build action
run: pnpm build
- name: Package action
run: pnpm package
coding-standards:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Biome
uses: biomejs/setup-biome@v2
- name: Run Biome
run: biome ci .

34
.github/workflows/test.yaml vendored Normal file
View File

@ -0,0 +1,34 @@
name: Test
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
jobs:
test:
name: Test
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
[
ubuntu-24.04,
ubuntu-24.04-arm,
macos-13,
macos-latest,
windows-11-arm,
windows-latest,
]
version: ["latest", "3.10.2"]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup SOPS
uses: ./
with:
version: ${{ matrix.version }}
- name: Test SOPS
run: sops --version

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

25
LICENSE.md Normal file
View File

@ -0,0 +1,25 @@
The MIT License (MIT)
=====================
Copyright © `2023` `Nicolas Hedger`
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the “Software”), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

62
README.md Normal file
View File

@ -0,0 +1,62 @@
# Setup SOPS in GitHub Actions
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/nhedger/setup-sops?label=latest&logo=github)](https://github.com/marketplace/actions/setup-sops)
[![Test](https://github.com/nhedger/setup-sops/actions/workflows/test.yaml/badge.svg)](https://github.com/nhedger/setup-sops/actions/workflows/test.yaml)
[![Integrate](https://github.com/nhedger/setup-sops/actions/workflows/integrate.yaml/badge.svg)](https://github.com/nhedger/setup-sops/actions/workflows/integrate.yaml)
**Setup SOPS** is a GitHub action that provides a cross-platform interface
for setting up [SOPS](https://github.com/getsops/sops) in GitHub
Actions runners.
## Inputs
The following inputs are supported.
```yaml
- name: Setup SOPS
uses: nhedger/setup-sops@v2
with:
# The version of SOPS to install.
# This input is optional and defaults to "latest".
# Example values: "3.7.3", "latest"
version: "latest"
# The GitHub token to use to authenticate GitHub API requests.
# This input is optional and defaults to the job's GitHub token.
# Example value: ${{ secrets.GITHUB_TOKEN }}
token: ${{ github.token }}
```
## Examples
### Basic example
Setup the latest version of SOPS.
```yaml
- name: Setup SOPS
uses: nhedger/setup-sops@v2
- name: Run SOPS
run: sops --version
```
### Specific version
Install version `3.7.3` of SOPS.
```yaml
- name: Setup SOPS
uses: nhedger/setup-sops@v2
with:
version: 3.7.3
- name: Run SOPS
run: sops --version
```
## License
The scripts and documentation in this project are licensed under
the [MIT License](LICENSE.md).

View File

@ -1,26 +1,5 @@
name: 'Setup AGE & SOPS'
description: 'Installs SOPS and AGE binaries'
runs:
using: 'composite'
steps:
- name: download
shell: bash
id: download
run: |
if ! [ -x "$(command -v curl)" ]; then
echo "I need curl to operate"
exit 1
fi
mkdir -p "$HOME/.bin"
if ! [ -x "$(command -v sops)" ]; then
curl https://github.com/getsops/sops/releases/download/v3.10.2/sops-v3.10.2.linux.amd64 -o "$HOME/.bin/sops"
else
echo "sops already present"
fi
if ! [ -x "$(command -v age)" ]; then
curl -L https://github.com/FiloSottile/age/releases/download/v1.2.1/age-v1.2.1-linux-amd64.tar.gz -o - | tar -xzO age/age > "$HOME/.bin/age"
else
echo "age already present"
fi
$HOME
echo "PATH=$HOME/.bin:$PATH" >> $GITHUB_ENV
using: 'node20'
main: 'dist/index.js'

28
biome.json Normal file
View File

@ -0,0 +1,28 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"files": {
"includes": ["**", "!**/build", "!**/dist", "!**/node_modules"]
},
"formatter": {
"indentWidth": 4,
"indentStyle": "tab",
"lineWidth": 120
},
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"linter": {
"rules": {
"style": {
"noParameterAssign": "error",
"useAsConstAssertion": "error",
"useDefaultParameterLast": "error",
"useEnumInitializers": "error",
"useSelfClosingElements": "error",
"useSingleVarDeclarator": "error",
"noUnusedTemplateLiteral": "error",
"useNumberNamespace": "error",
"noInferrableTypes": "error",
"noUselessElse": "error"
}
}
}
}

34287
dist/index.js vendored Normal file

File diff suppressed because one or more lines are too long

5
lefthook.yml Normal file
View File

@ -0,0 +1,5 @@
pre-commit:
commands:
package:
glob: ./**/*.ts
run: npm run package && git add dist/*

33
package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "setup-age-sops",
"description": "Setup AGE and SOPS in Gitea Actions",
"scripts": {
"prepackage": "npm run build",
"package": "ncc build src/index.ts -o dist"
},
"keywords": [
"age",
"sops",
"github-action"
],
"author": {
"name": "Nicolas Hedger",
"email": "nicolas@hedger.ch"
},
"license": "MIT",
"dependencies": {
"@actions/core": "^1.11.1",
"@actions/tool-cache": "^2.0.2",
"ts-dedent": "^2.2.0"
},
"devDependencies": {
"@hedger/prettier-config": "^1.2.0",
"@octokit/auth-action": "^6.0.1",
"@octokit/request-error": "^7.0.0",
"@octokit/rest": "^22.0.0",
"@octokit/types": "^14.1.0",
"@types/node": "^18.19.31",
"@vercel/ncc": "^0.38.3"
},
"packageManager": "pnpm@8.3.1"
}

1951
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

27
src/age.ts Normal file
View File

@ -0,0 +1,27 @@
import {defaultOptions, setup, SetupOptions, ToolDownloadInstruction} from "./setup";
export const age = async (config: Partial<SetupOptions>) => {
const options: SetupOptions = { ...defaultOptions, ...config };
const mapping: Map<string, string> = new Map([
["linux.x64", "-linux-amd64.tar.gz"],
["linux.arm64", "-linux-arm64.tar.gz"],
["darwin.x64", "-darwin-amd64.tar.gz"],
["darwin.arm64", "-darwin-arm64.tar.gz"],
["win32.x64", "-windows-amd64.zip"],
]);
const postfix = mapping.get(`${options.platform}.${options.arch}`)
const instruction: ToolDownloadInstruction = {
repo: {
owner: "FiloSottile",
name: "age",
},
artifactSelector: (name =>
(postfix ?? "").length > 0 && name.endsWith(postfix ?? "") &&
name.startsWith("age")
),
executablePathInArchive: "age",
commandName: "age"
}
return setup(instruction, options);
};

5
src/helpers.ts Normal file
View File

@ -0,0 +1,5 @@
import { getInput as coreGetInput } from "@actions/core";
export const getInput = (name: string): string | undefined => {
return coreGetInput(name) === "" ? undefined : coreGetInput(name);
};

23
src/index.ts Normal file
View File

@ -0,0 +1,23 @@
import { getInput } from "@actions/core";
import { createActionAuth } from "@octokit/auth-action";
import { Octokit } from "@octokit/rest";
import { sops } from "./sops";
import { age } from "./age";
(async () => {
const octokit = new Octokit({
auth: (await createActionAuth()()).token,
});
await sops({
version: getInput("version"),
platform: process.platform as "linux" | "darwin" | "win32",
arch: process.arch as "x64" | "arm64",
octokit: octokit,
});
await age({
version: getInput("version"),
platform: process.platform as "linux" | "darwin" | "win32",
arch: process.arch as "x64" | "arm64",
octokit: octokit,
});
})();

171
src/setup.ts Normal file
View File

@ -0,0 +1,171 @@
import { chmod, symlink } from "node:fs/promises";
import { dirname, basename, join } from "node:path";
import { addPath, setFailed } from "@actions/core";
import {cacheDir, downloadTool, extractTar} from "@actions/tool-cache";
import { RequestError } from "@octokit/request-error";
import { Octokit } from "@octokit/rest";
/**
* SOPS Setup Options
*/
export interface SetupOptions {
/**
* Version of SOPS to download
*/
version: string;
/**
* Operating system to download the CLI for
*/
platform: "linux" | "darwin" | "win32";
/**
* Architecture
*/
arch?: "x64" | "arm64";
/**
* Octokit instance to use for API calls
*/
octokit: Octokit;
}
export const defaultOptions: SetupOptions = {
version: "latest",
platform: process.platform as "linux" | "darwin" | "win32",
arch: process.arch as "x64" | "arm64",
octokit: new Octokit(),
};
export interface Repo {
owner: string;
name: string;
}
export type ArtifactSelector = (name: string) => boolean;
export interface ToolDownloadInstruction {
repo: Repo;
artifactSelector: ArtifactSelector;
executablePathInArchive: string|null;
commandName: string;
}
export const setup = async (instruction: ToolDownloadInstruction, options: SetupOptions) => {
try {
// Download SOPS
const downloadedArtifact = await download(instruction.repo, instruction.artifactSelector, options);
const executablePath = await extract(downloadedArtifact, instruction.executablePathInArchive ?? instruction.commandName, options);
// Install SOPS
await install(executablePath, instruction.commandName);
} catch (error: unknown) {
if (error instanceof Error) {
console.log(error.message);
setFailed(error.message);
}
}
};
/**
* Downloads SOPS
*/
const download = async (repo: Repo, mapping: ArtifactSelector, options: SetupOptions): Promise<string> => {
try {
const releaseId = await findRelease(repo, options);
const assetURL = await findAsset(repo, releaseId, options, mapping);
return await downloadTool(assetURL);
} catch (error) {
if (error instanceof RequestError) {
const requestError = error as RequestError;
if (requestError.status === 403 && requestError.response?.headers["x-ratelimit-remaining"] === "0") {
throw new Error(`
You have exceeded the GitHub API rate limit.
Please try again in ${requestError.response?.headers["x-ratelimit-reset"]} seconds.
If you have not already done so, you can try authenticating calls to the GitHub API
by setting the \`GITHUB_TOKEN\` environment variable.
`);
}
}
throw error;
}
};
const extract = async (downloadPath: string, pathInArchive: string, options: SetupOptions): Promise<string> => {
try {
if (downloadPath.toLowerCase().endsWith(".tar.gz") || downloadPath.toLowerCase().endsWith(".tgz")) {
const extractPath = await extractTar(downloadPath)
return join(extractPath, pathInArchive);
} else {
return downloadPath
}
} catch (error) {
throw error;
}
};
/**
* Finds the release for the given version
*/
const findRelease = async (repo: Repo, options: SetupOptions) => {
try {
if (options.version === "latest") {
return (
await options.octokit.repos.getLatestRelease({
owner: repo.owner,
repo: repo.name,
})
).data.id;
}
return (
await options.octokit.repos.getReleaseByTag({
owner: repo.owner,
repo: repo.name,
tag: `v${options.version}`,
})
).data.id;
} catch (error) {
if (error instanceof RequestError) {
const requestError = error as RequestError;
if (requestError.status === 404) {
throw new Error(`Version ${options.version} of SOPS does not exist.`);
}
throw error;
}
throw error;
}
};
/**
* Finds the asset for the given release ID and options
*/
const findAsset = async (repo: Repo, releaseId: number, options: SetupOptions, pattern: ArtifactSelector) => {
const assets = await options.octokit.repos.listReleaseAssets({
owner: repo.owner,
repo: repo.name,
release_id: releaseId,
});
const asset = assets.data.find((asset) => pattern(asset.name));
if (!asset) {
throw new Error(`Could not find a SOPS release for ${options.platform}.${options.arch} for the given version.`);
}
return asset.browser_download_url;
};
/**
* Installs the downloaded SOPS binary
*/
const install = async (executablePath: string, commandName: string) => {
await chmod(executablePath, 0o755);
if (basename(executablePath) != commandName) {
// Symlink the binary to sops
await symlink(executablePath, join(dirname(executablePath), commandName));
// Make binary executable
await chmod(join(dirname(executablePath), commandName), 0o755);
}
// Add the CLI binary to the PATH
addPath(dirname(executablePath));
};

28
src/sops.ts Normal file
View File

@ -0,0 +1,28 @@
import {ArtifactSelector, defaultOptions, Repo, setup, SetupOptions, ToolDownloadInstruction} from "./setup";
export const sops = async (config: Partial<SetupOptions>) => {
const options: SetupOptions = { ...defaultOptions, ...config };
const mapping: Map<string, string> = new Map([
["linux.x64", ".linux.amd64"],
["linux.arm64", ".linux.arm64"],
["darwin.x64", ".darwin.amd64"],
["darwin.arm64", ".darwin.arm64"],
["win32.x64", ".amd64.exe"],
["win32.arm64", ".arm64.exe"],
]);
const postfix = mapping.get(`${options.platform}.${options.arch}`)
const instruction: ToolDownloadInstruction = {
repo: {
owner: "getsops",
name: "sops",
},
artifactSelector: (name =>
name.endsWith(postfix ?? "") &&
name.startsWith("sops")
),
executablePathInArchive: "",
commandName: "sops"
}
return setup(instruction, options);
};

14
tsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es2021",
"module": "esnext",
"lib": ["esnext"],
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"strictNullChecks": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"skipDefaultLibCheck": true
}
}