Merge pull request #17 from Infisical/daniel/projects-and-environments
feat: create projects/project memberships/environments/folders
This commit is contained in:
92
README.md
92
README.md
@@ -432,3 +432,95 @@ const renewedLease = await client.dynamicSecrets().leases.renew(newLease.lease.i
|
|||||||
**Returns:**
|
**Returns:**
|
||||||
- `ApiV1DynamicSecretsLeasesLeaseIdDelete200Response`: The renewed lease response _(doesn't contain new credentials)_.
|
- `ApiV1DynamicSecretsLeasesLeaseIdDelete200Response`: The renewed lease response _(doesn't contain new credentials)_.
|
||||||
|
|
||||||
|
### `projects`
|
||||||
|
|
||||||
|
#### Create a new project
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const project = await client.projects().create({
|
||||||
|
projectName: "<name-of-project>",
|
||||||
|
type: "secret-manager", // cert-manager, secret-manager, kms, ssh
|
||||||
|
projectDescription: "<project-description>", // Optional
|
||||||
|
slug: "<slug-of-project-to-create>", // Optional
|
||||||
|
template: "<project-template-name>", // Optional
|
||||||
|
kmsKeyId: "kms-key-id" // Optional
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `projectName` (string): The name of the project to create.
|
||||||
|
- `type` (string): The type of project to create. Valid options are `secret-manager`, `cert-manager`, `kms`, `ssh`
|
||||||
|
- `projectDescription` (string): An optional description of the project to create.
|
||||||
|
- `slug` (string): An optional slug for the project to create. If not provided, one will be generated automatically.
|
||||||
|
- `template` (string): Optionally provide a project template name to use for creating this project.
|
||||||
|
- `kmsKeyId` (string): The ID of the KMS key to use for the project. Will use the Infisical KMS by default.
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `ApiV1WorkspaceWorkspaceIdGet200ResponseWorkspace`: The project that was created.
|
||||||
|
|
||||||
|
|
||||||
|
#### Invite members to a project
|
||||||
|
|
||||||
|
When inviting members to projects, you must either specify the `emails` or `usernames`. If neither are specified, the SDK will throw an error.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const memberships = await client.projects().inviteMembers({
|
||||||
|
projectId: project.id,
|
||||||
|
emails: ["test1@example.com", "test2@example.com"], // Optional
|
||||||
|
usernames: ["example-user3", "example-user4"] // Optional
|
||||||
|
roleSlugs: ["member"] // Optional
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `projectId`: (string): The ID of the project to invite members to
|
||||||
|
- `emails`: (string[]): An array of emails of the users to invite to the project.
|
||||||
|
- `usernames`: (string[]) An array of usernames of the users to invite to the project.
|
||||||
|
- `roleSlugs`: (string[]): An array of role slugs to assign to the members. If not specified, this will default to `member`.
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `ApiV1OrganizationAdminProjectsProjectIdGrantAdminAccessPost200ResponseMembership`: An array of the created project memberships.
|
||||||
|
|
||||||
|
### `environments`
|
||||||
|
|
||||||
|
#### Create a new environment
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const environment = await client.environments().create({
|
||||||
|
name: "<environment-name>",
|
||||||
|
projectId: "<your-project-id>",
|
||||||
|
slug: "<environment-slug>",
|
||||||
|
position: 1 // Optional
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `name` (string): The name of the environment to be created.
|
||||||
|
- `projectId` (string): The ID of the project to create the environment within.
|
||||||
|
- `slug`: (string): The slug of the environment to be created.
|
||||||
|
- `position` (number): An optional position of the environment to be created. The position is used in the Infisical UI to display environments in order. Environments with the lowest position come first.
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `ApiV1WorkspaceWorkspaceIdEnvironmentsEnvIdGet200ResponseEnvironment`: The environment that was created.
|
||||||
|
|
||||||
|
#### Create a new folder
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const folder = await client.folders().create({
|
||||||
|
name: "<folder-name>",
|
||||||
|
path: "<folder-path>",
|
||||||
|
projectId: "<your-project-id>",
|
||||||
|
environment: "<environment-slug>",
|
||||||
|
description: "<folder-description>" // Optional
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `name` (string): The name of the folder to create.
|
||||||
|
- `path` (string): The path where of where to create the folder. Defaults to `/`, which is the root folder.
|
||||||
|
- `projectId` (string): The ID of the project to create the folder within.
|
||||||
|
- `environment` (string): The slug of the environment to create the folder within.
|
||||||
|
- `description` (string): An optional folder description.
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `ApiV1FoldersPost200ResponseFolder`: The folder that was created.
|
||||||
|
|||||||
33
src/custom/environments.ts
Normal file
33
src/custom/environments.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { RawAxiosRequestConfig } from "axios";
|
||||||
|
import { DefaultApi as InfisicalApi } from "../infisicalapi_client";
|
||||||
|
import type { ApiV1WorkspaceWorkspaceIdEnvironmentsPostRequest, ApiV1WorkspaceWorkspaceIdEnvironmentsPost200Response } from "../infisicalapi_client";
|
||||||
|
import { newInfisicalError } from "./errors";
|
||||||
|
|
||||||
|
export type CreateEnvironmentOptions = {
|
||||||
|
projectId: string;
|
||||||
|
} & ApiV1WorkspaceWorkspaceIdEnvironmentsPostRequest;
|
||||||
|
export type CreateEnvironmentResult = ApiV1WorkspaceWorkspaceIdEnvironmentsPost200Response;
|
||||||
|
|
||||||
|
export default class EnvironmentsClient {
|
||||||
|
#apiInstance: InfisicalApi;
|
||||||
|
#requestOptions: RawAxiosRequestConfig | undefined;
|
||||||
|
constructor(apiInstance: InfisicalApi, requestOptions: RawAxiosRequestConfig | undefined) {
|
||||||
|
this.#apiInstance = apiInstance;
|
||||||
|
this.#requestOptions = requestOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
create = async (options: CreateEnvironmentOptions): Promise<CreateEnvironmentResult["environment"]> => {
|
||||||
|
try {
|
||||||
|
const res = await this.#apiInstance.apiV1WorkspaceWorkspaceIdEnvironmentsPost(
|
||||||
|
{
|
||||||
|
workspaceId: options.projectId,
|
||||||
|
apiV1WorkspaceWorkspaceIdEnvironmentsPostRequest: options
|
||||||
|
},
|
||||||
|
this.#requestOptions
|
||||||
|
);
|
||||||
|
return res.data.environment;
|
||||||
|
} catch (err) {
|
||||||
|
throw newInfisicalError(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -33,7 +33,12 @@ export const newInfisicalError = (error: any) => {
|
|||||||
const data = error?.response?.data as TApiErrorResponse;
|
const data = error?.response?.data as TApiErrorResponse;
|
||||||
|
|
||||||
if (data?.message) {
|
if (data?.message) {
|
||||||
return new InfisicalSDKRequestError(data.message, {
|
let message = data.message;
|
||||||
|
if (error.status === 422) {
|
||||||
|
message = JSON.stringify(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new InfisicalSDKRequestError(message, {
|
||||||
url: error.response?.config.url || "",
|
url: error.response?.config.url || "",
|
||||||
method: error.response?.config.method || "",
|
method: error.response?.config.method || "",
|
||||||
statusCode: error.response?.status || 0
|
statusCode: error.response?.status || 0
|
||||||
|
|||||||
35
src/custom/folders.ts
Normal file
35
src/custom/folders.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { RawAxiosRequestConfig } from "axios";
|
||||||
|
import { DefaultApi as InfisicalApi } from "../infisicalapi_client";
|
||||||
|
import type { ApiV1FoldersPostRequest, ApiV1FoldersPost200Response } from "../infisicalapi_client";
|
||||||
|
import { newInfisicalError } from "./errors";
|
||||||
|
|
||||||
|
export type CreateFolderOptions = {
|
||||||
|
projectId: string;
|
||||||
|
} & Omit<ApiV1FoldersPostRequest, "workspaceId" | "directory">;
|
||||||
|
export type CreateFolderResult = ApiV1FoldersPost200Response;
|
||||||
|
|
||||||
|
export default class FoldersClient {
|
||||||
|
#apiInstance: InfisicalApi;
|
||||||
|
#requestOptions: RawAxiosRequestConfig | undefined;
|
||||||
|
constructor(apiInstance: InfisicalApi, requestOptions: RawAxiosRequestConfig | undefined) {
|
||||||
|
this.#apiInstance = apiInstance;
|
||||||
|
this.#requestOptions = requestOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
create = async (options: CreateFolderOptions): Promise<CreateFolderResult["folder"]> => {
|
||||||
|
try {
|
||||||
|
const res = await this.#apiInstance.apiV1FoldersPost(
|
||||||
|
{
|
||||||
|
apiV1FoldersPostRequest: {
|
||||||
|
...options,
|
||||||
|
workspaceId: options.projectId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
this.#requestOptions
|
||||||
|
);
|
||||||
|
return res.data.folder;
|
||||||
|
} catch (err) {
|
||||||
|
throw newInfisicalError(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
56
src/custom/projects.ts
Normal file
56
src/custom/projects.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { RawAxiosRequestConfig } from "axios";
|
||||||
|
import { DefaultApi as InfisicalApi } from "../infisicalapi_client";
|
||||||
|
import type {
|
||||||
|
ApiV2WorkspacePost200Response,
|
||||||
|
ApiV2WorkspacePostRequest,
|
||||||
|
ApiV2WorkspaceProjectIdMembershipsPost200Response,
|
||||||
|
ApiV2WorkspaceProjectIdMembershipsPostRequest
|
||||||
|
} from "../infisicalapi_client";
|
||||||
|
import { newInfisicalError } from "./errors";
|
||||||
|
|
||||||
|
export type CreateProjectOptions = ApiV2WorkspacePostRequest;
|
||||||
|
export type CreateProjectResult = ApiV2WorkspacePost200Response;
|
||||||
|
|
||||||
|
export type InviteMemberToProjectOptions = { projectId: string } & ApiV2WorkspaceProjectIdMembershipsPostRequest;
|
||||||
|
export type InviteMemberToProjectResult = ApiV2WorkspaceProjectIdMembershipsPost200Response;
|
||||||
|
export default class ProjectsClient {
|
||||||
|
#apiInstance: InfisicalApi;
|
||||||
|
#requestOptions: RawAxiosRequestConfig | undefined;
|
||||||
|
constructor(apiInstance: InfisicalApi, requestOptions: RawAxiosRequestConfig | undefined) {
|
||||||
|
this.#apiInstance = apiInstance;
|
||||||
|
this.#requestOptions = requestOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
create = async (options: CreateProjectOptions): Promise<CreateProjectResult["project"]> => {
|
||||||
|
try {
|
||||||
|
const res = await this.#apiInstance.apiV2WorkspacePost(
|
||||||
|
{
|
||||||
|
apiV2WorkspacePostRequest: options
|
||||||
|
},
|
||||||
|
this.#requestOptions
|
||||||
|
);
|
||||||
|
return res.data.project;
|
||||||
|
} catch (err) {
|
||||||
|
throw newInfisicalError(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inviteMembers = async (options: InviteMemberToProjectOptions): Promise<InviteMemberToProjectResult["memberships"]> => {
|
||||||
|
try {
|
||||||
|
if (!options.usernames?.length && !options.emails?.length) {
|
||||||
|
throw new Error("Either usernames or emails must be provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await this.#apiInstance.apiV2WorkspaceProjectIdMembershipsPost(
|
||||||
|
{
|
||||||
|
projectId: options.projectId,
|
||||||
|
apiV2WorkspaceProjectIdMembershipsPostRequest: options
|
||||||
|
},
|
||||||
|
this.#requestOptions
|
||||||
|
);
|
||||||
|
return res.data.memberships;
|
||||||
|
} catch (err) {
|
||||||
|
throw newInfisicalError(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
15
src/index.ts
15
src/index.ts
@@ -6,6 +6,9 @@ import { RawAxiosRequestConfig } from "axios";
|
|||||||
import DynamicSecretsClient from "./custom/dynamic-secrets";
|
import DynamicSecretsClient from "./custom/dynamic-secrets";
|
||||||
|
|
||||||
import * as ApiClient from "./infisicalapi_client";
|
import * as ApiClient from "./infisicalapi_client";
|
||||||
|
import EnvironmentsClient from "./custom/environments";
|
||||||
|
import ProjectsClient from "./custom/projects";
|
||||||
|
import FoldersClient from "./custom/folders";
|
||||||
|
|
||||||
const buildRestClient = (apiClient: InfisicalApi, requestOptions?: RawAxiosRequestConfig) => {
|
const buildRestClient = (apiClient: InfisicalApi, requestOptions?: RawAxiosRequestConfig) => {
|
||||||
return {
|
return {
|
||||||
@@ -26,6 +29,9 @@ class InfisicalSDK {
|
|||||||
#requestOptions: RawAxiosRequestConfig | undefined;
|
#requestOptions: RawAxiosRequestConfig | undefined;
|
||||||
#secretsClient: SecretsClient;
|
#secretsClient: SecretsClient;
|
||||||
#dynamicSecretsClient: DynamicSecretsClient;
|
#dynamicSecretsClient: DynamicSecretsClient;
|
||||||
|
#environmentsClient: EnvironmentsClient;
|
||||||
|
#projectsClient: ProjectsClient;
|
||||||
|
#foldersClient: FoldersClient;
|
||||||
#authClient: AuthClient;
|
#authClient: AuthClient;
|
||||||
#basePath: string;
|
#basePath: string;
|
||||||
|
|
||||||
@@ -41,6 +47,9 @@ class InfisicalSDK {
|
|||||||
this.#authClient = new AuthClient(this.authenticate.bind(this), this.#apiInstance);
|
this.#authClient = new AuthClient(this.authenticate.bind(this), this.#apiInstance);
|
||||||
this.#dynamicSecretsClient = new DynamicSecretsClient(this.#apiInstance, this.#requestOptions);
|
this.#dynamicSecretsClient = new DynamicSecretsClient(this.#apiInstance, this.#requestOptions);
|
||||||
this.#secretsClient = new SecretsClient(this.#apiInstance, this.#requestOptions);
|
this.#secretsClient = new SecretsClient(this.#apiInstance, this.#requestOptions);
|
||||||
|
this.#environmentsClient = new EnvironmentsClient(this.#apiInstance, this.#requestOptions);
|
||||||
|
this.#projectsClient = new ProjectsClient(this.#apiInstance, this.#requestOptions);
|
||||||
|
this.#foldersClient = new FoldersClient(this.#apiInstance, this.#requestOptions);
|
||||||
this.rest = () => buildRestClient(this.#apiInstance, this.#requestOptions);
|
this.rest = () => buildRestClient(this.#apiInstance, this.#requestOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,11 +71,17 @@ class InfisicalSDK {
|
|||||||
this.#secretsClient = new SecretsClient(this.#apiInstance, this.#requestOptions);
|
this.#secretsClient = new SecretsClient(this.#apiInstance, this.#requestOptions);
|
||||||
this.#dynamicSecretsClient = new DynamicSecretsClient(this.#apiInstance, this.#requestOptions);
|
this.#dynamicSecretsClient = new DynamicSecretsClient(this.#apiInstance, this.#requestOptions);
|
||||||
this.#authClient = new AuthClient(this.authenticate.bind(this), this.#apiInstance, accessToken);
|
this.#authClient = new AuthClient(this.authenticate.bind(this), this.#apiInstance, accessToken);
|
||||||
|
this.#environmentsClient = new EnvironmentsClient(this.#apiInstance, this.#requestOptions);
|
||||||
|
this.#projectsClient = new ProjectsClient(this.#apiInstance, this.#requestOptions);
|
||||||
|
this.#foldersClient = new FoldersClient(this.#apiInstance, this.#requestOptions);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
secrets = () => this.#secretsClient;
|
secrets = () => this.#secretsClient;
|
||||||
|
environments = () => this.#environmentsClient;
|
||||||
|
projects = () => this.#projectsClient;
|
||||||
|
folders = () => this.#foldersClient;
|
||||||
dynamicSecrets = () => this.#dynamicSecretsClient;
|
dynamicSecrets = () => this.#dynamicSecretsClient;
|
||||||
auth = () => this.#authClient;
|
auth = () => this.#authClient;
|
||||||
rest = () => buildRestClient(this.#apiInstance, this.#requestOptions);
|
rest = () => buildRestClient(this.#apiInstance, this.#requestOptions);
|
||||||
|
|||||||
@@ -1,65 +1,52 @@
|
|||||||
import { InfisicalSDK } from "../src";
|
import { InfisicalSDK } from "../src";
|
||||||
|
|
||||||
const PROJECT_ID = "PROJECT_ID";
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const client = new InfisicalSDK({
|
const client = new InfisicalSDK({
|
||||||
siteUrl: "http://localhost:8080" // Optional, defaults to https://app.infisical.com
|
siteUrl: "http://localhost:8080" // Optional, defaults to https://app.infisical.com
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const EMAIL_TO_INVITE = "<your-email>";
|
||||||
|
|
||||||
|
const universalAuthClientId = process.env.UNIVERSAL_AUTH_CLIENT_ID;
|
||||||
|
const universalAuthClientSecret = process.env.UNIVERSAL_AUTH_CLIENT_SECRET;
|
||||||
|
|
||||||
|
if (!universalAuthClientId || !universalAuthClientSecret) {
|
||||||
|
throw new Error("UNIVERSAL_AUTH_CLIENT_ID and UNIVERSAL_AUTH_CLIENT_SECRET must be set");
|
||||||
|
}
|
||||||
|
|
||||||
await client.auth().universalAuth.login({
|
await client.auth().universalAuth.login({
|
||||||
clientId: "CLIENT_ID",
|
clientId: universalAuthClientId,
|
||||||
clientSecret: "CLIENT_SECRET"
|
clientSecret: universalAuthClientSecret
|
||||||
});
|
});
|
||||||
|
|
||||||
const allSecrets = await client.secrets().listSecrets({
|
console.log("Creating project");
|
||||||
environment: "dev",
|
const project = await client.projects().create({
|
||||||
projectId: PROJECT_ID,
|
projectDescription: "test description",
|
||||||
expandSecretReferences: true,
|
projectName: "test project1344assdfd",
|
||||||
includeImports: false,
|
type: "secret-manager",
|
||||||
recursive: false
|
slug: "test-project1assdfd43"
|
||||||
});
|
});
|
||||||
console.log(allSecrets.secrets);
|
|
||||||
|
|
||||||
const singleSecret = await client.secrets().getSecret({
|
const environment = await client.environments().create({
|
||||||
environment: "dev",
|
position: 100,
|
||||||
projectId: PROJECT_ID,
|
slug: "test-environment-custom-slug",
|
||||||
secretName: "TEST1",
|
name: "test environment",
|
||||||
expandSecretReferences: true, // Optional
|
projectId: project.id
|
||||||
includeImports: true, // Optional
|
|
||||||
|
|
||||||
type: "shared", // Optional
|
|
||||||
version: 1 // Optional
|
|
||||||
});
|
});
|
||||||
console.log(`Fetched single secret, ${singleSecret}=${singleSecret.secretValue}`);
|
|
||||||
|
|
||||||
const newSecret = await client.secrets().createSecret("NEW_SECRET_NAME22423423", {
|
console.log("Creating folder");
|
||||||
environment: "dev",
|
const folder = await client.folders().create({
|
||||||
projectId: PROJECT_ID,
|
name: "test-folder",
|
||||||
secretValue: "SECRET_VALUE"
|
projectId: project.id,
|
||||||
|
environment: environment.slug
|
||||||
});
|
});
|
||||||
console.log(`You created a new secret: ${newSecret.secret.secretKey}`);
|
|
||||||
|
|
||||||
const updatedSecret = await client.secrets().updateSecret("NEW_SECRET_NAME22423423", {
|
console.log("Inviting member to project");
|
||||||
environment: "dev",
|
const memberships = await client.projects().inviteMembers({
|
||||||
projectId: PROJECT_ID,
|
projectId: project.id,
|
||||||
secretValue: "UPDATED_SECRET_VALUE",
|
emails: [EMAIL_TO_INVITE],
|
||||||
newSecretName: "NEW_SECRET_NAME22222", // Optional
|
roleSlugs: ["admin"]
|
||||||
secretComment: "This is an updated secret", // Optional
|
|
||||||
|
|
||||||
secretReminderNote: "This is an updated reminder note", // Optional
|
|
||||||
secretReminderRepeatDays: 14, // Optional
|
|
||||||
skipMultilineEncoding: false, // Optional
|
|
||||||
metadata: {
|
|
||||||
// Optional
|
|
||||||
extra: "metadata"
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
console.log(`You updated the secret: ${updatedSecret.secret.secretKey}`);
|
|
||||||
|
|
||||||
const deletedSecret = await client.secrets().deleteSecret("NEW_SECRET_NAME22222", {
|
console.log("Memberships", memberships);
|
||||||
environment: "dev",
|
|
||||||
projectId: PROJECT_ID
|
|
||||||
});
|
|
||||||
console.log(`You deleted the secret: ${deletedSecret.secret.secretKey}`);
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user