Compare commits

..

4 Commits

Author SHA1 Message Date
e3a356cda9 updated go sdk 2024-10-24 14:47:00 +04:00
7fb3076238 fix: added sdk context support 2024-10-19 08:40:02 +04:00
a0865cda2e fix: enable sdk silent mode 2024-10-19 02:59:29 +04:00
1e7b1ccf22 feat: automatic token refreshing 2024-10-19 01:38:11 +04:00
38 changed files with 358 additions and 600 deletions

View File

@ -95,10 +95,6 @@ RUN mkdir frontend-build
# Production stage # Production stage
FROM base AS production FROM base AS production
RUN apk add --upgrade --no-cache ca-certificates RUN apk add --upgrade --no-cache ca-certificates
RUN apk add --no-cache bash curl && curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' | bash \
&& apk add infisical=0.31.1 && apk add --no-cache git
RUN addgroup --system --gid 1001 nodejs \ RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 non-root-user && adduser --system --uid 1001 non-root-user

View File

@ -2,8 +2,6 @@ import { z } from "zod";
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas"; import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types"; import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@ -25,13 +23,6 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
const appCfg = getConfig();
if (!appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(req.auth.orgId)) {
throw new BadRequestError({
message: "Secret scanning is temporarily unavailable."
});
}
const session = await server.services.secretScanning.createInstallationSession({ const session = await server.services.secretScanning.createInstallationSession({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
@ -39,7 +30,6 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
orgId: req.body.organizationId orgId: req.body.organizationId
}); });
return session; return session;
} }
}); });

View File

@ -1,6 +1,6 @@
import { ProbotOctokit } from "probot"; import { ProbotOctokit } from "probot";
import { OrgMembershipRole, TableName } from "@app/db/schemas"; import { OrgMembershipRole } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
@ -61,7 +61,7 @@ export const secretScanningQueueFactory = ({
const getOrgAdminEmails = async (organizationId: string) => { const getOrgAdminEmails = async (organizationId: string) => {
// get emails of admins // get emails of admins
const adminsOfWork = await orgMemberDAL.findMembership({ const adminsOfWork = await orgMemberDAL.findMembership({
[`${TableName.Organization}.id` as string]: organizationId, orgId: organizationId,
role: OrgMembershipRole.Admin role: OrgMembershipRole.Admin
}); });
return adminsOfWork.filter((userObject) => userObject.email).map((userObject) => userObject.email as string); return adminsOfWork.filter((userObject) => userObject.email).map((userObject) => userObject.email as string);

View File

@ -90,7 +90,7 @@ export const secretScanningServiceFactory = ({
const { const {
data: { repositories } data: { repositories }
} = await octokit.apps.listReposAccessibleToInstallation(); } = await octokit.apps.listReposAccessibleToInstallation();
if (appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(actorOrgId)) { if (!appCfg.DISABLE_SECRET_SCANNING) {
await Promise.all( await Promise.all(
repositories.map(({ id, full_name }) => repositories.map(({ id, full_name }) =>
secretScanningQueue.startFullRepoScan({ secretScanningQueue.startFullRepoScan({
@ -164,7 +164,7 @@ export const secretScanningServiceFactory = ({
}); });
if (!installationLink) return; if (!installationLink) return;
if (appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(installationLink.orgId)) { if (!appCfg.DISABLE_SECRET_SCANNING) {
await secretScanningQueue.startPushEventScan({ await secretScanningQueue.startPushEventScan({
commits, commits,
pusher: { name: pusher.name, email: pusher.email }, pusher: { name: pusher.name, email: pusher.email },

View File

@ -142,7 +142,6 @@ const envSchema = z
SECRET_SCANNING_WEBHOOK_SECRET: zpStr(z.string().optional()), SECRET_SCANNING_WEBHOOK_SECRET: zpStr(z.string().optional()),
SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()), SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()),
SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()), SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()),
SECRET_SCANNING_ORG_WHITELIST: zpStr(z.string().optional()),
// LICENSE // LICENSE
LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")), LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")),
LICENSE_SERVER_KEY: zpStr(z.string().optional()), LICENSE_SERVER_KEY: zpStr(z.string().optional()),
@ -178,8 +177,7 @@ const envSchema = z
Boolean(data.SECRET_SCANNING_GIT_APP_ID) && Boolean(data.SECRET_SCANNING_GIT_APP_ID) &&
Boolean(data.SECRET_SCANNING_PRIVATE_KEY) && Boolean(data.SECRET_SCANNING_PRIVATE_KEY) &&
Boolean(data.SECRET_SCANNING_WEBHOOK_SECRET), Boolean(data.SECRET_SCANNING_WEBHOOK_SECRET),
samlDefaultOrgSlug: data.DEFAULT_SAML_ORG_SLUG, samlDefaultOrgSlug: data.DEFAULT_SAML_ORG_SLUG
SECRET_SCANNING_ORG_WHITELIST: data.SECRET_SCANNING_ORG_WHITELIST?.split(",")
})); }));
let envCfg: Readonly<z.infer<typeof envSchema>>; let envCfg: Readonly<z.infer<typeof envSchema>>;

View File

@ -71,13 +71,6 @@ export class BadRequestError extends Error {
} }
} }
export class RateLimitError extends Error {
constructor({ message }: { message?: string }) {
super(message || "Rate limit exceeded");
this.name = "RateLimitExceeded";
}
}
export class NotFoundError extends Error { export class NotFoundError extends Error {
name: string; name: string;

View File

@ -2,7 +2,6 @@ import type { RateLimitOptions, RateLimitPluginOptions } from "@fastify/rate-lim
import { Redis } from "ioredis"; import { Redis } from "ioredis";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { RateLimitError } from "@app/lib/errors";
export const globalRateLimiterCfg = (): RateLimitPluginOptions => { export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
const appCfg = getConfig(); const appCfg = getConfig();
@ -11,11 +10,6 @@ export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
: null; : null;
return { return {
errorResponseBuilder: (_, context) => {
throw new RateLimitError({
message: `Rate limit exceeded. Please try again in ${context.after}`
});
},
timeWindow: 60 * 1000, timeWindow: 60 * 1000,
max: 600, max: 600,
redis, redis,

View File

@ -10,7 +10,6 @@ import {
GatewayTimeoutError, GatewayTimeoutError,
InternalServerError, InternalServerError,
NotFoundError, NotFoundError,
RateLimitError,
ScimRequestError, ScimRequestError,
UnauthorizedError UnauthorizedError
} from "@app/lib/errors"; } from "@app/lib/errors";
@ -28,8 +27,7 @@ enum HttpStatusCodes {
Forbidden = 403, Forbidden = 403,
// eslint-disable-next-line @typescript-eslint/no-shadow // eslint-disable-next-line @typescript-eslint/no-shadow
InternalServerError = 500, InternalServerError = 500,
GatewayTimeout = 504, GatewayTimeout = 504
TooManyRequests = 429
} }
export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => { export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => {
@ -71,12 +69,6 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
message: error.message, message: error.message,
error: error.name error: error.name
}); });
} else if (error instanceof RateLimitError) {
void res.status(HttpStatusCodes.TooManyRequests).send({
statusCode: HttpStatusCodes.TooManyRequests,
message: error.message,
error: error.name
});
} else if (error instanceof ScimRequestError) { } else if (error instanceof ScimRequestError) {
void res.status(error.status).send({ void res.status(error.status).send({
schemas: error.schemas, schemas: error.schemas,

View File

@ -225,7 +225,9 @@ export const registerRoutes = async (
}: { auditLogDb?: Knex; db: Knex; smtp: TSmtpService; queue: TQueueServiceFactory; keyStore: TKeyStoreFactory } }: { auditLogDb?: Knex; db: Knex; smtp: TSmtpService; queue: TQueueServiceFactory; keyStore: TKeyStoreFactory }
) => { ) => {
const appCfg = getConfig(); const appCfg = getConfig();
if (!appCfg.DISABLE_SECRET_SCANNING) {
await server.register(registerSecretScannerGhApp, { prefix: "/ss-webhook" }); await server.register(registerSecretScannerGhApp, { prefix: "/ss-webhook" });
}
// db layers // db layers
const userDAL = userDALFactory(db); const userDAL = userDALFactory(db);

View File

@ -16,10 +16,8 @@ as well as create a new project.
The **Settings** page lets you manage information about your organization including: The **Settings** page lets you manage information about your organization including:
- **Name**: The name of your organization. - Name: The name of your organization.
- **Slug**: The slug of your organization. - Incident contacts: Emails that should be alerted if anything abnormal is detected within the organization.
- **Default Organization Member Role**: The role assigned to users when joining your organization unless otherwise specified.
- **Incident Contacts**: Emails that should be alerted if anything abnormal is detected within the organization.
![organization settings general](../../images/platform/organization/organization-settings-general.png) ![organization settings general](../../images/platform/organization/organization-settings-general.png)

View File

@ -28,13 +28,6 @@ Prerequisites:
![SCIM copy token](/images/platform/scim/scim-copy-token.png) ![SCIM copy token](/images/platform/scim/scim-copy-token.png)
</Step> </Step>
<Step title="Add Users and Groups in Azure">
In Azure, navigate to Enterprise Application > Users and Groups. Add any users and/or groups to your application that you would like
to be provisioned over to Infisical.
![SCIM Azure Users and Groups](/images/platform/scim/azure/scim-azure-add-users-and-groups.png)
</Step>
<Step title="Configure SCIM in Azure"> <Step title="Configure SCIM in Azure">
In Azure, head to your Enterprise Application > Provisioning > Overview and press **Get started**. In Azure, head to your Enterprise Application > Provisioning > Overview and press **Get started**.

View File

@ -1,26 +0,0 @@
---
title: "SCIM Group Mappings"
description: "Learn how to enhance your SCIM implementation using group mappings"
---
<Info>
SCIM provisioning, and by extension group mapping, is a paid feature.
If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical,
then you should contact sales@infisical.com to purchase an enterprise license to use it.
</Info>
## SCIM Group to Organization Role Mapping
By default, when users are provisioned via SCIM, they will be assigned the default organization role configured in [Organization General Settings](/documentation/platform/organization#settings).
For more precise control over membership roles, you can set up SCIM Group to Organization Role Mappings. This enables you to assign specific roles based on the group from which a user is provisioned.
![SCIM Group Mapping](/images/platform/scim/scim-group-mapping.png)
To configure a mapping, simply enter the SCIM group's name and select the role you would like users to be assigned from this group. Be sure
to tap **Update Mappings** once complete.
<Note>
SCIM Group Mappings only apply when users are first provisioned. Previously provisioned users will not be affected, allowing you to customize user roles after they are added.
</Note>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 985 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -249,8 +249,7 @@
"documentation/platform/scim/overview", "documentation/platform/scim/overview",
"documentation/platform/scim/okta", "documentation/platform/scim/okta",
"documentation/platform/scim/azure", "documentation/platform/scim/azure",
"documentation/platform/scim/jumpcloud", "documentation/platform/scim/jumpcloud"
"documentation/platform/scim/group-mappings"
] ]
} }
] ]

View File

@ -11,7 +11,7 @@ If you're working with Go Lang, the official [Infisical Go SDK](https://github.c
- [Package](https://pkg.go.dev/github.com/infisical/go-sdk) - [Package](https://pkg.go.dev/github.com/infisical/go-sdk)
- [Github Repository](https://github.com/infisical/go-sdk) - [Github Repository](https://github.com/infisical/go-sdk)
# Basic Usage ## Basic Usage
```go ```go
package main package main
@ -19,15 +19,14 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"context"
infisical "github.com/infisical/go-sdk" infisical "github.com/infisical/go-sdk"
) )
func main() { func main() {
client := infisical.NewInfisicalClient(context.Background(), infisical.Config{ client := infisical.NewInfisicalClient(infisical.Config{
SiteUrl: "https://app.infisical.com", // Optional, default is https://app.infisical.com SiteUrl: "https://app.infisical.com", // Optional, default is https://app.infisical.com
AutoTokenRefresh: true, // Wether or not to let the SDK handle the access token lifecycle. Defaults to true if not specified.
}) })
_, err = client.Auth().UniversalAuthLogin("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET") _, err = client.Auth().UniversalAuthLogin("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")
@ -65,68 +64,32 @@ This example demonstrates how to use the Infisical Go SDK in a simple Go applica
```console ```console
$ go get github.com/infisical/go-sdk $ go get github.com/infisical/go-sdk
``` ```
# Configuration # Configuration
Import the SDK and create a client instance. Import the SDK and create a client instance.
```go ```go
client := infisical.NewInfisicalClient(context.Background(), infisical.Config{ client := infisical.NewInfisicalClient(infisical.Config{
SiteUrl: "https://app.infisical.com", // Optional, default is https://api.infisical.com SiteUrl: "https://app.infisical.com", // Optional, default is https://api.infisical.com
}) })
``` ```
### Configuration Options ### ClientSettings methods
<ParamField query="options" type="object"> <ParamField query="options" type="object">
<Expandable title="properties"> <Expandable title="properties">
<ParamField query="SiteUrl" type="string" optional default="https://app.infisical.com"> <ParamField query="SiteUrl" type="string" optional>
The URL of the Infisical API.. The URL of the Infisical API. Default is `https://api.infisical.com`.
</ParamField> </ParamField>
<ParamField query="UserAgent" type="string"> <ParamField query="UserAgent" type="string" required>
Optionally set the user agent that will be used for HTTP requests. _(Not recommended)_ Optionally set the user agent that will be used for HTTP requests. _(Not recommended)_
</ParamField> </ParamField>
<ParamField query="AutoTokenRefresh" type="boolean" default={true} optional>
Whether or not to let the SDK handle the access token lifecycle. Defaults to true if not specified.
</ParamField>
<ParamField query="SilentMode" type="boolean" default={false} optional>
Whether or not to suppress logs such as warnings from the token refreshing process. Defaults to false if not specified.
</ParamField>
</Expandable> </Expandable>
</ParamField> </ParamField>
# Automatic token refreshing ### Authentication
The Infisical Go SDK supports automatic token refreshing. After using one of the auth methods such as Universal Auth, the SDK will automatically renew and re-authenticate when needed.
This behavior is enabled by default, but you can opt-out by setting `AutoTokenRefresh` to `false` in the client settings.
```go
client := infisical.NewInfisicalClient(context.Background(), infisical.Config{
AutoTokenRefresh: false, // <- Disable automatic token refreshing
})
```
When using automatic token refreshing it's important to understand how your application uses the Infiiscal client. If you are instantiating new instances of the client often, it's important to cancel the context when the client is no longer needed to avoid the token refreshing process from running indefinitely.
```go
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // Cancel the context when the client is no longer needed
client := infisical.NewInfisicalClient(ctx, infisical.Config{
AutoTokenRefresh: true,
})
// Use the client
```
This is only necessary if you are creating multiple instances of the client, and those instances are deleted or otherwise removed throughout the application lifecycle.
If you are only creating one instance of the client, and it will be used throughout the lifetime of your application, you don't need to worry about this.
# Authentication
The SDK supports a variety of authentication methods. The most common authentication method is Universal Auth, which uses a client ID and client secret to authenticate. The SDK supports a variety of authentication methods. The most common authentication method is Universal Auth, which uses a client ID and client secret to authenticate.
@ -259,12 +222,9 @@ if err != nil {
} }
``` ```
## Working With Secrets ## Working with Secrets
### List Secrets ### client.Secrets().List(options)
`client.Secrets().List(options)`
Retrieve all secrets within the Infisical project and environment that client is connected to.
```go ```go
secrets, err := client.Secrets().List(infisical.ListSecretsOptions{ secrets, err := client.Secrets().List(infisical.ListSecretsOptions{
@ -275,7 +235,9 @@ secrets, err := client.Secrets().List(infisical.ListSecretsOptions{
}) })
``` ```
### Parameters Retrieve all secrets within the Infisical project and environment that client is connected to
#### Parameters
<ParamField query="Parameters" type="object"> <ParamField query="Parameters" type="object">
<Expandable title="properties"> <Expandable title="properties">
@ -310,11 +272,7 @@ secrets, err := client.Secrets().List(infisical.ListSecretsOptions{
</ParamField> </ParamField>
### ### client.Secrets().Retrieve(options)
### Retrieve Secret
`client.Secrets().Retrieve(options)`
Retrieve a secret from Infisical. By default `Secrets().Retrieve()` fetches and returns a shared secret.
```go ```go
secret, err := client.Secrets().Retrieve(infisical.RetrieveSecretOptions{ secret, err := client.Secrets().Retrieve(infisical.RetrieveSecretOptions{
@ -324,7 +282,11 @@ secret, err := client.Secrets().Retrieve(infisical.RetrieveSecretOptions{
}) })
``` ```
### Parameters Retrieve a secret from Infisical.
By default, `Secrets().Retrieve()` fetches and returns a shared secret.
#### Parameters
<ParamField query="Parameters" type="object" optional> <ParamField query="Parameters" type="object" optional>
<Expandable title="properties"> <Expandable title="properties">
@ -346,11 +308,7 @@ secret, err := client.Secrets().Retrieve(infisical.RetrieveSecretOptions{
</Expandable> </Expandable>
</ParamField> </ParamField>
### ### client.Secrets().Create(options)
### Create Secret
`client.Secrets().Create(options)`
Create a new secret in Infisical.
```go ```go
secret, err := client.Secrets().Create(infisical.CreateSecretOptions{ secret, err := client.Secrets().Create(infisical.CreateSecretOptions{
@ -363,8 +321,9 @@ secret, err := client.Secrets().Create(infisical.CreateSecretOptions{
}) })
``` ```
Create a new secret in Infisical.
### Parameters #### Parameters
<ParamField query="Parameters" type="object" optional> <ParamField query="Parameters" type="object" optional>
<Expandable title="properties"> <Expandable title="properties">
@ -392,12 +351,7 @@ secret, err := client.Secrets().Create(infisical.CreateSecretOptions{
</Expandable> </Expandable>
</ParamField> </ParamField>
### ### client.Secrets().Update(options)
### Update Secret
`client.Secrets().Update(options)`
Update an existing secret in Infisical.
```go ```go
secret, err := client.Secrets().Update(infisical.UpdateSecretOptions{ secret, err := client.Secrets().Update(infisical.UpdateSecretOptions{
@ -409,7 +363,9 @@ secret, err := client.Secrets().Update(infisical.UpdateSecretOptions{
}) })
``` ```
### Parameters Update an existing secret in Infisical.
#### Parameters
<ParamField query="Parameters" type="object" optional> <ParamField query="Parameters" type="object" optional>
<Expandable title="properties"> <Expandable title="properties">
@ -437,11 +393,7 @@ secret, err := client.Secrets().Update(infisical.UpdateSecretOptions{
</Expandable> </Expandable>
</ParamField> </ParamField>
### ### client.Secrets().Delete(options)
### Delete Secret
`client.Secrets().Delete(options)`
Delete a secret in Infisical.
```go ```go
secret, err := client.Secrets().Delete(infisical.DeleteSecretOptions{ secret, err := client.Secrets().Delete(infisical.DeleteSecretOptions{
@ -451,7 +403,9 @@ secret, err := client.Secrets().Delete(infisical.DeleteSecretOptions{
}) })
``` ```
### Parameters Delete a secret in Infisical.
#### Parameters
<ParamField query="Parameters" type="object" optional> <ParamField query="Parameters" type="object" optional>
<Expandable title="properties"> <Expandable title="properties">
@ -473,14 +427,10 @@ secret, err := client.Secrets().Delete(infisical.DeleteSecretOptions{
</Expandable> </Expandable>
</ParamField> </ParamField>
## Working With folders ## Working with folders
### ### client.Folders().List(options)
### List Folders
`client.Folders().List(options)`
Retrieve all within the Infisical project and environment that client is connected to.
```go ```go
folders, err := client.Folders().List(infisical.ListFoldersOptions{ folders, err := client.Folders().List(infisical.ListFoldersOptions{
@ -490,7 +440,9 @@ folders, err := client.Folders().List(infisical.ListFoldersOptions{
}) })
``` ```
### Parameters Retrieve all within the Infisical project and environment that client is connected to.
#### Parameters
<ParamField query="Parameters" type="object"> <ParamField query="Parameters" type="object">
<Expandable title="properties"> <Expandable title="properties">
@ -509,11 +461,7 @@ folders, err := client.Folders().List(infisical.ListFoldersOptions{
</ParamField> </ParamField>
### ### client.Folders().Create(options)
### Create Folder
`client.Folders().Create(options)`
Create a new folder in Infisical.
```go ```go
folder, err := client.Folders().Create(infisical.CreateFolderOptions{ folder, err := client.Folders().Create(infisical.CreateFolderOptions{
@ -524,7 +472,9 @@ folder, err := client.Folders().Create(infisical.CreateFolderOptions{
}) })
``` ```
### Parameters Create a new folder in Infisical.
#### Parameters
<ParamField query="Parameters" type="object" optional> <ParamField query="Parameters" type="object" optional>
<Expandable title="properties"> <Expandable title="properties">
@ -544,11 +494,8 @@ folder, err := client.Folders().Create(infisical.CreateFolderOptions{
</ParamField> </ParamField>
###
### Update Folder
`client.Folders().Update(options)`
Update an existing folder in Infisical. ### client.Folders().Update(options)
```go ```go
folder, err := client.Folders().Update(infisical.UpdateFolderOptions{ folder, err := client.Folders().Update(infisical.UpdateFolderOptions{
@ -560,7 +507,9 @@ folder, err := client.Folders().Update(infisical.UpdateFolderOptions{
}) })
``` ```
### Parameters Update an existing folder in Infisical.
#### Parameters
<ParamField query="Parameters" type="object" optional> <ParamField query="Parameters" type="object" optional>
<Expandable title="properties"> <Expandable title="properties">
@ -582,11 +531,7 @@ folder, err := client.Folders().Update(infisical.UpdateFolderOptions{
</Expandable> </Expandable>
</ParamField> </ParamField>
### ### client.Folders().Delete(options)
### Delete Folder
`client.Folders().Delete(options)`
Delete a folder in Infisical.
```go ```go
deletedFolder, err := client.Folders().Delete(infisical.DeleteFolderOptions{ deletedFolder, err := client.Folders().Delete(infisical.DeleteFolderOptions{
@ -599,7 +544,9 @@ deletedFolder, err := client.Folders().Delete(infisical.DeleteFolderOptions{
}) })
``` ```
### Parameters Delete a folder in Infisical.
#### Parameters
<ParamField query="Parameters" type="object" optional> <ParamField query="Parameters" type="object" optional>
<Expandable title="properties"> <Expandable title="properties">
@ -621,5 +568,3 @@ deletedFolder, err := client.Folders().Delete(infisical.DeleteFolderOptions{
</ParamField> </ParamField>
</Expandable> </Expandable>
</ParamField> </ParamField>

View File

@ -115,10 +115,7 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
formState: { isSubmitting }, formState: { isSubmitting },
handleSubmit handleSubmit
} = useForm<FormData>({ } = useForm<FormData>({
resolver: zodResolver(createTagSchema), resolver: zodResolver(createTagSchema)
defaultValues: {
color: secretTagsColors[0].hex
}
}); });
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();

View File

@ -1,5 +1,5 @@
import { ReactNode } from "react"; import { ReactNode } from "react";
import { faCheck, faMinus } from "@fortawesome/free-solid-svg-icons"; import { faCheck } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
@ -15,7 +15,6 @@ export type CheckboxProps = Omit<
isRequired?: boolean; isRequired?: boolean;
checkIndicatorBg?: string | undefined; checkIndicatorBg?: string | undefined;
isError?: boolean; isError?: boolean;
isIndeterminate?: boolean;
}; };
export const Checkbox = ({ export const Checkbox = ({
@ -27,7 +26,6 @@ export const Checkbox = ({
isRequired, isRequired,
checkIndicatorBg, checkIndicatorBg,
isError, isError,
isIndeterminate,
...props ...props
}: CheckboxProps): JSX.Element => { }: CheckboxProps): JSX.Element => {
return ( return (
@ -47,11 +45,7 @@ export const Checkbox = ({
id={id} id={id}
> >
<CheckboxPrimitive.Indicator className={`${checkIndicatorBg || "text-bunker-800"}`}> <CheckboxPrimitive.Indicator className={`${checkIndicatorBg || "text-bunker-800"}`}>
{isIndeterminate ? (
<FontAwesomeIcon icon={faMinus} size="sm" />
) : (
<FontAwesomeIcon icon={faCheck} size="sm" /> <FontAwesomeIcon icon={faCheck} size="sm" />
)}
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>
<label <label

View File

@ -1,4 +1,4 @@
import { DetailedHTMLProps, HTMLAttributes, ReactNode, TdHTMLAttributes } from "react"; import { HTMLAttributes, ReactNode, TdHTMLAttributes } from "react";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { Skeleton } from "../Skeleton"; import { Skeleton } from "../Skeleton";
@ -7,13 +7,12 @@ export type TableContainerProps = {
children: ReactNode; children: ReactNode;
isRounded?: boolean; isRounded?: boolean;
className?: string; className?: string;
} & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>; };
export const TableContainer = ({ export const TableContainer = ({
children, children,
className, className,
isRounded = true, isRounded = true
...props
}: TableContainerProps): JSX.Element => ( }: TableContainerProps): JSX.Element => (
<div <div
className={twMerge( className={twMerge(
@ -21,7 +20,6 @@ export const TableContainer = ({
isRounded && "rounded-lg", isRounded && "rounded-lg",
className className
)} )}
{...props}
> >
{children} {children}
</div> </div>

View File

@ -230,7 +230,6 @@ export const AppLayout = ({ children }: LayoutProps) => {
(!orgs?.map((org) => org.id)?.includes(router.query.id as string) && (!orgs?.map((org) => org.id)?.includes(router.query.id as string) &&
!router.asPath.includes("project") && !router.asPath.includes("project") &&
!router.asPath.includes("personal") && !router.asPath.includes("personal") &&
!router.asPath.includes("secret-scanning") &&
!router.asPath.includes("integration"))) !router.asPath.includes("integration")))
) { ) {
router.push(`/org/${currentOrg?.id}/overview`); router.push(`/org/${currentOrg?.id}/overview`);

View File

@ -72,8 +72,7 @@ const SecretScanning = withPermission(
</div> </div>
{config.isSecretScanningDisabled && ( {config.isSecretScanningDisabled && (
<NoticeBanner title="Secret scanning is in maintenance" className="mb-4"> <NoticeBanner title="Secret scanning is in maintenance" className="mb-4">
We are working on improving the performance of secret scanning due to increased We are working on improving the performance of secret scanning due to increased usage.
usage.
</NoticeBanner> </NoticeBanner>
)} )}
<div className="relative mb-6 flex justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6"> <div className="relative mb-6 flex justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6">
@ -117,7 +116,7 @@ const SecretScanning = withPermission(
colorSchema="primary" colorSchema="primary"
onClick={generateNewIntegrationSession} onClick={generateNewIntegrationSession}
className="h-min py-2" className="h-min py-2"
isDisabled={!isAllowed} isDisabled={!isAllowed || config.isSecretScanningDisabled}
> >
Integrate with GitHub Integrate with GitHub
</Button> </Button>

View File

@ -270,7 +270,7 @@ export const IdentityKubernetesAuthForm = ({
label="Allowed Namespaces" label="Allowed Namespaces"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
tooltipText="A comma-separated list of trusted namespaces that service accounts must belong to authenticate with Infisical." tooltipText="An optional comma-separated list of trusted service account names that are allowed to authenticate with Infisical. Leave empty to allow any namespaces."
> >
<Input {...field} placeholder="namespaceA, namespaceB" type="text" /> <Input {...field} placeholder="namespaceA, namespaceB" type="text" />
</FormControl> </FormControl>

View File

@ -2,33 +2,29 @@ import { createContext, ReactNode, useContext, useEffect, useRef } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { createStore, StateCreator, StoreApi, useStore } from "zustand"; import { createStore, StateCreator, StoreApi, useStore } from "zustand";
import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
// akhilmhdh: Don't remove this file if ur thinking why use zustand just for selected selects state // akhilmhdh: Don't remove this file if ur thinking why use zustand just for selected selects state
// This is first step and the whole secret crud will be moved to this global page scope state // This is first step and the whole secret crud will be moved to this global page scope state
// this will allow more stuff like undo grouping stuffs etc // this will allow more stuff like undo grouping stuffs etc
type SelectedSecretState = { type SelectedSecretState = {
selectedSecret: Record<string, SecretV3RawSanitized>; selectedSecret: Record<string, boolean>;
action: { action: {
toggle: (secret: SecretV3RawSanitized) => void; toggle: (id: string) => void;
reset: () => void; reset: () => void;
set: (secrets: Record<string, SecretV3RawSanitized>) => void;
}; };
}; };
const createSelectedSecretStore: StateCreator<SelectedSecretState> = (set) => ({ const createSelectedSecretStore: StateCreator<SelectedSecretState> = (set) => ({
selectedSecret: {}, selectedSecret: {},
action: { action: {
toggle: (secret) => toggle: (id) =>
set((state) => { set((state) => {
const isChecked = Boolean(state.selectedSecret?.[secret.id]); const isChecked = Boolean(state.selectedSecret?.[id]);
const newChecks = { ...state.selectedSecret }; const newChecks = { ...state.selectedSecret };
// remove selection if its present else add it // remove selection if its present else add it
if (isChecked) delete newChecks[secret.id]; if (isChecked) delete newChecks[id];
else newChecks[secret.id] = secret; else newChecks[id] = true;
return { selectedSecret: newChecks }; return { selectedSecret: newChecks };
}), }),
reset: () => set({ selectedSecret: {} }), reset: () => set({ selectedSecret: {} })
set: (secrets) => set({ selectedSecret: secrets })
} }
}); });

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { subject } from "@casl/ability"; import { subject } from "@casl/ability";
@ -9,7 +9,7 @@ import { twMerge } from "tailwind-merge";
import NavHeader from "@app/components/navigation/NavHeader"; import NavHeader from "@app/components/navigation/NavHeader";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { PermissionDeniedBanner } from "@app/components/permissions"; import { PermissionDeniedBanner } from "@app/components/permissions";
import { Checkbox, ContentLoader, Pagination, Tooltip } from "@app/components/v2"; import { ContentLoader, Pagination } from "@app/components/v2";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
@ -39,11 +39,7 @@ import { PitDrawer } from "./components/PitDrawer";
import { SecretDropzone } from "./components/SecretDropzone"; import { SecretDropzone } from "./components/SecretDropzone";
import { SecretListView } from "./components/SecretListView"; import { SecretListView } from "./components/SecretListView";
import { SnapshotView } from "./components/SnapshotView"; import { SnapshotView } from "./components/SnapshotView";
import { import { StoreProvider } from "./SecretMainPage.store";
StoreProvider,
useSelectedSecretActions,
useSelectedSecrets
} from "./SecretMainPage.store";
import { Filter, RowType } from "./SecretMainPage.types"; import { Filter, RowType } from "./SecretMainPage.types";
const LOADER_TEXT = [ const LOADER_TEXT = [
@ -52,7 +48,7 @@ const LOADER_TEXT = [
"Getting secret import links..." "Getting secret import links..."
]; ];
const SecretMainPageContent = () => { export const SecretMainPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { currentWorkspace, isLoading: isWorkspaceLoading } = useWorkspace(); const { currentWorkspace, isLoading: isWorkspaceLoading } = useWorkspace();
const router = useRouter(); const router = useRouter();
@ -287,33 +283,6 @@ const SecretMainPageContent = () => {
} }
}, [secretPath]); }, [secretPath]);
const selectedSecrets = useSelectedSecrets();
const selectedSecretActions = useSelectedSecretActions();
const allRowsSelectedOnPage = useMemo(() => {
if (secrets?.every((secret) => selectedSecrets[secret.id]))
return { isChecked: true, isIndeterminate: false };
if (secrets?.some((secret) => selectedSecrets[secret.id]))
return { isChecked: true, isIndeterminate: true };
return { isChecked: false, isIndeterminate: false };
}, [selectedSecrets, secrets]);
const toggleSelectAllRows = () => {
const newChecks = { ...selectedSecrets };
secrets?.forEach((secret) => {
if (allRowsSelectedOnPage.isChecked) {
delete newChecks[secret.id];
} else {
newChecks[secret.id] = secret;
}
});
selectedSecretActions.set(newChecks);
};
if (isDetailsLoading) { if (isDetailsLoading) {
return <ContentLoader text={LOADER_TEXT} />; return <ContentLoader text={LOADER_TEXT} />;
} }
@ -331,6 +300,7 @@ const SecretMainPageContent = () => {
}; };
return ( return (
<StoreProvider>
<div className="container mx-auto flex flex-col px-6 text-mineshaft-50 dark:[color-scheme:dark]"> <div className="container mx-auto flex flex-col px-6 text-mineshaft-50 dark:[color-scheme:dark]">
<SecretV2MigrationSection /> <SecretV2MigrationSection />
<div className="relative right-6 -top-2 mb-2 ml-6"> <div className="relative right-6 -top-2 mb-2 ml-6">
@ -349,6 +319,7 @@ const SecretMainPageContent = () => {
{!isRollbackMode ? ( {!isRollbackMode ? (
<> <>
<ActionBar <ActionBar
secrets={secrets}
environment={environment} environment={environment}
workspaceId={workspaceId} workspaceId={workspaceId}
projectSlug={projectSlug} projectSlug={projectSlug}
@ -372,6 +343,7 @@ const SecretMainPageContent = () => {
"sticky top-0 flex border-b border-mineshaft-600 bg-mineshaft-800 font-medium" "sticky top-0 flex border-b border-mineshaft-600 bg-mineshaft-800 font-medium"
)} )}
> >
<div style={{ width: "2.8rem" }} className="flex-shrink-0 px-4 py-3" />
<div <div
className="flex w-80 flex-shrink-0 items-center border-r border-mineshaft-600 px-4 py-2" className="flex w-80 flex-shrink-0 items-center border-r border-mineshaft-600 px-4 py-2"
role="button" role="button"
@ -381,27 +353,6 @@ const SecretMainPageContent = () => {
if (evt.key === "Enter") handleSortToggle(); if (evt.key === "Enter") handleSortToggle();
}} }}
> >
<Tooltip
className="max-w-[20rem] whitespace-nowrap"
content={
totalCount > 0
? `${
!allRowsSelectedOnPage.isChecked ? "Select" : "Unselect"
} all secrets on page`
: ""
}
>
<div className="mr-6 ml-1">
<Checkbox
isDisabled={totalCount === 0}
id="checkbox-select-all-rows"
onClick={(e) => e.stopPropagation()}
isChecked={allRowsSelectedOnPage.isChecked}
isIndeterminate={allRowsSelectedOnPage.isIndeterminate}
onCheckedChange={toggleSelectAllRows}
/>
</div>
</Tooltip>
Key Key
<FontAwesomeIcon <FontAwesomeIcon
icon={orderDirection === OrderByDirection.ASC ? faArrowDown : faArrowUp} icon={orderDirection === OrderByDirection.ASC ? faArrowDown : faArrowUp}
@ -512,11 +463,6 @@ const SecretMainPageContent = () => {
/> />
)} )}
</div> </div>
</StoreProvider>
); );
}; };
export const SecretMainPage = () => (
<StoreProvider>
<SecretMainPageContent />
</StoreProvider>
);

View File

@ -54,7 +54,7 @@ import {
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useCreateFolder, useDeleteSecretBatch, useMoveSecrets } from "@app/hooks/api"; import { useCreateFolder, useDeleteSecretBatch, useMoveSecrets } from "@app/hooks/api";
import { fetchProjectSecrets } from "@app/hooks/api/secrets/queries"; import { fetchProjectSecrets } from "@app/hooks/api/secrets/queries";
import { SecretType, WsTag } from "@app/hooks/api/types"; import { SecretType, SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
import { import {
PopUpNames, PopUpNames,
@ -69,7 +69,8 @@ import { FolderForm } from "./FolderForm";
import { MoveSecretsModal } from "./MoveSecretsModal"; import { MoveSecretsModal } from "./MoveSecretsModal";
type Props = { type Props = {
// switch the secrets type as it gets decrypted after api call secrets?: SecretV3RawSanitized[];
// swtich the secrets type as it gets decrypted after api call
environment: string; environment: string;
// @depreciated will be moving all these details to zustand // @depreciated will be moving all these details to zustand
workspaceId: string; workspaceId: string;
@ -88,6 +89,7 @@ type Props = {
}; };
export const ActionBar = ({ export const ActionBar = ({
secrets = [],
environment, environment,
workspaceId, workspaceId,
projectSlug, projectSlug,
@ -200,7 +202,7 @@ export const ActionBar = ({
}; };
const handleSecretBulkDelete = async () => { const handleSecretBulkDelete = async () => {
const bulkDeletedSecrets = Object.values(selectedSecrets); const bulkDeletedSecrets = secrets.filter(({ id }) => Boolean(selectedSecrets?.[id]));
try { try {
await deleteBatchSecretV3({ await deleteBatchSecretV3({
secretPath, secretPath,
@ -233,7 +235,7 @@ export const ActionBar = ({
shouldOverwrite: boolean; shouldOverwrite: boolean;
}) => { }) => {
try { try {
const secretsToMove = Object.values(selectedSecrets); const secretsToMove = secrets.filter(({ id }) => Boolean(selectedSecrets?.[id]));
const { isDestinationUpdated, isSourceUpdated } = await moveSecrets({ const { isDestinationUpdated, isSourceUpdated } = await moveSecrets({
projectSlug, projectSlug,
shouldOverwrite, shouldOverwrite,
@ -551,7 +553,7 @@ export const ActionBar = ({
<FontAwesomeIcon icon={faMinusSquare} size="lg" /> <FontAwesomeIcon icon={faMinusSquare} size="lg" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<div className="ml-2 flex-grow px-2 text-sm"> <div className="ml-4 flex-grow px-2 text-sm">
{Object.keys(selectedSecrets).length} Selected {Object.keys(selectedSecrets).length} Selected
</div> </div>
<ProjectPermissionCan <ProjectPermissionCan

View File

@ -158,17 +158,17 @@ export const SecretImportItem = ({
} }
}} }}
> >
<div className="flex w-11 items-center py-2 pl-5 text-green-700"> <div className="flex w-12 items-center px-4 py-2 text-green-700">
<FontAwesomeIcon icon={faFileImport} /> <FontAwesomeIcon icon={faFileImport} />
</div> </div>
<div className="flex flex-grow items-center py-2 pl-4 pr-2"> <div className="flex flex-grow items-center px-4 py-2">
<EnvFolderIcon <EnvFolderIcon
env={importEnv.slug || ""} env={importEnv.slug || ""}
secretPath={secretImport?.importPath || ""} secretPath={secretImport?.importPath || ""}
// isReplication={isReplication} // isReplication={isReplication}
/> />
</div> </div>
<div className="flex items-center space-x-4 py-2 pr-4"> <div className="flex items-center space-x-4 px-4 py-2">
{lastReplicated && ( {lastReplicated && (
<Tooltip <Tooltip
position="left" position="left"

View File

@ -56,7 +56,7 @@ type Props = {
onDetailViewSecret: (sec: SecretV3RawSanitized) => void; onDetailViewSecret: (sec: SecretV3RawSanitized) => void;
isVisible?: boolean; isVisible?: boolean;
isSelected?: boolean; isSelected?: boolean;
onToggleSecretSelect: (secret: SecretV3RawSanitized) => void; onToggleSecretSelect: (id: string) => void;
tags: WsTag[]; tags: WsTag[];
onCreateTag: () => void; onCreateTag: () => void;
environment: string; environment: string;
@ -218,7 +218,7 @@ export const SecretItem = memo(
<Checkbox <Checkbox
id={`checkbox-${secret.id}`} id={`checkbox-${secret.id}`}
isChecked={isSelected} isChecked={isSelected}
onCheckedChange={() => onToggleSecretSelect(secret)} onCheckedChange={() => onToggleSecretSelect(secret.id)}
className={twMerge("ml-3 hidden group-hover:flex", isSelected && "flex")} className={twMerge("ml-3 hidden group-hover:flex", isSelected && "flex")}
/> />
<FontAwesomeSymbol <FontAwesomeSymbol

View File

@ -304,7 +304,7 @@ export const SecretListView = ({
environment={environment} environment={environment}
secretPath={secretPath} secretPath={secretPath}
tags={wsTags} tags={wsTags}
isSelected={Boolean(selectedSecrets?.[secret.id])} isSelected={selectedSecrets?.[secret.id]}
onToggleSecretSelect={toggleSelectedSecret} onToggleSecretSelect={toggleSelectedSecret}
isVisible={isVisible} isVisible={isVisible}
secret={secret} secret={secret}

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
@ -25,7 +25,6 @@ import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { import {
Button, Button,
Checkbox,
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
@ -68,7 +67,7 @@ import { DashboardSecretsOrderBy } from "@app/hooks/api/dashboard/types";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries"; import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries";
import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types"; import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types";
import { SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/types"; import { SecretType, TSecretFolder } from "@app/hooks/api/types";
import { ProjectVersion } from "@app/hooks/api/workspace/types"; import { ProjectVersion } from "@app/hooks/api/workspace/types";
import { useDynamicSecretOverview, useFolderOverview, useSecretOverview } from "@app/hooks/utils"; import { useDynamicSecretOverview, useFolderOverview, useSecretOverview } from "@app/hooks/utils";
import { SecretOverviewDynamicSecretRow } from "@app/views/SecretOverviewPage/components/SecretOverviewDynamicSecretRow"; import { SecretOverviewDynamicSecretRow } from "@app/views/SecretOverviewPage/components/SecretOverviewDynamicSecretRow";
@ -107,10 +106,18 @@ export const SecretOverviewPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const router = useRouter(); const router = useRouter();
const [scrollOffset, setScrollOffset] = useState(0); // this is to set expandable table width
const [debouncedScrollOffset] = useDebounce(scrollOffset); // coz when overflow the table goes to the right
const parentTableRef = useRef<HTMLTableElement>(null);
const [expandableTableWidth, setExpandableTableWidth] = useState(0);
const { permission } = useProjectPermission(); const { permission } = useProjectPermission();
useEffect(() => {
if (parentTableRef.current) {
setExpandableTableWidth(parentTableRef.current.clientWidth);
}
}, [parentTableRef.current]);
const { currentWorkspace, isLoading: isWorkspaceLoading } = useWorkspace(); const { currentWorkspace, isLoading: isWorkspaceLoading } = useWorkspace();
const isProjectV3 = currentWorkspace?.version === ProjectVersion.V3; const isProjectV3 = currentWorkspace?.version === ProjectVersion.V3;
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
@ -126,9 +133,8 @@ export const SecretOverviewPage = () => {
>(new Map()); >(new Map());
const [selectedEntries, setSelectedEntries] = useState<{ const [selectedEntries, setSelectedEntries] = useState<{
// selectedEntries[name/key][envSlug][resource] [EntryType.FOLDER]: Record<string, boolean>;
[EntryType.FOLDER]: Record<string, Record<string, TSecretFolder>>; [EntryType.SECRET]: Record<string, boolean>;
[EntryType.SECRET]: Record<string, Record<string, SecretV3RawSanitized>>;
}>({ }>({
[EntryType.FOLDER]: {}, [EntryType.FOLDER]: {},
[EntryType.SECRET]: {} [EntryType.SECRET]: {}
@ -146,6 +152,23 @@ export const SecretOverviewPage = () => {
orderBy orderBy
} = usePagination<DashboardSecretsOrderBy>(DashboardSecretsOrderBy.Name); } = usePagination<DashboardSecretsOrderBy>(DashboardSecretsOrderBy.Name);
const toggleSelectedEntry = useCallback(
(type: EntryType, key: string) => {
const isChecked = Boolean(selectedEntries[type]?.[key]);
const newChecks = { ...selectedEntries };
// remove selection if its present else add it
if (isChecked) {
delete newChecks[type][key];
} else {
newChecks[type][key] = true;
}
setSelectedEntries(newChecks);
},
[selectedEntries]
);
const resetSelectedEntries = useCallback(() => { const resetSelectedEntries = useCallback(() => {
setSelectedEntries({ setSelectedEntries({
[EntryType.FOLDER]: {}, [EntryType.FOLDER]: {},
@ -154,13 +177,19 @@ export const SecretOverviewPage = () => {
}, []); }, []);
useEffect(() => { useEffect(() => {
const handleParentTableWidthResize = () => {
setExpandableTableWidth(parentTableRef.current?.clientWidth || 0);
};
const onRouteChangeStart = () => { const onRouteChangeStart = () => {
resetSelectedEntries(); resetSelectedEntries();
}; };
router.events.on("routeChangeStart", onRouteChangeStart); router.events.on("routeChangeStart", onRouteChangeStart);
window.addEventListener("resize", handleParentTableWidthResize);
return () => { return () => {
window.removeEventListener("resize", handleParentTableWidthResize);
router.events.off("routeChangeStart", onRouteChangeStart); router.events.off("routeChangeStart", onRouteChangeStart);
}; };
}, []); }, []);
@ -522,83 +551,6 @@ export const SecretOverviewPage = () => {
[] []
); );
const allRowsSelectedOnPage = useMemo(() => {
if (
(!secrets?.length ||
secrets?.every((secret) => selectedEntries[EntryType.SECRET][secret.key])) &&
(!folders?.length ||
folders?.every((folder) => selectedEntries[EntryType.FOLDER][folder.name]))
)
return { isChecked: true, isIndeterminate: false };
if (
secrets?.some((secret) => selectedEntries[EntryType.SECRET][secret.key]) ||
folders?.some((folder) => selectedEntries[EntryType.FOLDER][folder.name])
)
return { isChecked: true, isIndeterminate: true };
return { isChecked: false, isIndeterminate: false };
}, [selectedEntries, secrets, folders]);
const toggleSelectedEntry = useCallback(
(type: EntryType, key: string) => {
const isChecked = Boolean(selectedEntries[type]?.[key]);
const newChecks = { ...selectedEntries };
// remove selection if its present else add it
if (isChecked) {
delete newChecks[type][key];
} else {
newChecks[type][key] = {};
userAvailableEnvs.forEach((env) => {
const resource =
type === EntryType.SECRET
? getSecretByKey(env.slug, key)
: getFolderByNameAndEnv(key, env.slug);
if (resource) newChecks[type][key][env.slug] = resource;
});
}
setSelectedEntries(newChecks);
},
[selectedEntries, getFolderByNameAndEnv, getSecretByKey]
);
const toggleSelectAllRows = () => {
const newChecks = { ...selectedEntries };
userAvailableEnvs.forEach((env) => {
secrets?.forEach((secret) => {
if (allRowsSelectedOnPage.isChecked) {
delete newChecks[EntryType.SECRET][secret.key];
} else {
if (!newChecks[EntryType.SECRET][secret.key])
newChecks[EntryType.SECRET][secret.key] = {};
const resource = getSecretByKey(env.slug, secret.key);
if (resource) newChecks[EntryType.SECRET][secret.key][env.slug] = resource;
}
});
folders?.forEach((folder) => {
if (allRowsSelectedOnPage.isChecked) {
delete newChecks[EntryType.FOLDER][folder.name];
} else {
if (!newChecks[EntryType.FOLDER][folder.name])
newChecks[EntryType.FOLDER][folder.name] = {};
const resource = getFolderByNameAndEnv(folder.name, env.slug);
if (resource) newChecks[EntryType.FOLDER][folder.name][env.slug] = resource;
}
});
});
setSelectedEntries(newChecks);
};
if (isWorkspaceLoading || (isProjectV3 && isOverviewLoading)) { if (isWorkspaceLoading || (isProjectV3 && isOverviewLoading)) {
return ( return (
<div className="container mx-auto flex h-screen w-full items-center justify-center px-8 text-mineshaft-50 dark:[color-scheme:dark]"> <div className="container mx-auto flex h-screen w-full items-center justify-center px-8 text-mineshaft-50 dark:[color-scheme:dark]">
@ -847,39 +799,18 @@ export const SecretOverviewPage = () => {
</div> </div>
<SelectionPanel <SelectionPanel
secretPath={secretPath} secretPath={secretPath}
getSecretByKey={getSecretByKey}
getFolderByNameAndEnv={getFolderByNameAndEnv}
selectedEntries={selectedEntries} selectedEntries={selectedEntries}
resetSelectedEntries={resetSelectedEntries} resetSelectedEntries={resetSelectedEntries}
/> />
<div className="thin-scrollbar mt-4"> <div className="thin-scrollbar mt-4" ref={parentTableRef}>
<TableContainer <TableContainer className="rounded-b-none">
onScroll={(e) => setScrollOffset(e.currentTarget.scrollLeft)}
className="thin-scrollbar"
>
<Table> <Table>
<THead> <THead>
<Tr className="sticky top-0 z-20 border-0"> <Tr className="sticky top-0 z-20 border-0">
<Th className="sticky left-0 z-20 min-w-[20rem] border-b-0 p-0"> <Th className="sticky left-0 z-20 min-w-[20rem] border-b-0 p-0">
<div className="flex items-center border-b border-r border-mineshaft-600 pr-5 pl-3 pt-3.5 pb-3"> <div className="flex items-center border-b border-r border-mineshaft-600 px-5 pt-3.5 pb-3">
<Tooltip
className="max-w-[20rem] whitespace-nowrap capitalize"
content={
totalCount > 0
? `${
!allRowsSelectedOnPage.isChecked ? "Select" : "Unselect"
} all folders and secrets on page`
: ""
}
>
<div className="mr-4 ml-2">
<Checkbox
isDisabled={totalCount === 0}
id="checkbox-select-all-rows"
isChecked={allRowsSelectedOnPage.isChecked}
isIndeterminate={allRowsSelectedOnPage.isIndeterminate}
onCheckedChange={toggleSelectAllRows}
/>
</div>
</Tooltip>
Name Name
<IconButton <IconButton
variant="plain" variant="plain"
@ -1007,7 +938,7 @@ export const SecretOverviewPage = () => {
<SecretOverviewFolderRow <SecretOverviewFolderRow
folderName={folderName} folderName={folderName}
isFolderPresentInEnv={isFolderPresentInEnv} isFolderPresentInEnv={isFolderPresentInEnv}
isSelected={Boolean(selectedEntries.folder[folderName])} isSelected={selectedEntries.folder[folderName]}
onToggleFolderSelect={() => onToggleFolderSelect={() =>
toggleSelectedEntry(EntryType.FOLDER, folderName) toggleSelectedEntry(EntryType.FOLDER, folderName)
} }
@ -1029,7 +960,7 @@ export const SecretOverviewPage = () => {
))} ))}
{secKeys.map((key, index) => ( {secKeys.map((key, index) => (
<SecretOverviewTableRow <SecretOverviewTableRow
isSelected={Boolean(selectedEntries.secret[key])} isSelected={selectedEntries.secret[key]}
onToggleSecretSelect={() => toggleSelectedEntry(EntryType.SECRET, key)} onToggleSecretSelect={() => toggleSelectedEntry(EntryType.SECRET, key)}
secretPath={secretPath} secretPath={secretPath}
getImportedSecretByKey={getImportedSecretByKey} getImportedSecretByKey={getImportedSecretByKey}
@ -1041,7 +972,7 @@ export const SecretOverviewPage = () => {
environments={visibleEnvs} environments={visibleEnvs}
secretKey={key} secretKey={key}
getSecretByKey={getSecretByKey} getSecretByKey={getSecretByKey}
scrollOffset={debouncedScrollOffset} expandableColWidth={expandableTableWidth}
/> />
))} ))}
</> </>

View File

@ -23,6 +23,7 @@ type Props = {
secretKey: string; secretKey: string;
secretPath: string; secretPath: string;
environments: { name: string; slug: string }[]; environments: { name: string; slug: string }[];
expandableColWidth: number;
isSelected: boolean; isSelected: boolean;
onToggleSecretSelect: (key: string) => void; onToggleSecretSelect: (key: string) => void;
getSecretByKey: (slug: string, key: string) => SecretV3RawSanitized | undefined; getSecretByKey: (slug: string, key: string) => SecretV3RawSanitized | undefined;
@ -40,7 +41,6 @@ type Props = {
env: string, env: string,
secretName: string secretName: string
) => { secret?: SecretV3RawSanitized; environmentInfo?: WorkspaceEnv } | undefined; ) => { secret?: SecretV3RawSanitized; environmentInfo?: WorkspaceEnv } | undefined;
scrollOffset: number;
}; };
export const SecretOverviewTableRow = ({ export const SecretOverviewTableRow = ({
@ -53,7 +53,9 @@ export const SecretOverviewTableRow = ({
onSecretDelete, onSecretDelete,
isImportedSecretPresentInEnv, isImportedSecretPresentInEnv,
getImportedSecretByKey, getImportedSecretByKey,
scrollOffset, // temporary until below todo is resolved
// eslint-disable-next-line @typescript-eslint/no-unused-vars
expandableColWidth,
onToggleSecretSelect, onToggleSecretSelect,
isSelected isSelected
}: Props) => { }: Props) => {
@ -150,11 +152,11 @@ export const SecretOverviewTableRow = ({
}`} }`}
> >
<div <div
className="ml-2 p-2" className="ml-2 w-[99%] p-2"
style={{ // TODO: scott expandableColWidth sometimes 0 due to parent ref not mounting, opting for relative width until resolved
marginLeft: scrollOffset, // style={{
width: "calc(100vw - 290px)" // 290px accounts for sidebar and margin // width: `calc(${expandableColWidth} - 1rem)`
}} // }}
> >
<SecretRenameRow <SecretRenameRow
secretKey={secretKey} secretKey={secretKey}

View File

@ -27,23 +27,31 @@ export enum EntryType {
type Props = { type Props = {
secretPath: string; secretPath: string;
getSecretByKey: (slug: string, key: string) => SecretV3RawSanitized | undefined;
getFolderByNameAndEnv: (name: string, env: string) => TSecretFolder | undefined;
resetSelectedEntries: () => void; resetSelectedEntries: () => void;
selectedEntries: { selectedEntries: {
[EntryType.FOLDER]: Record<string, Record<string, TSecretFolder>>; [EntryType.FOLDER]: Record<string, boolean>;
[EntryType.SECRET]: Record<string, Record<string, SecretV3RawSanitized>>; [EntryType.SECRET]: Record<string, boolean>;
}; };
}; };
export const SelectionPanel = ({ secretPath, resetSelectedEntries, selectedEntries }: Props) => { export const SelectionPanel = ({
getFolderByNameAndEnv,
getSecretByKey,
secretPath,
resetSelectedEntries,
selectedEntries
}: Props) => {
const { permission } = useProjectPermission(); const { permission } = useProjectPermission();
const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([ const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([
"bulkDeleteEntries" "bulkDeleteEntries"
] as const); ] as const);
const selectedFolderCount = Object.keys(selectedEntries.folder).length; const selectedFolderCount = Object.keys(selectedEntries.folder).length
const selectedKeysCount = Object.keys(selectedEntries.secret).length; const selectedKeysCount = Object.keys(selectedEntries.secret).length
const selectedCount = selectedFolderCount + selectedKeysCount; const selectedCount = selectedFolderCount + selectedKeysCount
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const workspaceId = currentWorkspace?.id || ""; const workspaceId = currentWorkspace?.id || "";
@ -69,7 +77,7 @@ export const SelectionPanel = ({ secretPath, resetSelectedEntries, selectedEntri
return "Do you want to delete the selected secrets across environments?"; return "Do you want to delete the selected secrets across environments?";
} }
return "Do you want to delete the selected folders across environments?"; return "Do you want to delete the selected folders across environments?";
}; }
const handleBulkDelete = async () => { const handleBulkDelete = async () => {
let processedEntries = 0; let processedEntries = 0;
@ -86,8 +94,8 @@ export const SelectionPanel = ({ secretPath, resetSelectedEntries, selectedEntri
} }
await Promise.all( await Promise.all(
Object.values(selectedEntries.folder).map(async (folderRecord) => { Object.keys(selectedEntries.folder).map(async (folderName) => {
const folder = folderRecord[env.slug]; const folder = getFolderByNameAndEnv(folderName, env.slug);
if (folder) { if (folder) {
processedEntries += 1; processedEntries += 1;
await deleteFolder({ await deleteFolder({
@ -100,9 +108,9 @@ export const SelectionPanel = ({ secretPath, resetSelectedEntries, selectedEntri
}) })
); );
const secretsToDelete = Object.values(selectedEntries.secret).reduce( const secretsToDelete = Object.keys(selectedEntries.secret).reduce(
(accum: TDeleteSecretBatchDTO["secrets"], secretRecord) => { (accum: TDeleteSecretBatchDTO["secrets"], secretName) => {
const entry = secretRecord[env.slug]; const entry = getSecretByKey(env.slug, secretName);
if (entry) { if (entry) {
return [ return [
...accum, ...accum,
@ -165,7 +173,7 @@ export const SelectionPanel = ({ secretPath, resetSelectedEntries, selectedEntri
<FontAwesomeIcon icon={faMinusSquare} size="lg" /> <FontAwesomeIcon icon={faMinusSquare} size="lg" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<div className="ml-1 flex-grow px-2 text-sm">{selectedCount} Selected</div> <div className="ml-4 flex-grow px-2 text-sm">{selectedCount} Selected</div>
{shouldShowDelete && ( {shouldShowDelete && (
<Button <Button
variant="outline_bg" variant="outline_bg"

View File

@ -13,9 +13,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: v0.7.2 version: v0.7.3
# This is the version number of the application being deployed. This version number should be # This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to # incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using. # follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes. # It is recommended to use it with quotes.
appVersion: "v0.7.2" appVersion: "v0.7.3"

View File

@ -32,7 +32,7 @@ controllerManager:
- ALL - ALL
image: image:
repository: infisical/kubernetes-operator repository: infisical/kubernetes-operator
tag: v0.7.2 tag: v0.7.3
resources: resources:
limits: limits:
cpu: 500m cpu: 500m

View File

@ -39,6 +39,7 @@ type InfisicalSecretReconciler struct {
type ResourceVariables struct { type ResourceVariables struct {
infisicalClient infisicalSdk.InfisicalClientInterface infisicalClient infisicalSdk.InfisicalClientInterface
cancelCtx context.CancelFunc
authDetails AuthenticationDetails authDetails AuthenticationDetails
} }
@ -136,11 +137,17 @@ func (r *InfisicalSecretReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr). return ctrl.NewControllerManagedBy(mgr).
For(&secretsv1alpha1.InfisicalSecret{}, builder.WithPredicates(predicate.Funcs{ For(&secretsv1alpha1.InfisicalSecret{}, builder.WithPredicates(predicate.Funcs{
UpdateFunc: func(e event.UpdateEvent) bool { UpdateFunc: func(e event.UpdateEvent) bool {
if rv, ok := resourceVariablesMap[string(e.ObjectNew.GetUID())]; ok {
rv.cancelCtx()
delete(resourceVariablesMap, string(e.ObjectNew.GetUID())) delete(resourceVariablesMap, string(e.ObjectNew.GetUID()))
}
return true return true
}, },
DeleteFunc: func(e event.DeleteEvent) bool { DeleteFunc: func(e event.DeleteEvent) bool {
if rv, ok := resourceVariablesMap[string(e.Object.GetUID())]; ok {
rv.cancelCtx()
delete(resourceVariablesMap, string(e.Object.GetUID())) delete(resourceVariablesMap, string(e.Object.GetUID()))
}
return true return true
}, },
})). })).

View File

@ -293,13 +293,16 @@ func (r *InfisicalSecretReconciler) GetResourceVariables(infisicalSecret v1alpha
if _, ok := resourceVariablesMap[string(infisicalSecret.UID)]; !ok { if _, ok := resourceVariablesMap[string(infisicalSecret.UID)]; !ok {
client := infisicalSdk.NewInfisicalClient(infisicalSdk.Config{ ctx, cancel := context.WithCancel(context.Background())
client := infisicalSdk.NewInfisicalClient(ctx, infisicalSdk.Config{
SiteUrl: api.API_HOST_URL, SiteUrl: api.API_HOST_URL,
UserAgent: api.USER_AGENT_NAME, UserAgent: api.USER_AGENT_NAME,
}) })
resourceVariablesMap[string(infisicalSecret.UID)] = ResourceVariables{ resourceVariablesMap[string(infisicalSecret.UID)] = ResourceVariables{
infisicalClient: client, infisicalClient: client,
cancelCtx: cancel,
authDetails: AuthenticationDetails{}, authDetails: AuthenticationDetails{},
} }
@ -321,6 +324,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
resourceVariables := r.GetResourceVariables(infisicalSecret) resourceVariables := r.GetResourceVariables(infisicalSecret)
infisicalClient := resourceVariables.infisicalClient infisicalClient := resourceVariables.infisicalClient
cancelCtx := resourceVariables.cancelCtx
authDetails := resourceVariables.authDetails authDetails := resourceVariables.authDetails
var err error var err error
@ -335,6 +339,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
r.UpdateResourceVariables(infisicalSecret, ResourceVariables{ r.UpdateResourceVariables(infisicalSecret, ResourceVariables{
infisicalClient: infisicalClient, infisicalClient: infisicalClient,
cancelCtx: cancelCtx,
authDetails: authDetails, authDetails: authDetails,
}) })
} }

View File

@ -3,7 +3,7 @@ module github.com/Infisical/infisical/k8-operator
go 1.21 go 1.21
require ( require (
github.com/infisical/go-sdk v0.3.2 github.com/infisical/go-sdk v0.3.7
github.com/onsi/ginkgo/v2 v2.6.0 github.com/onsi/ginkgo/v2 v2.6.0
github.com/onsi/gomega v1.24.1 github.com/onsi/gomega v1.24.1
k8s.io/apimachinery v0.26.1 k8s.io/apimachinery v0.26.1
@ -12,10 +12,10 @@ require (
) )
require ( require (
cloud.google.com/go/auth v0.6.1 // indirect cloud.google.com/go/auth v0.7.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
cloud.google.com/go/compute/metadata v0.4.0 // indirect cloud.google.com/go/compute/metadata v0.4.0 // indirect
cloud.google.com/go/iam v1.1.10 // indirect cloud.google.com/go/iam v1.1.11 // indirect
github.com/aws/aws-sdk-go-v2 v1.30.1 // indirect github.com/aws/aws-sdk-go-v2 v1.30.1 // indirect
github.com/aws/aws-sdk-go-v2/config v1.27.24 // indirect github.com/aws/aws-sdk-go-v2/config v1.27.24 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.24 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.24 // indirect
@ -41,7 +41,7 @@ require (
go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect
golang.org/x/sync v0.7.0 // indirect golang.org/x/sync v0.7.0 // indirect
google.golang.org/api v0.187.0 // indirect google.golang.org/api v0.188.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240708141625-4ad9e859172b // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240708141625-4ad9e859172b // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b // indirect
google.golang.org/grpc v1.65.0 // indirect google.golang.org/grpc v1.65.0 // indirect

View File

@ -13,8 +13,8 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/auth v0.6.1 h1:T0Zw1XM5c1GlpN2HYr2s+m3vr1p2wy+8VN+Z1FKxW38= cloud.google.com/go/auth v0.7.0 h1:kf/x9B3WTbBUHkC+1VS8wwwli9TzhSt0vSTVBmMR8Ts=
cloud.google.com/go/auth v0.6.1/go.mod h1:eFHG7zDzbXHKmjJddFG/rBlcGp6t25SwRUiEQSlO4x4= cloud.google.com/go/auth v0.7.0/go.mod h1:D+WqdrpcjmiCgWrXmLLxOVq1GACoE36chW6KXoEvuIw=
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
@ -27,8 +27,8 @@ cloud.google.com/go/compute/metadata v0.4.0 h1:vHzJCWaM4g8XIcm8kopr3XmDA4Gy/lblD
cloud.google.com/go/compute/metadata v0.4.0/go.mod h1:SIQh1Kkb4ZJ8zJ874fqVkslA29PRXuleyj6vOzlbK7M= cloud.google.com/go/compute/metadata v0.4.0/go.mod h1:SIQh1Kkb4ZJ8zJ874fqVkslA29PRXuleyj6vOzlbK7M=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/iam v1.1.10 h1:ZSAr64oEhQSClwBL670MsJAW5/RLiC6kfw3Bqmd5ZDI= cloud.google.com/go/iam v1.1.11 h1:0mQ8UKSfdHLut6pH9FM3bI55KWR46ketn0PuXleDyxw=
cloud.google.com/go/iam v1.1.10/go.mod h1:iEgMq62sg8zx446GCaijmA2Miwg5o3UbO+nI47WHJps= cloud.google.com/go/iam v1.1.11/go.mod h1:biXoiLWYIKntto2joP+62sd9uW5EpkZmKIvfNcTWlnQ=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@ -217,8 +217,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/infisical/go-sdk v0.3.2 h1:BfeQzG7s3qmEGhgXu0d1YNsyaiHucHgI+BaLpx+W8cc= github.com/infisical/go-sdk v0.3.7 h1:EE0ALjjdJtNvDzHtxotkBxYZ6L9ZmeruH89u6jh1Bik=
github.com/infisical/go-sdk v0.3.2/go.mod h1:vHTDVw3k+wfStXab513TGk1n53kaKF2xgLqpw/xvtl4= github.com/infisical/go-sdk v0.3.7/go.mod h1:HHW7DgUqoolyQIUw/9HdpkZ3bDLwWyZ0HEtYiVaDKQw=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
@ -601,8 +601,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.187.0 h1:Mxs7VATVC2v7CY+7Xwm4ndkX71hpElcvx0D1Ji/p1eo= google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw=
google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk= google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=