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
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,24 @@ steps:
uses: actions/checkout@v4
```

### A single secret

Set `secret-name` to fetch only one secret instead of every secret in `secret-path`. The secret is exported the same way as above, so it is available as an environment variable (or in the exported file) under its own name.

```yaml
- uses: Infisical/secrets-action@v1.0.9
with:
method: "oidc"
identity-id: "24be0d94-b43a-41c4-812c-1e8654d9ce1e"
domain: "https://app.infisical.com" # Update to the instance URL when using EU (https://eu.infisical.com), a dedicated instance, or a self-hosted instance
env-slug: "dev"
project-slug: "cli-integration-tests-9-edj"
secret-path: "/my-secret-path"
secret-name: "DATABASE_URL"
```

The action fails with an error if the secret does not exist at the given path.

## Inputs

### `method`
Expand Down Expand Up @@ -132,6 +150,10 @@ steps:

**Optional**. Source secret path. Defaults to `/`. Example: `/my-secret-path`.

### `secret-name`

**Optional**. The name of a single secret to fetch from `secret-path`. When omitted, every secret in `secret-path` is fetched. When set, `recursive` is ignored.

### `include-imports`

**Optional**. If set to `true`, it will include imported secrets. Defaults to `true`
Expand Down
3 changes: 3 additions & 0 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ inputs:
description: "Source path, example: `/my-secret-path`"
required: false
default: "/"
secret-name:
description: "If set, only the secret with this name will be fetched from `secret-path`, instead of every secret in the path"
required: false
include-imports:
description: "If set to `true`, it will include imported secrets"
required: false
Expand Down
61 changes: 52 additions & 9 deletions dist/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -52958,6 +52958,35 @@ const getRawSecrets = (_a) => __awaiter(void 0, [_a], void 0, function* ({ envSl
throw err;
}
});
const getRawSecret = (_a) => __awaiter(void 0, [_a], void 0, function* ({ envSlug, infisicalToken, projectSlug, secretPath, secretName, shouldIncludeImports, axiosInstance }) {
var _b;
try {
const response = yield axiosInstance({
method: "get",
url: `/api/v3/secrets/raw/${encodeURIComponent(secretName)}`,
headers: {
Authorization: `Bearer ${infisicalToken}`
},
params: {
secretPath,
environment: envSlug,
include_imports: shouldIncludeImports,
workspaceSlug: projectSlug,
expandSecretReferences: true
}
});
const secret = response.data.secret;
// return the same key/value shape as getRawSecrets so both paths export identically
return { [secret.secretKey]: secret.secretValue };
}
catch (err) {
if (err instanceof AxiosError && ((_b = err.response) === null || _b === void 0 ? void 0 : _b.status) === 404) {
throw new Error(`Secret "${secretName}" was not found at path "${secretPath}" in environment "${envSlug}" of project "${projectSlug}"`);
}
handleError(err);
throw err;
}
});

function parseHeadersInput(inputKey) {
const rawHeadersString = core.getInput(inputKey) || "";
Expand Down Expand Up @@ -52990,6 +53019,7 @@ const main = () => __awaiter(void 0, void 0, void 0, function* () {
const envSlug = core.getInput("env-slug");
const projectSlug = core.getInput("project-slug");
const secretPath = core.getInput("secret-path");
const secretName = core.getInput("secret-name");
const exportType = core.getInput("export-type");
const fileOutputPath = core.getInput("file-output-path");
const shouldIncludeImports = core.getBooleanInput("include-imports");
Expand Down Expand Up @@ -53034,16 +53064,29 @@ const main = () => __awaiter(void 0, void 0, void 0, function* () {
default:
throw new Error(`Invalid authentication method: ${method}`);
}
if (secretName && shouldRecurse) {
core.warning("The `recursive` input is ignored when `secret-name` is set");
}
// get secrets from Infisical using input params
const keyValueSecrets = yield getRawSecrets({
axiosInstance,
envSlug,
infisicalToken,
projectSlug,
secretPath,
shouldIncludeImports,
shouldRecurse
});
const keyValueSecrets = secretName
? yield getRawSecret({
axiosInstance,
envSlug,
infisicalToken,
projectSlug,
secretPath,
secretName,
shouldIncludeImports
})
: yield getRawSecrets({
axiosInstance,
envSlug,
infisicalToken,
projectSlug,
secretPath,
shouldIncludeImports,
shouldRecurse
});
core.debug(`Exporting the following envs", ${JSON.stringify(Object.keys(keyValueSecrets))}`);
// export fetched secrets
if (exportType === "env") {
Expand Down
2 changes: 1 addition & 1 deletion dist/index.cjs.map

Large diffs are not rendered by default.

35 changes: 25 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import core from "@actions/core";
import { UALogin, getRawSecrets, oidcLogin, awsIamLogin, createAxiosInstance } from "./infisical";
import { UALogin, getRawSecret, getRawSecrets, oidcLogin, awsIamLogin, createAxiosInstance } from "./infisical";
import fs from "fs/promises";
import { AuthMethod } from "./constants";

Expand Down Expand Up @@ -40,6 +40,7 @@ const main = async () => {
const envSlug = core.getInput("env-slug");
const projectSlug = core.getInput("project-slug");
const secretPath = core.getInput("secret-path");
const secretName = core.getInput("secret-name");
const exportType = core.getInput("export-type");
const fileOutputPath = core.getInput("file-output-path");
const shouldIncludeImports = core.getBooleanInput("include-imports");
Expand Down Expand Up @@ -88,16 +89,30 @@ const main = async () => {
throw new Error(`Invalid authentication method: ${method}`);
}

if (secretName && shouldRecurse) {
core.warning("The `recursive` input is ignored when `secret-name` is set");
}

// get secrets from Infisical using input params
const keyValueSecrets = await getRawSecrets({
axiosInstance,
envSlug,
infisicalToken,
projectSlug,
secretPath,
shouldIncludeImports,
shouldRecurse
});
const keyValueSecrets = secretName
? await getRawSecret({
axiosInstance,
envSlug,
infisicalToken,
projectSlug,
secretPath,
secretName,
shouldIncludeImports
})
: await getRawSecrets({
axiosInstance,
envSlug,
infisicalToken,
projectSlug,
secretPath,
shouldIncludeImports,
shouldRecurse
});

core.debug(`Exporting the following envs", ${JSON.stringify(Object.keys(keyValueSecrets))}`);

Expand Down
53 changes: 53 additions & 0 deletions src/infisical.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,56 @@ export const getRawSecrets = async ({
throw err;
}
};

export const getRawSecret = async ({
envSlug,
infisicalToken,
projectSlug,
secretPath,
secretName,
shouldIncludeImports,
axiosInstance
}: {
envSlug: string;
infisicalToken: string;
projectSlug: string;
secretPath: string;
secretName: string;
shouldIncludeImports: boolean;
axiosInstance: AxiosInstance;
}) => {
try {
const response = await axiosInstance<{
secret: {
secretKey: string;
secretValue: string;
};
}>({
method: "get",
url: `/api/v3/secrets/raw/${encodeURIComponent(secretName)}`,
headers: {
Authorization: `Bearer ${infisicalToken}`
},
params: {
secretPath,
environment: envSlug,
include_imports: shouldIncludeImports,
workspaceSlug: projectSlug,
expandSecretReferences: true
}
});

const secret = response.data.secret;

// return the same key/value shape as getRawSecrets so both paths export identically
return { [secret.secretKey]: secret.secretValue };
} catch (err) {
if (err instanceof AxiosError && err.response?.status === 404) {
throw new Error(
`Secret "${secretName}" was not found at path "${secretPath}" in environment "${envSlug}" of project "${projectSlug}"`
);
}
handleError(err);
throw err;
}
};
Loading