first try
This commit is contained in:
14
.editorconfig
Normal file
14
.editorconfig
Normal 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
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
github: [ nhedger ]
|
||||
16
.github/dependabot.yml
vendored
Normal file
16
.github/dependabot.yml
vendored
Normal 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
42
.github/workflows/integrate.yaml
vendored
Normal 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
34
.github/workflows/test.yaml
vendored
Normal 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
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
||||
25
LICENSE.md
Normal file
25
LICENSE.md
Normal 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
62
README.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Setup SOPS in GitHub Actions
|
||||
|
||||
[](https://github.com/marketplace/actions/setup-sops)
|
||||
[](https://github.com/nhedger/setup-sops/actions/workflows/test.yaml)
|
||||
[](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).
|
||||
25
action.yaml
25
action.yaml
@ -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
28
biome.json
Normal 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
34287
dist/index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
lefthook.yml
Normal file
5
lefthook.yml
Normal file
@ -0,0 +1,5 @@
|
||||
pre-commit:
|
||||
commands:
|
||||
package:
|
||||
glob: ./**/*.ts
|
||||
run: npm run package && git add dist/*
|
||||
33
package.json
Normal file
33
package.json
Normal 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
1951
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
src/age.ts
Normal file
27
src/age.ts
Normal 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
5
src/helpers.ts
Normal 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
23
src/index.ts
Normal 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
171
src/setup.ts
Normal 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
28
src/sops.ts
Normal 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
14
tsconfig.json
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user