diff --git a/charts/console-rapid/charts/controller-0.0.201.tgz b/charts/console-rapid/charts/controller-0.0.201.tgz index a0b495bf36..57e85f60f7 100644 Binary files a/charts/console-rapid/charts/controller-0.0.201.tgz and b/charts/console-rapid/charts/controller-0.0.201.tgz differ diff --git a/charts/console/charts/controller-0.0.201.tgz b/charts/console/charts/controller-0.0.201.tgz index 74d300f23d..0938b3edd8 100644 Binary files a/charts/console/charts/controller-0.0.201.tgz and b/charts/console/charts/controller-0.0.201.tgz differ diff --git a/charts/controller/crds/deployments.plural.sh_personas.yaml b/charts/controller/crds/deployments.plural.sh_personas.yaml index b056d1ed59..9b1e246490 100644 --- a/charts/controller/crds/deployments.plural.sh_personas.yaml +++ b/charts/controller/crds/deployments.plural.sh_personas.yaml @@ -160,11 +160,6 @@ spec: Permissions enables the permissions action in the flow header when set to true. This allows personas to manage flow permissions from the flow detail page. type: boolean - startWorkbenchJob: - description: |- - StartWorkbenchJob enables the "Start workbench job" action in the flow header when set to true. - This allows personas to launch workbench jobs directly from the flow detail page. - type: boolean pipelines: description: |- Pipelines enables access to flow pipeline features when set to true. @@ -175,6 +170,11 @@ spec: Previews enables access to flow preview environment features when set to true. This includes viewing and managing preview environments associated with flows. type: boolean + startWorkbenchJob: + description: |- + StartWorkbenchJob enables the "Start workbench job" action in the flow header when set to true. + This allows personas to launch workbench jobs directly from the flow detail page. + type: boolean workbenches: description: |- Workbenches enables access to flow workbench features when set to true. diff --git a/charts/controller/crds/deployments.plural.sh_workbenchtools.yaml b/charts/controller/crds/deployments.plural.sh_workbenchtools.yaml index fcaf370701..9163c27e28 100644 --- a/charts/controller/crds/deployments.plural.sh_workbenchtools.yaml +++ b/charts/controller/crds/deployments.plural.sh_workbenchtools.yaml @@ -52,6 +52,58 @@ spec: spec: description: WorkbenchToolSpec defines the desired state of a WorkbenchTool. properties: + approval: + description: Whether this tool requires approval before execution. + type: boolean + bindings: + description: Bindings define the read and write access policies for + this tool. + properties: + read: + description: Read bindings. + items: + description: Binding used to assign permissions to a resource + for a user or a group in the system. + properties: + UserID: + type: string + groupID: + type: string + groupName: + type: string + id: + description: |- + ID of a binding. + Deprecated. + Do not use. + type: string + userEmail: + type: string + type: object + type: array + write: + description: Write bindings. + items: + description: Binding used to assign permissions to a resource + for a user or a group in the system. + properties: + UserID: + type: string + groupID: + type: string + groupName: + type: string + id: + description: |- + ID of a binding. + Deprecated. + Do not use. + type: string + userEmail: + type: string + type: object + type: array + type: object categories: description: Categories for the tool. items: @@ -193,6 +245,10 @@ spec: - key type: object x-kubernetes-map-type: atomic + prometheusUrl: + description: Optional azure managed prometheus url if you + wish to use it for metrics. + type: string subscriptionId: description: Azure subscription id. type: string @@ -205,6 +261,138 @@ spec: - subscriptionId - tenantId type: object + azureDevops: + description: Azure DevOps connection (scm). + properties: + tokenSecretRef: + description: Reference to a secret key containing the Azure + DevOps personal access token. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + url: + description: Optional REST API root (defaults to https://dev.azure.com). + type: string + type: object + azureFunction: + description: Azure Function / Cloud Function configuration. + properties: + description: + description: Description of the function exposed to the agent. + type: string + identifier: + description: Cloud Function identifier. + type: string + inputSchema: + description: JSON schema for the tool input. + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - description + - identifier + type: object + bitbucket: + description: Bitbucket Cloud connection (scm). + properties: + tokenSecretRef: + description: Reference to a secret key containing the Bitbucket + app password or access token. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + url: + description: Optional Bitbucket Cloud API base URL. + type: string + type: object + bitbucketDatacenter: + description: Bitbucket Data Center connection (scm). + properties: + tokenSecretRef: + description: Reference to a secret key containing the HTTP + access token or personal access token. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + url: + description: Bitbucket Data Center REST API base URL. + type: string + required: + - url + type: object + cloudRun: + description: Google Cloud Run service configuration. + properties: + description: + description: Description of the function exposed to the agent. + type: string + identifier: + description: Cloud Run service identifier. + type: string + inputSchema: + description: JSON schema for the tool input. + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - description + - identifier + type: object cloudwatch: description: Cloudwatch connection (metrics, logs). properties: @@ -336,6 +524,330 @@ spec: description: Datadog site (e.g. datadoghq.com). type: string type: object + docker: + description: Docker/OCI registry connection. + properties: + auth: + description: Registry authentication credentials and optional + proxy. + properties: + aws: + description: |- + Aws specifies AWS-specific authentication for Amazon ECR and other AWS-hosted repositories. + Supports both static credentials and IAM role-based authentication for secure access + to private repositories hosted in Amazon Web Services. + properties: + accessKey: + description: |- + AccessKey specifies the AWS access key ID for authentication. + When using static credentials, this should be set along with the secret access key. + For enhanced security, consider using IAM roles instead of static credentials. + type: string + assumeRoleArn: + description: |- + AssumeRoleArn specifies an AWS IAM role ARN to assume for repository access. + This enables cross-account access and role-based authentication, providing + enhanced security and flexibility in AWS environments. + type: string + secretAccessKeySecretKeyRef: + description: |- + SecretAccessKeySecretKeyRef references a specific key within a Kubernetes Secret containing the secret access key. + This is the preferred method for storing AWS credentials as it allows multiple + credential sets to be organized within a single secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secretAccessKeySecretRef: + description: |- + SecretAccessKeySecretRef references a Kubernetes Secret containing the AWS secret access key. + The entire secret content will be used as the secret access key. + This approach is deprecated in favor of SecretAccessKeySecretKeyRef for better secret management. + properties: + name: + description: name is unique within a namespace + to reference a secret resource. + type: string + namespace: + description: namespace defines the space within + which the secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + type: object + azure: + description: |- + Azure specifies Azure-specific authentication for Azure Container Registry (ACR). + Supports service principal authentication and managed identity for secure access + to private repositories hosted in Microsoft Azure. + properties: + clientId: + description: |- + ClientID specifies the Azure service principal client ID. + This is used in conjunction with the client secret to authenticate with Azure services. + type: string + clientSecretSecretKeyRef: + description: |- + ClientSecretSecretKeyRef references a specific key within a Kubernetes Secret containing the client secret. + This is the preferred method for storing Azure credentials as it allows proper + secret organization and key-based access control. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecretSecretRef: + description: |- + ClientSecretSecretRef references a Kubernetes Secret containing the Azure service principal client secret. + The entire secret content will be used as the client secret. + This approach is deprecated in favor of ClientSecretSecretKeyRef for better secret management. + properties: + name: + description: name is unique within a namespace + to reference a secret resource. + type: string + namespace: + description: namespace defines the space within + which the secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + subscriptionId: + description: |- + SubscriptionID specifies the Azure subscription ID. + This identifies the Azure subscription containing the resources + and may be required for certain repository access scenarios. + type: string + tenantId: + description: |- + TenantID specifies the Azure Active Directory tenant ID. + This identifies the Azure AD instance that contains the service principal + and is required for proper authentication scope. + type: string + type: object + basic: + description: |- + Basic specifies username/password authentication for repositories that support HTTP Basic Auth. + Commonly used with private Helm repositories, Harbor registries, and other traditional + repository managers that implement standard HTTP authentication. + properties: + passwordSecretKeyRef: + description: |- + PasswordSecretKeyRef references a specific key within a Kubernetes Secret that contains the password. + This is the preferred method for password storage as it allows multiple credentials + to be stored in a single secret with proper key-based access. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + passwordSecretRef: + description: |- + PasswordSecretRef references a Kubernetes Secret containing the password for Basic authentication. + The entire secret content will be used as the password. + This approach is deprecated in favor of PasswordSecretKeyRef for better secret management. + properties: + name: + description: name is unique within a namespace + to reference a secret resource. + type: string + namespace: + description: namespace defines the space within + which the secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + username: + description: |- + Username specifies the username for HTTP Basic authentication. + This is typically a user account or service account name configured + in the target repository system. + type: string + required: + - username + type: object + bearer: + description: |- + Bearer specifies token-based authentication for repositories that support Bearer tokens. + Used with modern container registries and repositories that implement OAuth2 or similar + token-based authentication schemes. + properties: + tokenSecretKeyRef: + description: |- + TokenSecretKeyRef references a specific key within a Kubernetes Secret that contains the bearer token. + This is the preferred method for token storage as it allows multiple tokens + to be stored in a single secret with proper key-based access. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenSecretRef: + description: |- + TokenSecretRef references a Kubernetes Secret containing the bearer token. + The entire secret content will be used as the authentication token. + This approach is deprecated in favor of TokenSecretKeyRef for better secret management. + properties: + name: + description: name is unique within a namespace + to reference a secret resource. + type: string + namespace: + description: namespace defines the space within + which the secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + type: object + gcp: + description: |- + Gcp specifies Google Cloud-specific authentication for Google Artifact Registry. + Supports service account key authentication for secure access to private + repositories hosted in Google Cloud Platform. + properties: + applicationCredentialsSecretKeyRef: + description: |- + ApplicationCredentialsSecretKeyRef references a specific key within a Kubernetes Secret containing the service account JSON. + This is the preferred method for storing GCP credentials as it allows multiple + service account keys to be organized within a single secret with proper access control. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + applicationCredentialsSecretRef: + description: |- + ApplicationCredentialsSecretRef references a Kubernetes Secret containing the GCP service account key JSON. + The entire secret content will be used as the service account credentials. + This approach is deprecated in favor of ApplicationCredentialsSecretKeyRef for better secret management. + properties: + name: + description: name is unique within a namespace + to reference a secret resource. + type: string + namespace: + description: namespace defines the space within + which the secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + type: object + proxy: + description: Proxy configures an HTTP proxy for requests + to this Helm repository or OCI registry. + properties: + noproxy: + description: The comma-separated list of hosts that + should not be proxied, will behave equivalently + to a NOPROXY env var. + type: string + url: + description: The url of your HTTP proxy. + type: string + required: + - url + type: object + type: object + provider: + description: 'Registry authentication provider: BASIC, BEARER, + AWS, AZURE, or GCP.' + enum: + - BASIC + - BEARER + - GCP + - AZURE + - AWS + type: string + url: + description: Registry host or base URL (defaults to registry-1.docker.io). + type: string + type: object dynatrace: description: Dynatrace connection (metrics, logs, traces). properties: @@ -413,12 +925,149 @@ spec: - url - username type: object + exa: + description: Exa connection (search). + properties: + apiKeySecretRef: + description: Reference to a secret key containing the exa + API key. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + github: + description: GitHub connection (integration). + properties: + accessTokenSecretRef: + description: Reference to a secret key containing an optional + GitHub personal access token or fine-grained token. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + appId: + description: GitHub App ID (use with installationId and privateKey + instead of accessToken). + type: string + installationId: + description: GitHub App installation ID for this organization + or account. + type: string + privateKeySecretRef: + description: Reference to a secret key containing the PEM + private key for the GitHub App. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + toolset: + description: 'Optional native tool subset: issues, pull_requests, + repos, security, default/all, or omit for all tools.' + type: string + url: + description: Optional GitHub REST API base URL (defaults to + https://api.github.com/; set for GitHub Enterprise Server). + type: string + type: object + gitlab: + description: GitLab connection (scm). + properties: + tokenSecretRef: + description: Reference to a secret key containing the GitLab + personal access token or project/group token. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + url: + description: Optional GitLab API base URL (defaults to https://gitlab.com + when omitted). + type: string + type: object http: description: Http tool configuration. properties: body: description: Request body. type: string + function: + description: When true, exposes this HTTP tool as a workbench + action; execution may require approval when tool approval + is enabled. + type: boolean headers: description: Request headers. items: @@ -515,6 +1164,23 @@ spec: required: - url type: object + lambda: + description: AWS Lambda function configuration. + properties: + description: + description: Description of the function exposed to the agent. + type: string + inputSchema: + description: JSON schema for the tool input. + type: object + x-kubernetes-preserve-unknown-fields: true + lambdaArn: + description: AWS Lambda function ARN. + type: string + required: + - description + - lambdaArn + type: object linear: description: Linear connection (ticketing). properties: @@ -609,9 +1275,168 @@ spec: required: - url type: object + opensearch: + description: AWS OpenSearch connection (logs). + properties: + assumeRoleArn: + description: Optional IAM role ARN to assume before signing + OpenSearch requests. + type: string + awsAccessKeyIdSecretRef: + description: Reference to a secret key containing the AWS + access key id for SigV4 authentication. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + awsRegion: + description: AWS region for SigV4 authentication. + type: string + awsSecretAccessKeySecretRef: + description: Reference to a secret key containing the AWS + secret access key for SigV4 authentication. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + host: + description: AWS OpenSearch endpoint. + type: string + index: + description: OpenSearch index. + type: string + usePodIdentity: + description: Whether to use pod identity (IRSA/Workload Identity) + for AWS authentication instead of static credentials. + type: boolean + required: + - host + - index + type: object + pagerduty: + description: PagerDuty connection (integration). + properties: + apiTokenSecretRef: + description: Reference to a secret key containing the pagerduty + REST API key. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object prometheus: description: Prometheus connection (metrics). properties: + awsAccessKeyIdSecretRef: + description: Reference to a secret key containing the AWS + access key id for SigV4 authentication. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + awsRegion: + description: AWS region for SigV4 authentication. + type: string + awsSecretAccessKeySecretRef: + description: Reference to a secret key containing the AWS + secret access key for SigV4 authentication. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + awsSigv4: + description: Whether to sign requests with AWS SigV4. + type: boolean passwordSecretRef: description: Reference to a secret key containing the basic auth password. @@ -674,6 +1499,68 @@ spec: required: - url type: object + sentry: + description: Sentry connection (error tracking). + properties: + accessTokenSecretRef: + description: Reference to a secret key containing the Sentry + user auth token or internal integration token. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + url: + description: Optional Sentry API host (defaults to https://sentry.io; + set for self-hosted). + type: string + type: object + slack: + description: Slack connection (integration). + properties: + botTokenSecretRef: + description: Reference to a secret key containing the slack + bot user OAuth token (xoxb-...). + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object splunk: description: Splunk connection (logs). properties: @@ -736,6 +1623,45 @@ spec: required: - url type: object + teams: + description: Microsoft Teams / Graph connection (integration). + properties: + clientId: + description: Microsoft Entra application (client) id. + type: string + clientSecretSecretRef: + description: Reference to a secret key containing the microsoft + entra client secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tenantId: + description: Microsoft Entra tenant (directory) id. + type: string + required: + - clientId + - clientSecretSecretRef + - tenantId + type: object tempo: description: Tempo connection (traces). properties: @@ -914,6 +1840,50 @@ spec: example: 5m30s type: string type: object + scmConnectionRef: + description: The SCM connection for this tool (e.g. shared Git provider + credentials). + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic tool: description: The type of tool. enum: @@ -933,6 +1903,20 @@ spec: - AZURE - CLOUD - JAEGER + - EXA + - GITHUB + - SLACK + - TEAMS + - GITLAB + - BITBUCKET + - BITBUCKET_DATACENTER + - AZURE_DEVOPS + - PAGERDUTY + - OPENSEARCH + - LAMBDA + - CLOUD_RUN + - AZURE_FUNCTION + - DOCKER type: string required: - tool diff --git a/go/controller/api/v1alpha1/workbenchtool_types.go b/go/controller/api/v1alpha1/workbenchtool_types.go index 598b7ba413..5e799c438f 100644 --- a/go/controller/api/v1alpha1/workbenchtool_types.go +++ b/go/controller/api/v1alpha1/workbenchtool_types.go @@ -80,9 +80,10 @@ func (in *WorkbenchTool) ConsoleName() string { return in.Name } -// Diff compares the current WorkbenchTool spec with its last known state. -func (in *WorkbenchTool) Diff(hasher Hasher) (changed bool, sha string, err error) { - currentSha, err := hasher(in.Spec) +// Diff compares the resolved WorkbenchTool attributes with their last known state. +// Attributes include secret values, so credential rotation is detected. +func (in *WorkbenchTool) Diff(attrs console.WorkbenchToolAttributes, hasher Hasher) (changed bool, sha string, err error) { + currentSha, err := hasher(attrs) if err != nil { return false, "", err } @@ -94,7 +95,7 @@ func (in *WorkbenchTool) SetCondition(condition metav1.Condition) { meta.SetStatusCondition(&in.Status.Conditions, condition) } -func (in *WorkbenchTool) Attributes(ctx context.Context, c client.Client, projectID, mcpServerID, cloudConnectionID *string) (console.WorkbenchToolAttributes, error) { +func (in *WorkbenchTool) Attributes(ctx context.Context, c client.Client, projectID, mcpServerID, cloudConnectionID, scmConnectionID *string, readBindings, writeBindings []*console.PolicyBindingAttributes) (console.WorkbenchToolAttributes, error) { configuration, err := in.Spec.Configuration.Attributes(ctx, c, in.Namespace) if err != nil { return console.WorkbenchToolAttributes{}, err @@ -107,6 +108,10 @@ func (in *WorkbenchTool) Attributes(ctx context.Context, c client.Client, projec ProjectID: projectID, McpServerID: mcpServerID, CloudConnectionID: cloudConnectionID, + ScmConnectionID: scmConnectionID, + Approval: in.Spec.Approval, + ReadBindings: readBindings, + WriteBindings: writeBindings, Configuration: configuration, }, nil } @@ -121,13 +126,17 @@ type WorkbenchToolSpec struct { // The type of tool. // +kubebuilder:validation:Required - // +kubebuilder:validation:Enum:=HTTP;ELASTIC;DATADOG;PROMETHEUS;LOKI;TEMPO;SENTRY;MCP;LINEAR;ATLASSIAN;SPLUNK;DYNATRACE;CLOUDWATCH;AZURE;CLOUD;JAEGER + // +kubebuilder:validation:Enum:=HTTP;ELASTIC;DATADOG;PROMETHEUS;LOKI;TEMPO;SENTRY;MCP;LINEAR;ATLASSIAN;SPLUNK;DYNATRACE;CLOUDWATCH;AZURE;CLOUD;JAEGER;EXA;GITHUB;SLACK;TEAMS;GITLAB;BITBUCKET;BITBUCKET_DATACENTER;AZURE_DEVOPS;PAGERDUTY;OPENSEARCH;LAMBDA;CLOUD_RUN;AZURE_FUNCTION;DOCKER Tool console.WorkbenchToolType `json:"tool"` // Categories for the tool. // +kubebuilder:validation:Optional Categories []console.WorkbenchToolCategory `json:"categories,omitempty"` + // Whether this tool requires approval before execution. + // +kubebuilder:validation:Optional + Approval *bool `json:"approval,omitempty"` + // The project for this tool. // +kubebuilder:validation:Optional ProjectRef *corev1.ObjectReference `json:"projectRef,omitempty"` @@ -140,6 +149,14 @@ type WorkbenchToolSpec struct { // +kubebuilder:validation:Optional CloudConnectionRef *corev1.ObjectReference `json:"cloudConnectionRef,omitempty"` + // The SCM connection for this tool (e.g. shared Git provider credentials). + // +kubebuilder:validation:Optional + ScmConnectionRef *corev1.ObjectReference `json:"scmConnectionRef,omitempty"` + + // Bindings define the read and write access policies for this tool. + // +kubebuilder:validation:Optional + Bindings *Bindings `json:"bindings,omitempty"` + // Tool configuration (e.g. HTTP). // +kubebuilder:validation:Optional Configuration *WorkbenchToolConfiguration `json:"configuration,omitempty"` @@ -158,6 +175,10 @@ type WorkbenchToolConfiguration struct { // +kubebuilder:validation:Optional Elastic *WorkbenchToolElasticConfig `json:"elastic,omitempty"` + // AWS OpenSearch connection (logs). + // +kubebuilder:validation:Optional + Opensearch *WorkbenchToolOpensearchConfig `json:"opensearch,omitempty"` + // Prometheus connection (metrics). // +kubebuilder:validation:Optional Prometheus *WorkbenchToolPrometheusConfig `json:"prometheus,omitempty"` @@ -194,13 +215,69 @@ type WorkbenchToolConfiguration struct { // +kubebuilder:validation:Optional Azure *WorkbenchToolAzureConfig `json:"azure,omitempty"` + // Sentry connection (error tracking). + // +kubebuilder:validation:Optional + Sentry *WorkbenchToolSentryConfig `json:"sentry,omitempty"` + // Linear connection (ticketing). // +kubebuilder:validation:Optional Linear *WorkbenchToolLinearConfig `json:"linear,omitempty"` + // Slack connection (integration). + // +kubebuilder:validation:Optional + Slack *WorkbenchToolSlackConfig `json:"slack,omitempty"` + + // PagerDuty connection (integration). + // +kubebuilder:validation:Optional + Pagerduty *WorkbenchToolPagerdutyConfig `json:"pagerduty,omitempty"` + + // Microsoft Teams / Graph connection (integration). + // +kubebuilder:validation:Optional + Teams *WorkbenchToolTeamsConfig `json:"teams,omitempty"` + // Atlassian/jira connection (ticketing). // +kubebuilder:validation:Optional Atlassian *WorkbenchToolAtlassianConfig `json:"atlassian,omitempty"` + + // Exa connection (search). + // +kubebuilder:validation:Optional + Exa *WorkbenchToolExaConfig `json:"exa,omitempty"` + + // GitHub connection (integration). + // +kubebuilder:validation:Optional + Github *WorkbenchToolGithubConfig `json:"github,omitempty"` + + // GitLab connection (scm). + // +kubebuilder:validation:Optional + Gitlab *WorkbenchToolGitlabConfig `json:"gitlab,omitempty"` + + // Bitbucket Cloud connection (scm). + // +kubebuilder:validation:Optional + Bitbucket *WorkbenchToolBitbucketConfig `json:"bitbucket,omitempty"` + + // Bitbucket Data Center connection (scm). + // +kubebuilder:validation:Optional + BitbucketDatacenter *WorkbenchToolBitbucketDatacenterConfig `json:"bitbucketDatacenter,omitempty"` + + // Azure DevOps connection (scm). + // +kubebuilder:validation:Optional + AzureDevops *WorkbenchToolAzureDevopsConfig `json:"azureDevops,omitempty"` + + // AWS Lambda function configuration. + // +kubebuilder:validation:Optional + Lambda *WorkbenchToolLambdaConfig `json:"lambda,omitempty"` + + // Google Cloud Run service configuration. + // +kubebuilder:validation:Optional + CloudRun *WorkbenchToolCloudRunConfig `json:"cloudRun,omitempty"` + + // Azure Function / Cloud Function configuration. + // +kubebuilder:validation:Optional + AzureFunction *WorkbenchToolAzureFunctionConfig `json:"azureFunction,omitempty"` + + // Docker/OCI registry connection. + // +kubebuilder:validation:Optional + Docker *WorkbenchToolDockerConfig `json:"docker,omitempty"` } func (c *WorkbenchToolConfiguration) Attributes(ctx context.Context, cl client.Client, namespace string) (*console.WorkbenchToolConfigurationAttributes, error) { @@ -213,6 +290,11 @@ func (c *WorkbenchToolConfiguration) Attributes(ctx context.Context, cl client.C return nil, err } + opensearch, err := c.Opensearch.Attributes(ctx, cl, namespace) + if err != nil { + return nil, err + } + prometheus, err := c.Prometheus.Attributes(ctx, cl, namespace) if err != nil { return nil, err @@ -258,30 +340,95 @@ func (c *WorkbenchToolConfiguration) Attributes(ctx context.Context, cl client.C return nil, err } + sentry, err := c.Sentry.Attributes(ctx, cl, namespace) + if err != nil { + return nil, err + } + linear, err := c.Linear.Attributes(ctx, cl, namespace) if err != nil { return nil, err } + slack, err := c.Slack.Attributes(ctx, cl, namespace) + if err != nil { + return nil, err + } + + pagerduty, err := c.Pagerduty.Attributes(ctx, cl, namespace) + if err != nil { + return nil, err + } + + teams, err := c.Teams.Attributes(ctx, cl, namespace) + if err != nil { + return nil, err + } + atlassian, err := c.Atlassian.Attributes(ctx, cl, namespace) if err != nil { return nil, err } + exa, err := c.Exa.Attributes(ctx, cl, namespace) + if err != nil { + return nil, err + } + + github, err := c.Github.Attributes(ctx, cl, namespace) + if err != nil { + return nil, err + } + + gitlab, err := c.Gitlab.Attributes(ctx, cl, namespace) + if err != nil { + return nil, err + } + + bitbucket, err := c.Bitbucket.Attributes(ctx, cl, namespace) + if err != nil { + return nil, err + } + + bitbucketDatacenter, err := c.BitbucketDatacenter.Attributes(ctx, cl, namespace) + if err != nil { + return nil, err + } + + azureDevops, err := c.AzureDevops.Attributes(ctx, cl, namespace) + if err != nil { + return nil, err + } + return &console.WorkbenchToolConfigurationAttributes{ - HTTP: c.HTTP.Attributes(), - Elastic: elastic, - Prometheus: prometheus, - Loki: loki, - Tempo: tempo, - Jaeger: jaeger, - Splunk: splunk, - Datadog: datadog, - Dynatrace: dynatrace, - Cloudwatch: cloudwatch, - Azure: azure, - Linear: linear, - Atlassian: atlassian, + HTTP: c.HTTP.Attributes(), + Elastic: elastic, + Opensearch: opensearch, + Prometheus: prometheus, + Loki: loki, + Tempo: tempo, + Jaeger: jaeger, + Splunk: splunk, + Datadog: datadog, + Dynatrace: dynatrace, + Cloudwatch: cloudwatch, + Azure: azure, + Sentry: sentry, + Linear: linear, + Slack: slack, + Pagerduty: pagerduty, + Teams: teams, + Atlassian: atlassian, + Exa: exa, + Github: github, + Gitlab: gitlab, + Bitbucket: bitbucket, + BitbucketDatacenter: bitbucketDatacenter, + AzureDevops: azureDevops, + Lambda: c.Lambda.Attributes(), + CloudRun: c.CloudRun.Attributes(), + AzureFunction: c.AzureFunction.Attributes(), + Docker: c.Docker.Attributes(), }, nil } @@ -298,6 +445,10 @@ type WorkbenchToolHTTPConfig struct { // +kubebuilder:validation:Enum:=GET;POST;PUT;DELETE;PATCH Method console.WorkbenchToolHTTPMethod `json:"method"` + // When true, exposes this HTTP tool as a workbench action; execution may require approval when tool approval is enabled. + // +kubebuilder:validation:Optional + Function *bool `json:"function,omitempty"` + // Request headers. // +kubebuilder:validation:Optional Headers []WorkbenchToolHTTPHeader `json:"headers,omitempty"` @@ -318,8 +469,9 @@ func (c *WorkbenchToolHTTPConfig) Attributes() *console.WorkbenchToolHTTPConfigu } return &console.WorkbenchToolHTTPConfigurationAttributes{ - URL: c.URL, - Method: c.Method, + URL: c.URL, + Method: c.Method, + Function: c.Function, Headers: lo.Map(c.Headers, func(header WorkbenchToolHTTPHeader, _ int) *console.WorkbenchToolHTTPHeaderAttributes { return &console.WorkbenchToolHTTPHeaderAttributes{ Name: header.Name, @@ -383,6 +535,69 @@ func (c *WorkbenchToolElasticConfig) Attributes(ctx context.Context, cl client.C return attr, nil } +// WorkbenchToolOpensearchConfig defines an AWS OpenSearch connection. +type WorkbenchToolOpensearchConfig struct { + // AWS OpenSearch endpoint. + // +kubebuilder:validation:Required + Host string `json:"host"` + + // OpenSearch index. + // +kubebuilder:validation:Required + Index string `json:"index"` + + // Reference to a secret key containing the AWS access key id for SigV4 authentication. + // +kubebuilder:validation:Optional + AWSAccessKeyIDSecretRef *corev1.SecretKeySelector `json:"awsAccessKeyIdSecretRef,omitempty"` + + // Reference to a secret key containing the AWS secret access key for SigV4 authentication. + // +kubebuilder:validation:Optional + AWSSecretAccessKeySecretRef *corev1.SecretKeySelector `json:"awsSecretAccessKeySecretRef,omitempty"` + + // AWS region for SigV4 authentication. + // +kubebuilder:validation:Optional + AWSRegion *string `json:"awsRegion,omitempty"` + + // Optional IAM role ARN to assume before signing OpenSearch requests. + // +kubebuilder:validation:Optional + AssumeRoleArn *string `json:"assumeRoleArn,omitempty"` + + // Whether to use pod identity (IRSA/Workload Identity) for AWS authentication instead of static credentials. + // +kubebuilder:validation:Optional + UsePodIdentity *bool `json:"usePodIdentity,omitempty"` +} + +func (c *WorkbenchToolOpensearchConfig) Attributes(ctx context.Context, cl client.Client, namespace string) (*console.WorkbenchToolOpensearchConnectionAttributes, error) { + if c == nil { + return nil, nil + } + + attr := &console.WorkbenchToolOpensearchConnectionAttributes{ + Host: c.Host, + Index: c.Index, + AWSRegion: c.AWSRegion, + AssumeRoleArn: c.AssumeRoleArn, + UsePodIdentity: c.UsePodIdentity, + } + + if c.AWSAccessKeyIDSecretRef != nil { + accessKeyID, err := utils.GetSecretKey(ctx, cl, c.AWSAccessKeyIDSecretRef, namespace) + if err != nil { + return nil, err + } + attr.AWSAccessKeyID = lo.ToPtr(accessKeyID) + } + + if c.AWSSecretAccessKeySecretRef != nil { + secretAccessKey, err := utils.GetSecretKey(ctx, cl, c.AWSSecretAccessKeySecretRef, namespace) + if err != nil { + return nil, err + } + attr.AWSSecretAccessKey = lo.ToPtr(secretAccessKey) + } + + return attr, nil +} + // WorkbenchToolPrometheusConfig defines a prometheus connection. type WorkbenchToolPrometheusConfig struct { // Prometheus base URL. @@ -404,6 +619,22 @@ type WorkbenchToolPrometheusConfig struct { // Optional tenant id (e.g. for Mimir). // +kubebuilder:validation:Optional TenantID *string `json:"tenantId,omitempty"` + + // Whether to sign requests with AWS SigV4. + // +kubebuilder:validation:Optional + AWSSigv4 *bool `json:"awsSigv4,omitempty"` + + // Reference to a secret key containing the AWS access key id for SigV4 authentication. + // +kubebuilder:validation:Optional + AWSAccessKeyIDSecretRef *corev1.SecretKeySelector `json:"awsAccessKeyIdSecretRef,omitempty"` + + // Reference to a secret key containing the AWS secret access key for SigV4 authentication. + // +kubebuilder:validation:Optional + AWSSecretAccessKeySecretRef *corev1.SecretKeySelector `json:"awsSecretAccessKeySecretRef,omitempty"` + + // AWS region for SigV4 authentication. + // +kubebuilder:validation:Optional + AWSRegion *string `json:"awsRegion,omitempty"` } func (c *WorkbenchToolPrometheusConfig) Attributes(ctx context.Context, cl client.Client, namespace string) (*console.WorkbenchToolPrometheusConnectionAttributes, error) { @@ -412,9 +643,11 @@ func (c *WorkbenchToolPrometheusConfig) Attributes(ctx context.Context, cl clien } attr := &console.WorkbenchToolPrometheusConnectionAttributes{ - URL: c.URL, - Username: c.Username, - TenantID: c.TenantID, + URL: c.URL, + Username: c.Username, + TenantID: c.TenantID, + AWSSigv4: c.AWSSigv4, + AWSRegion: c.AWSRegion, } if c.TokenSecretRef != nil { @@ -433,6 +666,22 @@ func (c *WorkbenchToolPrometheusConfig) Attributes(ctx context.Context, cl clien attr.Password = lo.ToPtr(password) } + if c.AWSAccessKeyIDSecretRef != nil { + accessKeyID, err := utils.GetSecretKey(ctx, cl, c.AWSAccessKeyIDSecretRef, namespace) + if err != nil { + return nil, err + } + attr.AWSAccessKeyID = lo.ToPtr(accessKeyID) + } + + if c.AWSSecretAccessKeySecretRef != nil { + secretAccessKey, err := utils.GetSecretKey(ctx, cl, c.AWSSecretAccessKeySecretRef, namespace) + if err != nil { + return nil, err + } + attr.AWSSecretAccessKey = lo.ToPtr(secretAccessKey) + } + return attr, nil } @@ -788,6 +1037,10 @@ type WorkbenchToolAzureConfig struct { // Reference to a secret key containing the azure client secret. // +kubebuilder:validation:Required ClientSecretSecretRef corev1.SecretKeySelector `json:"clientSecretSecretRef"` + + // Optional azure managed prometheus url if you wish to use it for metrics. + // +kubebuilder:validation:Optional + PrometheusURL *string `json:"prometheusUrl,omitempty"` } func (c *WorkbenchToolAzureConfig) Attributes(ctx context.Context, cl client.Client, namespace string) (*console.WorkbenchToolAzureConnectionAttributes, error) { @@ -805,6 +1058,7 @@ func (c *WorkbenchToolAzureConfig) Attributes(ctx context.Context, cl client.Cli TenantID: c.TenantID, ClientID: c.ClientID, ClientSecret: clientSecret, + PrometheusURL: c.PrometheusURL, }, nil } @@ -875,3 +1129,441 @@ func (c *WorkbenchToolAtlassianConfig) Attributes(ctx context.Context, cl client return attr, nil } + +// WorkbenchToolSentryConfig defines a sentry connection. +type WorkbenchToolSentryConfig struct { + // Optional Sentry API host (defaults to https://sentry.io; set for self-hosted). + // +kubebuilder:validation:Optional + URL *string `json:"url,omitempty"` + + // Reference to a secret key containing the Sentry user auth token or internal integration token. + // +kubebuilder:validation:Optional + AccessTokenSecretRef *corev1.SecretKeySelector `json:"accessTokenSecretRef,omitempty"` +} + +func (c *WorkbenchToolSentryConfig) Attributes(ctx context.Context, cl client.Client, namespace string) (*console.WorkbenchToolSentryConnectionAttributes, error) { + if c == nil { + return nil, nil + } + + attr := &console.WorkbenchToolSentryConnectionAttributes{ + URL: c.URL, + } + + if c.AccessTokenSecretRef != nil { + accessToken, err := utils.GetSecretKey(ctx, cl, c.AccessTokenSecretRef, namespace) + if err != nil { + return nil, err + } + attr.AccessToken = lo.ToPtr(accessToken) + } + + return attr, nil +} + +// WorkbenchToolSlackConfig defines a slack connection. +type WorkbenchToolSlackConfig struct { + // Reference to a secret key containing the slack bot user OAuth token (xoxb-...). + // +kubebuilder:validation:Optional + BotTokenSecretRef *corev1.SecretKeySelector `json:"botTokenSecretRef,omitempty"` +} + +func (c *WorkbenchToolSlackConfig) Attributes(ctx context.Context, cl client.Client, namespace string) (*console.WorkbenchToolSlackConnectionAttributes, error) { + if c == nil { + return nil, nil + } + + attr := &console.WorkbenchToolSlackConnectionAttributes{} + + if c.BotTokenSecretRef != nil { + botToken, err := utils.GetSecretKey(ctx, cl, c.BotTokenSecretRef, namespace) + if err != nil { + return nil, err + } + attr.BotToken = lo.ToPtr(botToken) + } + + return attr, nil +} + +// WorkbenchToolPagerdutyConfig defines a pagerduty connection. +type WorkbenchToolPagerdutyConfig struct { + // Reference to a secret key containing the pagerduty REST API key. + // +kubebuilder:validation:Optional + APITokenSecretRef *corev1.SecretKeySelector `json:"apiTokenSecretRef,omitempty"` +} + +func (c *WorkbenchToolPagerdutyConfig) Attributes(ctx context.Context, cl client.Client, namespace string) (*console.WorkbenchToolPagerdutyConnectionAttributes, error) { + if c == nil { + return nil, nil + } + + attr := &console.WorkbenchToolPagerdutyConnectionAttributes{} + + if c.APITokenSecretRef != nil { + apiToken, err := utils.GetSecretKey(ctx, cl, c.APITokenSecretRef, namespace) + if err != nil { + return nil, err + } + attr.APIToken = lo.ToPtr(apiToken) + } + + return attr, nil +} + +// WorkbenchToolTeamsConfig defines a microsoft teams / graph connection. +type WorkbenchToolTeamsConfig struct { + // Microsoft Entra application (client) id. + // +kubebuilder:validation:Required + ClientID string `json:"clientId"` + + // Reference to a secret key containing the microsoft entra client secret. + // +kubebuilder:validation:Required + ClientSecretSecretRef corev1.SecretKeySelector `json:"clientSecretSecretRef"` + + // Microsoft Entra tenant (directory) id. + // +kubebuilder:validation:Required + TenantID string `json:"tenantId"` +} + +func (c *WorkbenchToolTeamsConfig) Attributes(ctx context.Context, cl client.Client, namespace string) (*console.WorkbenchToolTeamsConnectionAttributes, error) { + if c == nil { + return nil, nil + } + + clientSecret, err := utils.GetSecretKey(ctx, cl, &c.ClientSecretSecretRef, namespace) + if err != nil { + return nil, err + } + + return &console.WorkbenchToolTeamsConnectionAttributes{ + ClientID: c.ClientID, + ClientSecret: clientSecret, + TenantID: c.TenantID, + }, nil +} + +// WorkbenchToolExaConfig defines an exa connection. +type WorkbenchToolExaConfig struct { + // Reference to a secret key containing the exa API key. + // +kubebuilder:validation:Optional + APIKeySecretRef *corev1.SecretKeySelector `json:"apiKeySecretRef,omitempty"` +} + +func (c *WorkbenchToolExaConfig) Attributes(ctx context.Context, cl client.Client, namespace string) (*console.WorkbenchToolExaConnectionAttributes, error) { + if c == nil { + return nil, nil + } + + attr := &console.WorkbenchToolExaConnectionAttributes{} + + if c.APIKeySecretRef != nil { + apiKey, err := utils.GetSecretKey(ctx, cl, c.APIKeySecretRef, namespace) + if err != nil { + return nil, err + } + attr.APIKey = lo.ToPtr(apiKey) + } + + return attr, nil +} + +// WorkbenchToolGithubConfig defines a github connection. +type WorkbenchToolGithubConfig struct { + // Optional GitHub REST API base URL (defaults to https://api.github.com/; set for GitHub Enterprise Server). + // +kubebuilder:validation:Optional + URL *string `json:"url,omitempty"` + + // Reference to a secret key containing an optional GitHub personal access token or fine-grained token. + // +kubebuilder:validation:Optional + AccessTokenSecretRef *corev1.SecretKeySelector `json:"accessTokenSecretRef,omitempty"` + + // Optional native tool subset: issues, pull_requests, repos, security, default/all, or omit for all tools. + // +kubebuilder:validation:Optional + Toolset *string `json:"toolset,omitempty"` + + // GitHub App ID (use with installationId and privateKey instead of accessToken). + // +kubebuilder:validation:Optional + AppID *string `json:"appId,omitempty"` + + // GitHub App installation ID for this organization or account. + // +kubebuilder:validation:Optional + InstallationID *string `json:"installationId,omitempty"` + + // Reference to a secret key containing the PEM private key for the GitHub App. + // +kubebuilder:validation:Optional + PrivateKeySecretRef *corev1.SecretKeySelector `json:"privateKeySecretRef,omitempty"` +} + +func (c *WorkbenchToolGithubConfig) Attributes(ctx context.Context, cl client.Client, namespace string) (*console.WorkbenchToolGithubConnectionAttributes, error) { + if c == nil { + return nil, nil + } + + attr := &console.WorkbenchToolGithubConnectionAttributes{ + URL: c.URL, + Toolset: c.Toolset, + AppID: c.AppID, + InstallationID: c.InstallationID, + } + + if c.AccessTokenSecretRef != nil { + accessToken, err := utils.GetSecretKey(ctx, cl, c.AccessTokenSecretRef, namespace) + if err != nil { + return nil, err + } + attr.AccessToken = lo.ToPtr(accessToken) + } + + if c.PrivateKeySecretRef != nil { + privateKey, err := utils.GetSecretKey(ctx, cl, c.PrivateKeySecretRef, namespace) + if err != nil { + return nil, err + } + attr.PrivateKey = lo.ToPtr(privateKey) + } + + return attr, nil +} + +// WorkbenchToolGitlabConfig defines a gitlab connection. +type WorkbenchToolGitlabConfig struct { + // Optional GitLab API base URL (defaults to https://gitlab.com when omitted). + // +kubebuilder:validation:Optional + URL *string `json:"url,omitempty"` + + // Reference to a secret key containing the GitLab personal access token or project/group token. + // +kubebuilder:validation:Optional + TokenSecretRef *corev1.SecretKeySelector `json:"tokenSecretRef,omitempty"` +} + +func (c *WorkbenchToolGitlabConfig) Attributes(ctx context.Context, cl client.Client, namespace string) (*console.WorkbenchToolGitlabConnectionAttributes, error) { + if c == nil { + return nil, nil + } + + attr := &console.WorkbenchToolGitlabConnectionAttributes{ + URL: c.URL, + } + + if c.TokenSecretRef != nil { + token, err := utils.GetSecretKey(ctx, cl, c.TokenSecretRef, namespace) + if err != nil { + return nil, err + } + attr.Token = lo.ToPtr(token) + } + + return attr, nil +} + +// WorkbenchToolBitbucketConfig defines a bitbucket cloud connection. +type WorkbenchToolBitbucketConfig struct { + // Optional Bitbucket Cloud API base URL. + // +kubebuilder:validation:Optional + URL *string `json:"url,omitempty"` + + // Reference to a secret key containing the Bitbucket app password or access token. + // +kubebuilder:validation:Optional + TokenSecretRef *corev1.SecretKeySelector `json:"tokenSecretRef,omitempty"` +} + +func (c *WorkbenchToolBitbucketConfig) Attributes(ctx context.Context, cl client.Client, namespace string) (*console.WorkbenchToolBitbucketConnectionAttributes, error) { + if c == nil { + return nil, nil + } + + attr := &console.WorkbenchToolBitbucketConnectionAttributes{ + URL: c.URL, + } + + if c.TokenSecretRef != nil { + token, err := utils.GetSecretKey(ctx, cl, c.TokenSecretRef, namespace) + if err != nil { + return nil, err + } + attr.Token = lo.ToPtr(token) + } + + return attr, nil +} + +// WorkbenchToolBitbucketDatacenterConfig defines a bitbucket data center connection. +type WorkbenchToolBitbucketDatacenterConfig struct { + // Bitbucket Data Center REST API base URL. + // +kubebuilder:validation:Required + URL string `json:"url"` + + // Reference to a secret key containing the HTTP access token or personal access token. + // +kubebuilder:validation:Optional + TokenSecretRef *corev1.SecretKeySelector `json:"tokenSecretRef,omitempty"` +} + +func (c *WorkbenchToolBitbucketDatacenterConfig) Attributes(ctx context.Context, cl client.Client, namespace string) (*console.WorkbenchToolBitbucketDatacenterConnectionAttributes, error) { + if c == nil { + return nil, nil + } + + attr := &console.WorkbenchToolBitbucketDatacenterConnectionAttributes{ + URL: c.URL, + } + + if c.TokenSecretRef != nil { + token, err := utils.GetSecretKey(ctx, cl, c.TokenSecretRef, namespace) + if err != nil { + return nil, err + } + attr.Token = lo.ToPtr(token) + } + + return attr, nil +} + +// WorkbenchToolAzureDevopsConfig defines an azure devops connection. +type WorkbenchToolAzureDevopsConfig struct { + // Optional REST API root (defaults to https://dev.azure.com). + // +kubebuilder:validation:Optional + URL *string `json:"url,omitempty"` + + // Reference to a secret key containing the Azure DevOps personal access token. + // +kubebuilder:validation:Optional + TokenSecretRef *corev1.SecretKeySelector `json:"tokenSecretRef,omitempty"` +} + +func (c *WorkbenchToolAzureDevopsConfig) Attributes(ctx context.Context, cl client.Client, namespace string) (*console.WorkbenchToolAzureDevopsConnectionAttributes, error) { + if c == nil { + return nil, nil + } + + attr := &console.WorkbenchToolAzureDevopsConnectionAttributes{ + URL: c.URL, + } + + if c.TokenSecretRef != nil { + token, err := utils.GetSecretKey(ctx, cl, c.TokenSecretRef, namespace) + if err != nil { + return nil, err + } + attr.Token = lo.ToPtr(token) + } + + return attr, nil +} + +// WorkbenchToolLambdaConfig defines an AWS Lambda function configuration. +type WorkbenchToolLambdaConfig struct { + // AWS Lambda function ARN. + // +kubebuilder:validation:Required + LambdaArn string `json:"lambdaArn"` + + // Description of the function exposed to the agent. + // +kubebuilder:validation:Required + Description string `json:"description"` + + // JSON schema for the tool input. + // +kubebuilder:validation:Optional + InputSchema *runtime.RawExtension `json:"inputSchema,omitempty"` +} + +func (c *WorkbenchToolLambdaConfig) Attributes() *console.WorkbenchToolLambdaConnectionAttributes { + if c == nil { + return nil + } + + attr := &console.WorkbenchToolLambdaConnectionAttributes{ + LambdaArn: c.LambdaArn, + Description: c.Description, + } + if c.InputSchema != nil { + attr.InputSchema = lo.ToPtr(string(c.InputSchema.Raw)) + } + return attr +} + +// WorkbenchToolCloudRunConfig defines a Google Cloud Run service configuration. +type WorkbenchToolCloudRunConfig struct { + // Cloud Run service identifier. + // +kubebuilder:validation:Required + Identifier string `json:"identifier"` + + // Description of the function exposed to the agent. + // +kubebuilder:validation:Required + Description string `json:"description"` + + // JSON schema for the tool input. + // +kubebuilder:validation:Optional + InputSchema *runtime.RawExtension `json:"inputSchema,omitempty"` +} + +func (c *WorkbenchToolCloudRunConfig) Attributes() *console.WorkbenchToolCloudRunConnectionAttributes { + if c == nil { + return nil + } + + attr := &console.WorkbenchToolCloudRunConnectionAttributes{ + Identifier: c.Identifier, + Description: c.Description, + } + if c.InputSchema != nil { + attr.InputSchema = lo.ToPtr(string(c.InputSchema.Raw)) + } + return attr +} + +// WorkbenchToolAzureFunctionConfig defines an Azure Function / Cloud Function configuration. +type WorkbenchToolAzureFunctionConfig struct { + // Cloud Function identifier. + // +kubebuilder:validation:Required + Identifier string `json:"identifier"` + + // Description of the function exposed to the agent. + // +kubebuilder:validation:Required + Description string `json:"description"` + + // JSON schema for the tool input. + // +kubebuilder:validation:Optional + InputSchema *runtime.RawExtension `json:"inputSchema,omitempty"` +} + +func (c *WorkbenchToolAzureFunctionConfig) Attributes() *console.WorkbenchToolAzureFunctionConnectionAttributes { + if c == nil { + return nil + } + + attr := &console.WorkbenchToolAzureFunctionConnectionAttributes{ + Identifier: c.Identifier, + Description: c.Description, + } + if c.InputSchema != nil { + attr.InputSchema = lo.ToPtr(string(c.InputSchema.Raw)) + } + return attr +} + +// WorkbenchToolDockerConfig defines a docker/OCI registry connection. +// Auth secrets are resolved by the reconciler via HelmRepositoryAuth helpers. +type WorkbenchToolDockerConfig struct { + // Registry host or base URL (defaults to registry-1.docker.io). + // +kubebuilder:validation:Optional + URL *string `json:"url,omitempty"` + + // Registry authentication provider: BASIC, BEARER, AWS, AZURE, or GCP. + // +kubebuilder:validation:Optional + // +kubebuilder:validation:Enum:=BASIC;BEARER;GCP;AZURE;AWS + Provider *console.HelmAuthProvider `json:"provider,omitempty"` + + // Registry authentication credentials and optional proxy. + // +kubebuilder:validation:Optional + Auth *HelmRepositoryAuth `json:"auth,omitempty"` +} + +func (c *WorkbenchToolDockerConfig) Attributes() *console.WorkbenchToolDockerConnectionAttributes { + if c == nil { + return nil + } + + return &console.WorkbenchToolDockerConnectionAttributes{ + URL: c.URL, + Provider: c.Provider, + } +} diff --git a/go/controller/api/v1alpha1/zz_generated.deepcopy.go b/go/controller/api/v1alpha1/zz_generated.deepcopy.go index c647e86657..be61390665 100644 --- a/go/controller/api/v1alpha1/zz_generated.deepcopy.go +++ b/go/controller/api/v1alpha1/zz_generated.deepcopy.go @@ -10627,6 +10627,11 @@ func (in *WorkbenchToolAtlassianConfig) DeepCopy() *WorkbenchToolAtlassianConfig func (in *WorkbenchToolAzureConfig) DeepCopyInto(out *WorkbenchToolAzureConfig) { *out = *in in.ClientSecretSecretRef.DeepCopyInto(&out.ClientSecretSecretRef) + if in.PrometheusURL != nil { + in, out := &in.PrometheusURL, &out.PrometheusURL + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolAzureConfig. @@ -10639,6 +10644,116 @@ func (in *WorkbenchToolAzureConfig) DeepCopy() *WorkbenchToolAzureConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkbenchToolAzureDevopsConfig) DeepCopyInto(out *WorkbenchToolAzureDevopsConfig) { + *out = *in + if in.URL != nil { + in, out := &in.URL, &out.URL + *out = new(string) + **out = **in + } + if in.TokenSecretRef != nil { + in, out := &in.TokenSecretRef, &out.TokenSecretRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolAzureDevopsConfig. +func (in *WorkbenchToolAzureDevopsConfig) DeepCopy() *WorkbenchToolAzureDevopsConfig { + if in == nil { + return nil + } + out := new(WorkbenchToolAzureDevopsConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkbenchToolAzureFunctionConfig) DeepCopyInto(out *WorkbenchToolAzureFunctionConfig) { + *out = *in + if in.InputSchema != nil { + in, out := &in.InputSchema, &out.InputSchema + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolAzureFunctionConfig. +func (in *WorkbenchToolAzureFunctionConfig) DeepCopy() *WorkbenchToolAzureFunctionConfig { + if in == nil { + return nil + } + out := new(WorkbenchToolAzureFunctionConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkbenchToolBitbucketConfig) DeepCopyInto(out *WorkbenchToolBitbucketConfig) { + *out = *in + if in.URL != nil { + in, out := &in.URL, &out.URL + *out = new(string) + **out = **in + } + if in.TokenSecretRef != nil { + in, out := &in.TokenSecretRef, &out.TokenSecretRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolBitbucketConfig. +func (in *WorkbenchToolBitbucketConfig) DeepCopy() *WorkbenchToolBitbucketConfig { + if in == nil { + return nil + } + out := new(WorkbenchToolBitbucketConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkbenchToolBitbucketDatacenterConfig) DeepCopyInto(out *WorkbenchToolBitbucketDatacenterConfig) { + *out = *in + if in.TokenSecretRef != nil { + in, out := &in.TokenSecretRef, &out.TokenSecretRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolBitbucketDatacenterConfig. +func (in *WorkbenchToolBitbucketDatacenterConfig) DeepCopy() *WorkbenchToolBitbucketDatacenterConfig { + if in == nil { + return nil + } + out := new(WorkbenchToolBitbucketDatacenterConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkbenchToolCloudRunConfig) DeepCopyInto(out *WorkbenchToolCloudRunConfig) { + *out = *in + if in.InputSchema != nil { + in, out := &in.InputSchema, &out.InputSchema + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolCloudRunConfig. +func (in *WorkbenchToolCloudRunConfig) DeepCopy() *WorkbenchToolCloudRunConfig { + if in == nil { + return nil + } + out := new(WorkbenchToolCloudRunConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkbenchToolCloudwatchConfig) DeepCopyInto(out *WorkbenchToolCloudwatchConfig) { *out = *in @@ -10697,6 +10812,11 @@ func (in *WorkbenchToolConfiguration) DeepCopyInto(out *WorkbenchToolConfigurati *out = new(WorkbenchToolElasticConfig) (*in).DeepCopyInto(*out) } + if in.Opensearch != nil { + in, out := &in.Opensearch, &out.Opensearch + *out = new(WorkbenchToolOpensearchConfig) + (*in).DeepCopyInto(*out) + } if in.Prometheus != nil { in, out := &in.Prometheus, &out.Prometheus *out = new(WorkbenchToolPrometheusConfig) @@ -10742,16 +10862,86 @@ func (in *WorkbenchToolConfiguration) DeepCopyInto(out *WorkbenchToolConfigurati *out = new(WorkbenchToolAzureConfig) (*in).DeepCopyInto(*out) } + if in.Sentry != nil { + in, out := &in.Sentry, &out.Sentry + *out = new(WorkbenchToolSentryConfig) + (*in).DeepCopyInto(*out) + } if in.Linear != nil { in, out := &in.Linear, &out.Linear *out = new(WorkbenchToolLinearConfig) (*in).DeepCopyInto(*out) } + if in.Slack != nil { + in, out := &in.Slack, &out.Slack + *out = new(WorkbenchToolSlackConfig) + (*in).DeepCopyInto(*out) + } + if in.Pagerduty != nil { + in, out := &in.Pagerduty, &out.Pagerduty + *out = new(WorkbenchToolPagerdutyConfig) + (*in).DeepCopyInto(*out) + } + if in.Teams != nil { + in, out := &in.Teams, &out.Teams + *out = new(WorkbenchToolTeamsConfig) + (*in).DeepCopyInto(*out) + } if in.Atlassian != nil { in, out := &in.Atlassian, &out.Atlassian *out = new(WorkbenchToolAtlassianConfig) (*in).DeepCopyInto(*out) } + if in.Exa != nil { + in, out := &in.Exa, &out.Exa + *out = new(WorkbenchToolExaConfig) + (*in).DeepCopyInto(*out) + } + if in.Github != nil { + in, out := &in.Github, &out.Github + *out = new(WorkbenchToolGithubConfig) + (*in).DeepCopyInto(*out) + } + if in.Gitlab != nil { + in, out := &in.Gitlab, &out.Gitlab + *out = new(WorkbenchToolGitlabConfig) + (*in).DeepCopyInto(*out) + } + if in.Bitbucket != nil { + in, out := &in.Bitbucket, &out.Bitbucket + *out = new(WorkbenchToolBitbucketConfig) + (*in).DeepCopyInto(*out) + } + if in.BitbucketDatacenter != nil { + in, out := &in.BitbucketDatacenter, &out.BitbucketDatacenter + *out = new(WorkbenchToolBitbucketDatacenterConfig) + (*in).DeepCopyInto(*out) + } + if in.AzureDevops != nil { + in, out := &in.AzureDevops, &out.AzureDevops + *out = new(WorkbenchToolAzureDevopsConfig) + (*in).DeepCopyInto(*out) + } + if in.Lambda != nil { + in, out := &in.Lambda, &out.Lambda + *out = new(WorkbenchToolLambdaConfig) + (*in).DeepCopyInto(*out) + } + if in.CloudRun != nil { + in, out := &in.CloudRun, &out.CloudRun + *out = new(WorkbenchToolCloudRunConfig) + (*in).DeepCopyInto(*out) + } + if in.AzureFunction != nil { + in, out := &in.AzureFunction, &out.AzureFunction + *out = new(WorkbenchToolAzureFunctionConfig) + (*in).DeepCopyInto(*out) + } + if in.Docker != nil { + in, out := &in.Docker, &out.Docker + *out = new(WorkbenchToolDockerConfig) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolConfiguration. @@ -10794,6 +10984,36 @@ func (in *WorkbenchToolDatadogConfig) DeepCopy() *WorkbenchToolDatadogConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkbenchToolDockerConfig) DeepCopyInto(out *WorkbenchToolDockerConfig) { + *out = *in + if in.URL != nil { + in, out := &in.URL, &out.URL + *out = new(string) + **out = **in + } + if in.Provider != nil { + in, out := &in.Provider, &out.Provider + *out = new(client.HelmAuthProvider) + **out = **in + } + if in.Auth != nil { + in, out := &in.Auth, &out.Auth + *out = new(HelmRepositoryAuth) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolDockerConfig. +func (in *WorkbenchToolDockerConfig) DeepCopy() *WorkbenchToolDockerConfig { + if in == nil { + return nil + } + out := new(WorkbenchToolDockerConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkbenchToolDynatraceConfig) DeepCopyInto(out *WorkbenchToolDynatraceConfig) { *out = *in @@ -10830,9 +11050,104 @@ func (in *WorkbenchToolElasticConfig) DeepCopy() *WorkbenchToolElasticConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkbenchToolExaConfig) DeepCopyInto(out *WorkbenchToolExaConfig) { + *out = *in + if in.APIKeySecretRef != nil { + in, out := &in.APIKeySecretRef, &out.APIKeySecretRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolExaConfig. +func (in *WorkbenchToolExaConfig) DeepCopy() *WorkbenchToolExaConfig { + if in == nil { + return nil + } + out := new(WorkbenchToolExaConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkbenchToolGithubConfig) DeepCopyInto(out *WorkbenchToolGithubConfig) { + *out = *in + if in.URL != nil { + in, out := &in.URL, &out.URL + *out = new(string) + **out = **in + } + if in.AccessTokenSecretRef != nil { + in, out := &in.AccessTokenSecretRef, &out.AccessTokenSecretRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } + if in.Toolset != nil { + in, out := &in.Toolset, &out.Toolset + *out = new(string) + **out = **in + } + if in.AppID != nil { + in, out := &in.AppID, &out.AppID + *out = new(string) + **out = **in + } + if in.InstallationID != nil { + in, out := &in.InstallationID, &out.InstallationID + *out = new(string) + **out = **in + } + if in.PrivateKeySecretRef != nil { + in, out := &in.PrivateKeySecretRef, &out.PrivateKeySecretRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolGithubConfig. +func (in *WorkbenchToolGithubConfig) DeepCopy() *WorkbenchToolGithubConfig { + if in == nil { + return nil + } + out := new(WorkbenchToolGithubConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkbenchToolGitlabConfig) DeepCopyInto(out *WorkbenchToolGitlabConfig) { + *out = *in + if in.URL != nil { + in, out := &in.URL, &out.URL + *out = new(string) + **out = **in + } + if in.TokenSecretRef != nil { + in, out := &in.TokenSecretRef, &out.TokenSecretRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolGitlabConfig. +func (in *WorkbenchToolGitlabConfig) DeepCopy() *WorkbenchToolGitlabConfig { + if in == nil { + return nil + } + out := new(WorkbenchToolGitlabConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkbenchToolHTTPConfig) DeepCopyInto(out *WorkbenchToolHTTPConfig) { *out = *in + if in.Function != nil { + in, out := &in.Function, &out.Function + *out = new(bool) + **out = **in + } if in.Headers != nil { in, out := &in.Headers, &out.Headers *out = make([]WorkbenchToolHTTPHeader, len(*in)) @@ -10917,6 +11232,26 @@ func (in *WorkbenchToolJaegerConfig) DeepCopy() *WorkbenchToolJaegerConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkbenchToolLambdaConfig) DeepCopyInto(out *WorkbenchToolLambdaConfig) { + *out = *in + if in.InputSchema != nil { + in, out := &in.InputSchema, &out.InputSchema + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolLambdaConfig. +func (in *WorkbenchToolLambdaConfig) DeepCopy() *WorkbenchToolLambdaConfig { + if in == nil { + return nil + } + out := new(WorkbenchToolLambdaConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkbenchToolLinearConfig) DeepCopyInto(out *WorkbenchToolLinearConfig) { *out = *in @@ -11004,6 +11339,66 @@ func (in *WorkbenchToolLokiConfig) DeepCopy() *WorkbenchToolLokiConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkbenchToolOpensearchConfig) DeepCopyInto(out *WorkbenchToolOpensearchConfig) { + *out = *in + if in.AWSAccessKeyIDSecretRef != nil { + in, out := &in.AWSAccessKeyIDSecretRef, &out.AWSAccessKeyIDSecretRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } + if in.AWSSecretAccessKeySecretRef != nil { + in, out := &in.AWSSecretAccessKeySecretRef, &out.AWSSecretAccessKeySecretRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } + if in.AWSRegion != nil { + in, out := &in.AWSRegion, &out.AWSRegion + *out = new(string) + **out = **in + } + if in.AssumeRoleArn != nil { + in, out := &in.AssumeRoleArn, &out.AssumeRoleArn + *out = new(string) + **out = **in + } + if in.UsePodIdentity != nil { + in, out := &in.UsePodIdentity, &out.UsePodIdentity + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolOpensearchConfig. +func (in *WorkbenchToolOpensearchConfig) DeepCopy() *WorkbenchToolOpensearchConfig { + if in == nil { + return nil + } + out := new(WorkbenchToolOpensearchConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkbenchToolPagerdutyConfig) DeepCopyInto(out *WorkbenchToolPagerdutyConfig) { + *out = *in + if in.APITokenSecretRef != nil { + in, out := &in.APITokenSecretRef, &out.APITokenSecretRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolPagerdutyConfig. +func (in *WorkbenchToolPagerdutyConfig) DeepCopy() *WorkbenchToolPagerdutyConfig { + if in == nil { + return nil + } + out := new(WorkbenchToolPagerdutyConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkbenchToolPrometheusConfig) DeepCopyInto(out *WorkbenchToolPrometheusConfig) { *out = *in @@ -11027,6 +11422,26 @@ func (in *WorkbenchToolPrometheusConfig) DeepCopyInto(out *WorkbenchToolPromethe *out = new(string) **out = **in } + if in.AWSSigv4 != nil { + in, out := &in.AWSSigv4, &out.AWSSigv4 + *out = new(bool) + **out = **in + } + if in.AWSAccessKeyIDSecretRef != nil { + in, out := &in.AWSAccessKeyIDSecretRef, &out.AWSAccessKeyIDSecretRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } + if in.AWSSecretAccessKeySecretRef != nil { + in, out := &in.AWSSecretAccessKeySecretRef, &out.AWSSecretAccessKeySecretRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } + if in.AWSRegion != nil { + in, out := &in.AWSRegion, &out.AWSRegion + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolPrometheusConfig. @@ -11039,6 +11454,51 @@ func (in *WorkbenchToolPrometheusConfig) DeepCopy() *WorkbenchToolPrometheusConf return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkbenchToolSentryConfig) DeepCopyInto(out *WorkbenchToolSentryConfig) { + *out = *in + if in.URL != nil { + in, out := &in.URL, &out.URL + *out = new(string) + **out = **in + } + if in.AccessTokenSecretRef != nil { + in, out := &in.AccessTokenSecretRef, &out.AccessTokenSecretRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolSentryConfig. +func (in *WorkbenchToolSentryConfig) DeepCopy() *WorkbenchToolSentryConfig { + if in == nil { + return nil + } + out := new(WorkbenchToolSentryConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkbenchToolSlackConfig) DeepCopyInto(out *WorkbenchToolSlackConfig) { + *out = *in + if in.BotTokenSecretRef != nil { + in, out := &in.BotTokenSecretRef, &out.BotTokenSecretRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolSlackConfig. +func (in *WorkbenchToolSlackConfig) DeepCopy() *WorkbenchToolSlackConfig { + if in == nil { + return nil + } + out := new(WorkbenchToolSlackConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkbenchToolSpec) DeepCopyInto(out *WorkbenchToolSpec) { *out = *in @@ -11052,6 +11512,11 @@ func (in *WorkbenchToolSpec) DeepCopyInto(out *WorkbenchToolSpec) { *out = make([]client.WorkbenchToolCategory, len(*in)) copy(*out, *in) } + if in.Approval != nil { + in, out := &in.Approval, &out.Approval + *out = new(bool) + **out = **in + } if in.ProjectRef != nil { in, out := &in.ProjectRef, &out.ProjectRef *out = new(v1.ObjectReference) @@ -11067,6 +11532,16 @@ func (in *WorkbenchToolSpec) DeepCopyInto(out *WorkbenchToolSpec) { *out = new(v1.ObjectReference) **out = **in } + if in.ScmConnectionRef != nil { + in, out := &in.ScmConnectionRef, &out.ScmConnectionRef + *out = new(v1.ObjectReference) + **out = **in + } + if in.Bindings != nil { + in, out := &in.Bindings, &out.Bindings + *out = new(Bindings) + (*in).DeepCopyInto(*out) + } if in.Configuration != nil { in, out := &in.Configuration, &out.Configuration *out = new(WorkbenchToolConfiguration) @@ -11119,6 +11594,22 @@ func (in *WorkbenchToolSplunkConfig) DeepCopy() *WorkbenchToolSplunkConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkbenchToolTeamsConfig) DeepCopyInto(out *WorkbenchToolTeamsConfig) { + *out = *in + in.ClientSecretSecretRef.DeepCopyInto(&out.ClientSecretSecretRef) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchToolTeamsConfig. +func (in *WorkbenchToolTeamsConfig) DeepCopy() *WorkbenchToolTeamsConfig { + if in == nil { + return nil + } + out := new(WorkbenchToolTeamsConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkbenchToolTempoConfig) DeepCopyInto(out *WorkbenchToolTempoConfig) { *out = *in diff --git a/go/controller/config/crd/bases/deployments.plural.sh_personas.yaml b/go/controller/config/crd/bases/deployments.plural.sh_personas.yaml index b056d1ed59..9b1e246490 100644 --- a/go/controller/config/crd/bases/deployments.plural.sh_personas.yaml +++ b/go/controller/config/crd/bases/deployments.plural.sh_personas.yaml @@ -160,11 +160,6 @@ spec: Permissions enables the permissions action in the flow header when set to true. This allows personas to manage flow permissions from the flow detail page. type: boolean - startWorkbenchJob: - description: |- - StartWorkbenchJob enables the "Start workbench job" action in the flow header when set to true. - This allows personas to launch workbench jobs directly from the flow detail page. - type: boolean pipelines: description: |- Pipelines enables access to flow pipeline features when set to true. @@ -175,6 +170,11 @@ spec: Previews enables access to flow preview environment features when set to true. This includes viewing and managing preview environments associated with flows. type: boolean + startWorkbenchJob: + description: |- + StartWorkbenchJob enables the "Start workbench job" action in the flow header when set to true. + This allows personas to launch workbench jobs directly from the flow detail page. + type: boolean workbenches: description: |- Workbenches enables access to flow workbench features when set to true. diff --git a/go/controller/config/crd/bases/deployments.plural.sh_workbenchtools.yaml b/go/controller/config/crd/bases/deployments.plural.sh_workbenchtools.yaml index fcaf370701..9163c27e28 100644 --- a/go/controller/config/crd/bases/deployments.plural.sh_workbenchtools.yaml +++ b/go/controller/config/crd/bases/deployments.plural.sh_workbenchtools.yaml @@ -52,6 +52,58 @@ spec: spec: description: WorkbenchToolSpec defines the desired state of a WorkbenchTool. properties: + approval: + description: Whether this tool requires approval before execution. + type: boolean + bindings: + description: Bindings define the read and write access policies for + this tool. + properties: + read: + description: Read bindings. + items: + description: Binding used to assign permissions to a resource + for a user or a group in the system. + properties: + UserID: + type: string + groupID: + type: string + groupName: + type: string + id: + description: |- + ID of a binding. + Deprecated. + Do not use. + type: string + userEmail: + type: string + type: object + type: array + write: + description: Write bindings. + items: + description: Binding used to assign permissions to a resource + for a user or a group in the system. + properties: + UserID: + type: string + groupID: + type: string + groupName: + type: string + id: + description: |- + ID of a binding. + Deprecated. + Do not use. + type: string + userEmail: + type: string + type: object + type: array + type: object categories: description: Categories for the tool. items: @@ -193,6 +245,10 @@ spec: - key type: object x-kubernetes-map-type: atomic + prometheusUrl: + description: Optional azure managed prometheus url if you + wish to use it for metrics. + type: string subscriptionId: description: Azure subscription id. type: string @@ -205,6 +261,138 @@ spec: - subscriptionId - tenantId type: object + azureDevops: + description: Azure DevOps connection (scm). + properties: + tokenSecretRef: + description: Reference to a secret key containing the Azure + DevOps personal access token. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + url: + description: Optional REST API root (defaults to https://dev.azure.com). + type: string + type: object + azureFunction: + description: Azure Function / Cloud Function configuration. + properties: + description: + description: Description of the function exposed to the agent. + type: string + identifier: + description: Cloud Function identifier. + type: string + inputSchema: + description: JSON schema for the tool input. + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - description + - identifier + type: object + bitbucket: + description: Bitbucket Cloud connection (scm). + properties: + tokenSecretRef: + description: Reference to a secret key containing the Bitbucket + app password or access token. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + url: + description: Optional Bitbucket Cloud API base URL. + type: string + type: object + bitbucketDatacenter: + description: Bitbucket Data Center connection (scm). + properties: + tokenSecretRef: + description: Reference to a secret key containing the HTTP + access token or personal access token. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + url: + description: Bitbucket Data Center REST API base URL. + type: string + required: + - url + type: object + cloudRun: + description: Google Cloud Run service configuration. + properties: + description: + description: Description of the function exposed to the agent. + type: string + identifier: + description: Cloud Run service identifier. + type: string + inputSchema: + description: JSON schema for the tool input. + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - description + - identifier + type: object cloudwatch: description: Cloudwatch connection (metrics, logs). properties: @@ -336,6 +524,330 @@ spec: description: Datadog site (e.g. datadoghq.com). type: string type: object + docker: + description: Docker/OCI registry connection. + properties: + auth: + description: Registry authentication credentials and optional + proxy. + properties: + aws: + description: |- + Aws specifies AWS-specific authentication for Amazon ECR and other AWS-hosted repositories. + Supports both static credentials and IAM role-based authentication for secure access + to private repositories hosted in Amazon Web Services. + properties: + accessKey: + description: |- + AccessKey specifies the AWS access key ID for authentication. + When using static credentials, this should be set along with the secret access key. + For enhanced security, consider using IAM roles instead of static credentials. + type: string + assumeRoleArn: + description: |- + AssumeRoleArn specifies an AWS IAM role ARN to assume for repository access. + This enables cross-account access and role-based authentication, providing + enhanced security and flexibility in AWS environments. + type: string + secretAccessKeySecretKeyRef: + description: |- + SecretAccessKeySecretKeyRef references a specific key within a Kubernetes Secret containing the secret access key. + This is the preferred method for storing AWS credentials as it allows multiple + credential sets to be organized within a single secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secretAccessKeySecretRef: + description: |- + SecretAccessKeySecretRef references a Kubernetes Secret containing the AWS secret access key. + The entire secret content will be used as the secret access key. + This approach is deprecated in favor of SecretAccessKeySecretKeyRef for better secret management. + properties: + name: + description: name is unique within a namespace + to reference a secret resource. + type: string + namespace: + description: namespace defines the space within + which the secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + type: object + azure: + description: |- + Azure specifies Azure-specific authentication for Azure Container Registry (ACR). + Supports service principal authentication and managed identity for secure access + to private repositories hosted in Microsoft Azure. + properties: + clientId: + description: |- + ClientID specifies the Azure service principal client ID. + This is used in conjunction with the client secret to authenticate with Azure services. + type: string + clientSecretSecretKeyRef: + description: |- + ClientSecretSecretKeyRef references a specific key within a Kubernetes Secret containing the client secret. + This is the preferred method for storing Azure credentials as it allows proper + secret organization and key-based access control. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + clientSecretSecretRef: + description: |- + ClientSecretSecretRef references a Kubernetes Secret containing the Azure service principal client secret. + The entire secret content will be used as the client secret. + This approach is deprecated in favor of ClientSecretSecretKeyRef for better secret management. + properties: + name: + description: name is unique within a namespace + to reference a secret resource. + type: string + namespace: + description: namespace defines the space within + which the secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + subscriptionId: + description: |- + SubscriptionID specifies the Azure subscription ID. + This identifies the Azure subscription containing the resources + and may be required for certain repository access scenarios. + type: string + tenantId: + description: |- + TenantID specifies the Azure Active Directory tenant ID. + This identifies the Azure AD instance that contains the service principal + and is required for proper authentication scope. + type: string + type: object + basic: + description: |- + Basic specifies username/password authentication for repositories that support HTTP Basic Auth. + Commonly used with private Helm repositories, Harbor registries, and other traditional + repository managers that implement standard HTTP authentication. + properties: + passwordSecretKeyRef: + description: |- + PasswordSecretKeyRef references a specific key within a Kubernetes Secret that contains the password. + This is the preferred method for password storage as it allows multiple credentials + to be stored in a single secret with proper key-based access. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + passwordSecretRef: + description: |- + PasswordSecretRef references a Kubernetes Secret containing the password for Basic authentication. + The entire secret content will be used as the password. + This approach is deprecated in favor of PasswordSecretKeyRef for better secret management. + properties: + name: + description: name is unique within a namespace + to reference a secret resource. + type: string + namespace: + description: namespace defines the space within + which the secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + username: + description: |- + Username specifies the username for HTTP Basic authentication. + This is typically a user account or service account name configured + in the target repository system. + type: string + required: + - username + type: object + bearer: + description: |- + Bearer specifies token-based authentication for repositories that support Bearer tokens. + Used with modern container registries and repositories that implement OAuth2 or similar + token-based authentication schemes. + properties: + tokenSecretKeyRef: + description: |- + TokenSecretKeyRef references a specific key within a Kubernetes Secret that contains the bearer token. + This is the preferred method for token storage as it allows multiple tokens + to be stored in a single secret with proper key-based access. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tokenSecretRef: + description: |- + TokenSecretRef references a Kubernetes Secret containing the bearer token. + The entire secret content will be used as the authentication token. + This approach is deprecated in favor of TokenSecretKeyRef for better secret management. + properties: + name: + description: name is unique within a namespace + to reference a secret resource. + type: string + namespace: + description: namespace defines the space within + which the secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + type: object + gcp: + description: |- + Gcp specifies Google Cloud-specific authentication for Google Artifact Registry. + Supports service account key authentication for secure access to private + repositories hosted in Google Cloud Platform. + properties: + applicationCredentialsSecretKeyRef: + description: |- + ApplicationCredentialsSecretKeyRef references a specific key within a Kubernetes Secret containing the service account JSON. + This is the preferred method for storing GCP credentials as it allows multiple + service account keys to be organized within a single secret with proper access control. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + applicationCredentialsSecretRef: + description: |- + ApplicationCredentialsSecretRef references a Kubernetes Secret containing the GCP service account key JSON. + The entire secret content will be used as the service account credentials. + This approach is deprecated in favor of ApplicationCredentialsSecretKeyRef for better secret management. + properties: + name: + description: name is unique within a namespace + to reference a secret resource. + type: string + namespace: + description: namespace defines the space within + which the secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + type: object + proxy: + description: Proxy configures an HTTP proxy for requests + to this Helm repository or OCI registry. + properties: + noproxy: + description: The comma-separated list of hosts that + should not be proxied, will behave equivalently + to a NOPROXY env var. + type: string + url: + description: The url of your HTTP proxy. + type: string + required: + - url + type: object + type: object + provider: + description: 'Registry authentication provider: BASIC, BEARER, + AWS, AZURE, or GCP.' + enum: + - BASIC + - BEARER + - GCP + - AZURE + - AWS + type: string + url: + description: Registry host or base URL (defaults to registry-1.docker.io). + type: string + type: object dynatrace: description: Dynatrace connection (metrics, logs, traces). properties: @@ -413,12 +925,149 @@ spec: - url - username type: object + exa: + description: Exa connection (search). + properties: + apiKeySecretRef: + description: Reference to a secret key containing the exa + API key. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + github: + description: GitHub connection (integration). + properties: + accessTokenSecretRef: + description: Reference to a secret key containing an optional + GitHub personal access token or fine-grained token. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + appId: + description: GitHub App ID (use with installationId and privateKey + instead of accessToken). + type: string + installationId: + description: GitHub App installation ID for this organization + or account. + type: string + privateKeySecretRef: + description: Reference to a secret key containing the PEM + private key for the GitHub App. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + toolset: + description: 'Optional native tool subset: issues, pull_requests, + repos, security, default/all, or omit for all tools.' + type: string + url: + description: Optional GitHub REST API base URL (defaults to + https://api.github.com/; set for GitHub Enterprise Server). + type: string + type: object + gitlab: + description: GitLab connection (scm). + properties: + tokenSecretRef: + description: Reference to a secret key containing the GitLab + personal access token or project/group token. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + url: + description: Optional GitLab API base URL (defaults to https://gitlab.com + when omitted). + type: string + type: object http: description: Http tool configuration. properties: body: description: Request body. type: string + function: + description: When true, exposes this HTTP tool as a workbench + action; execution may require approval when tool approval + is enabled. + type: boolean headers: description: Request headers. items: @@ -515,6 +1164,23 @@ spec: required: - url type: object + lambda: + description: AWS Lambda function configuration. + properties: + description: + description: Description of the function exposed to the agent. + type: string + inputSchema: + description: JSON schema for the tool input. + type: object + x-kubernetes-preserve-unknown-fields: true + lambdaArn: + description: AWS Lambda function ARN. + type: string + required: + - description + - lambdaArn + type: object linear: description: Linear connection (ticketing). properties: @@ -609,9 +1275,168 @@ spec: required: - url type: object + opensearch: + description: AWS OpenSearch connection (logs). + properties: + assumeRoleArn: + description: Optional IAM role ARN to assume before signing + OpenSearch requests. + type: string + awsAccessKeyIdSecretRef: + description: Reference to a secret key containing the AWS + access key id for SigV4 authentication. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + awsRegion: + description: AWS region for SigV4 authentication. + type: string + awsSecretAccessKeySecretRef: + description: Reference to a secret key containing the AWS + secret access key for SigV4 authentication. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + host: + description: AWS OpenSearch endpoint. + type: string + index: + description: OpenSearch index. + type: string + usePodIdentity: + description: Whether to use pod identity (IRSA/Workload Identity) + for AWS authentication instead of static credentials. + type: boolean + required: + - host + - index + type: object + pagerduty: + description: PagerDuty connection (integration). + properties: + apiTokenSecretRef: + description: Reference to a secret key containing the pagerduty + REST API key. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object prometheus: description: Prometheus connection (metrics). properties: + awsAccessKeyIdSecretRef: + description: Reference to a secret key containing the AWS + access key id for SigV4 authentication. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + awsRegion: + description: AWS region for SigV4 authentication. + type: string + awsSecretAccessKeySecretRef: + description: Reference to a secret key containing the AWS + secret access key for SigV4 authentication. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + awsSigv4: + description: Whether to sign requests with AWS SigV4. + type: boolean passwordSecretRef: description: Reference to a secret key containing the basic auth password. @@ -674,6 +1499,68 @@ spec: required: - url type: object + sentry: + description: Sentry connection (error tracking). + properties: + accessTokenSecretRef: + description: Reference to a secret key containing the Sentry + user auth token or internal integration token. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + url: + description: Optional Sentry API host (defaults to https://sentry.io; + set for self-hosted). + type: string + type: object + slack: + description: Slack connection (integration). + properties: + botTokenSecretRef: + description: Reference to a secret key containing the slack + bot user OAuth token (xoxb-...). + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object splunk: description: Splunk connection (logs). properties: @@ -736,6 +1623,45 @@ spec: required: - url type: object + teams: + description: Microsoft Teams / Graph connection (integration). + properties: + clientId: + description: Microsoft Entra application (client) id. + type: string + clientSecretSecretRef: + description: Reference to a secret key containing the microsoft + entra client secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + tenantId: + description: Microsoft Entra tenant (directory) id. + type: string + required: + - clientId + - clientSecretSecretRef + - tenantId + type: object tempo: description: Tempo connection (traces). properties: @@ -914,6 +1840,50 @@ spec: example: 5m30s type: string type: object + scmConnectionRef: + description: The SCM connection for this tool (e.g. shared Git provider + credentials). + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic tool: description: The type of tool. enum: @@ -933,6 +1903,20 @@ spec: - AZURE - CLOUD - JAEGER + - EXA + - GITHUB + - SLACK + - TEAMS + - GITLAB + - BITBUCKET + - BITBUCKET_DATACENTER + - AZURE_DEVOPS + - PAGERDUTY + - OPENSEARCH + - LAMBDA + - CLOUD_RUN + - AZURE_FUNCTION + - DOCKER type: string required: - tool diff --git a/go/controller/config/samples/workbench.yaml b/go/controller/config/samples/workbench.yaml index 284333008d..8ed7d7211d 100644 --- a/go/controller/config/samples/workbench.yaml +++ b/go/controller/config/samples/workbench.yaml @@ -14,6 +14,24 @@ stringData: clientSecret: "placeholder" accessToken: "placeholder" apiToken: "placeholder" + botToken: "xoxb-placeholder" + privateKey: | + -----BEGIN RSA PRIVATE KEY----- + placeholder + -----END RSA PRIVATE KEY----- +--- +# Dedicated Docker registry secret so credential rotation can be tested independently: +# kubectl patch secret workbench-docker-creds -n default --type merge -p '{"stringData":{"token":"rotated-token"}}' +# Then confirm the WorkbenchTool reconciles and Console receives the new token. +apiVersion: v1 +kind: Secret +metadata: + name: workbench-docker-creds + namespace: default +stringData: + token: "initial-registry-token" + password: "initial-registry-password" + username: "registry-user" --- apiVersion: deployments.plural.sh/v1alpha1 kind: WorkbenchTool @@ -31,10 +49,12 @@ spec: tool: HTTP categories: - INTEGRATION + approval: true configuration: http: url: https://api.example.com/health method: GET + function: true inputSchema: type: object properties: @@ -102,6 +122,14 @@ spec: name: workbench-tool-creds key: password tenantId: my-tenant + awsSigv4: true + awsRegion: us-east-1 + awsAccessKeyIdSecretRef: + name: workbench-tool-creds + key: accessKeyId + awsSecretAccessKeySecretRef: + name: workbench-tool-creds + key: secretAccessKey --- apiVersion: deployments.plural.sh/v1alpha1 kind: WorkbenchTool @@ -154,6 +182,36 @@ spec: --- apiVersion: deployments.plural.sh/v1alpha1 kind: WorkbenchTool +metadata: + labels: + app.kubernetes.io/name: workbenchtool + app.kubernetes.io/instance: workbenchtool-opensearch-sample + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: controller + name: workbenchtool-opensearch-sample + namespace: default +spec: + name: workbenchtool_opensearch_sample + tool: OPENSEARCH + categories: + - LOGS + configuration: + opensearch: + host: https://search-my-domain.us-east-1.es.amazonaws.com + index: logs-* + awsRegion: us-east-1 + assumeRoleArn: arn:aws:iam::123456789012:role/opensearch-reader + usePodIdentity: false + awsAccessKeyIdSecretRef: + name: workbench-tool-creds + key: accessKeyId + awsSecretAccessKeySecretRef: + name: workbench-tool-creds + key: secretAccessKey +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: WorkbenchTool metadata: labels: app.kubernetes.io/name: workbenchtool @@ -282,6 +340,30 @@ spec: clientSecretSecretRef: name: workbench-tool-creds key: clientSecret + prometheusUrl: https://my-workspace.eastus.prometheus.monitor.azure.com +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: WorkbenchTool +metadata: + labels: + app.kubernetes.io/name: workbenchtool + app.kubernetes.io/instance: workbenchtool-sentry-sample + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: controller + name: workbenchtool-sentry-sample + namespace: default +spec: + name: workbenchtool_sentry_sample + tool: SENTRY + categories: + - ERROR_TRACKING + configuration: + sentry: + url: https://sentry.io + accessTokenSecretRef: + name: workbench-tool-creds + key: accessToken --- apiVersion: deployments.plural.sh/v1alpha1 kind: WorkbenchTool @@ -330,6 +412,356 @@ spec: --- apiVersion: deployments.plural.sh/v1alpha1 kind: WorkbenchTool +metadata: + labels: + app.kubernetes.io/name: workbenchtool + app.kubernetes.io/instance: workbenchtool-slack-sample + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: controller + name: workbenchtool-slack-sample + namespace: default +spec: + name: workbenchtool_slack_sample + tool: SLACK + categories: + - CHAT + configuration: + slack: + botTokenSecretRef: + name: workbench-tool-creds + key: botToken +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: WorkbenchTool +metadata: + labels: + app.kubernetes.io/name: workbenchtool + app.kubernetes.io/instance: workbenchtool-pagerduty-sample + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: controller + name: workbenchtool-pagerduty-sample + namespace: default +spec: + name: workbenchtool_pagerduty_sample + tool: PAGERDUTY + categories: + - INTEGRATION + configuration: + pagerduty: + apiTokenSecretRef: + name: workbench-tool-creds + key: apiToken +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: WorkbenchTool +metadata: + labels: + app.kubernetes.io/name: workbenchtool + app.kubernetes.io/instance: workbenchtool-teams-sample + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: controller + name: workbenchtool-teams-sample + namespace: default +spec: + name: workbenchtool_teams_sample + tool: TEAMS + categories: + - CHAT + configuration: + teams: + clientId: 00000000-0000-0000-0000-000000000010 + tenantId: 00000000-0000-0000-0000-000000000011 + clientSecretSecretRef: + name: workbench-tool-creds + key: clientSecret +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: WorkbenchTool +metadata: + labels: + app.kubernetes.io/name: workbenchtool + app.kubernetes.io/instance: workbenchtool-exa-sample + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: controller + name: workbenchtool-exa-sample + namespace: default +spec: + name: workbenchtool_exa_sample + tool: EXA + categories: + - SEARCH + configuration: + exa: + apiKeySecretRef: + name: workbench-tool-creds + key: apiKey +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: WorkbenchTool +metadata: + labels: + app.kubernetes.io/name: workbenchtool + app.kubernetes.io/instance: workbenchtool-github-sample + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: controller + name: workbenchtool-github-sample + namespace: default +spec: + name: workbenchtool_github_sample + tool: GITHUB + categories: + - SCM + scmConnectionRef: + name: github + configuration: + github: + url: https://api.github.com/ + toolset: default + appId: "123456" + installationId: "987654" + privateKeySecretRef: + name: workbench-tool-creds + key: privateKey +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: WorkbenchTool +metadata: + labels: + app.kubernetes.io/name: workbenchtool + app.kubernetes.io/instance: workbenchtool-gitlab-sample + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: controller + name: workbenchtool-gitlab-sample + namespace: default +spec: + name: workbenchtool_gitlab_sample + tool: GITLAB + categories: + - SCM + configuration: + gitlab: + url: https://gitlab.com + tokenSecretRef: + name: workbench-tool-creds + key: token +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: WorkbenchTool +metadata: + labels: + app.kubernetes.io/name: workbenchtool + app.kubernetes.io/instance: workbenchtool-bitbucket-sample + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: controller + name: workbenchtool-bitbucket-sample + namespace: default +spec: + name: workbenchtool_bitbucket_sample + tool: BITBUCKET + categories: + - SCM + configuration: + bitbucket: + url: https://api.bitbucket.org + tokenSecretRef: + name: workbench-tool-creds + key: token +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: WorkbenchTool +metadata: + labels: + app.kubernetes.io/name: workbenchtool + app.kubernetes.io/instance: workbenchtool-bitbucket-dc-sample + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: controller + name: workbenchtool-bitbucket-dc-sample + namespace: default +spec: + name: workbenchtool_bitbucket_dc_sample + tool: BITBUCKET_DATACENTER + categories: + - SCM + configuration: + bitbucketDatacenter: + url: https://bitbucket.example.com + tokenSecretRef: + name: workbench-tool-creds + key: token +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: WorkbenchTool +metadata: + labels: + app.kubernetes.io/name: workbenchtool + app.kubernetes.io/instance: workbenchtool-azure-devops-sample + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: controller + name: workbenchtool-azure-devops-sample + namespace: default +spec: + name: workbenchtool_azure_devops_sample + tool: AZURE_DEVOPS + categories: + - SCM + configuration: + azureDevops: + url: https://dev.azure.com/my-org + tokenSecretRef: + name: workbench-tool-creds + key: token +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: WorkbenchTool +metadata: + labels: + app.kubernetes.io/name: workbenchtool + app.kubernetes.io/instance: workbenchtool-lambda-sample + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: controller + name: workbenchtool-lambda-sample + namespace: default +spec: + name: workbenchtool_lambda_sample + tool: LAMBDA + categories: + - FUNCTION + approval: true + configuration: + lambda: + lambdaArn: arn:aws:lambda:us-east-1:123456789012:function:my-function + description: Invoke a custom Lambda function from the workbench + inputSchema: + type: object + properties: + payload: + type: string + required: + - payload +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: WorkbenchTool +metadata: + labels: + app.kubernetes.io/name: workbenchtool + app.kubernetes.io/instance: workbenchtool-cloud-run-sample + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: controller + name: workbenchtool-cloud-run-sample + namespace: default +spec: + name: workbenchtool_cloud_run_sample + tool: CLOUD_RUN + categories: + - FUNCTION + configuration: + cloudRun: + identifier: projects/my-project/locations/us-central1/services/my-service + description: Invoke a Cloud Run service from the workbench + inputSchema: + type: object + properties: + path: + type: string +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: WorkbenchTool +metadata: + labels: + app.kubernetes.io/name: workbenchtool + app.kubernetes.io/instance: workbenchtool-azure-function-sample + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: controller + name: workbenchtool-azure-function-sample + namespace: default +spec: + name: workbenchtool_azure_function_sample + tool: AZURE_FUNCTION + categories: + - FUNCTION + configuration: + azureFunction: + identifier: https://my-function-app.azurewebsites.net/api/HttpTrigger + description: Invoke an Azure Function from the workbench + inputSchema: + type: object + properties: + name: + type: string +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: WorkbenchTool +metadata: + labels: + app.kubernetes.io/name: workbenchtool + app.kubernetes.io/instance: workbenchtool-docker-sample + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: controller + name: workbenchtool-docker-sample + namespace: default +spec: + name: workbenchtool_docker_sample + tool: DOCKER + categories: + - INTEGRATION + reconciliation: + driftDetection: true + interval: 1m + configuration: + docker: + url: registry-1.docker.io + provider: BEARER + auth: + proxy: + url: http://proxy.example.com:8080 + noproxy: localhost,127.0.0.1 + bearer: + tokenSecretKeyRef: + name: workbench-docker-creds + key: token +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: WorkbenchTool +metadata: + labels: + app.kubernetes.io/name: workbenchtool + app.kubernetes.io/instance: workbenchtool-docker-basic-sample + app.kubernetes.io/part-of: controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: controller + name: workbenchtool-docker-basic-sample + namespace: default +spec: + name: workbenchtool_docker_basic_sample + tool: DOCKER + categories: + - INTEGRATION + configuration: + docker: + url: ghcr.io + provider: BASIC + auth: + basic: + username: registry-user + passwordSecretKeyRef: + name: workbench-docker-creds + key: password +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: WorkbenchTool metadata: labels: app.kubernetes.io/name: workbenchtool @@ -402,6 +834,26 @@ spec: namespace: default key: secretAccessKey --- +apiVersion: v1 +kind: Secret +metadata: + name: github-token + namespace: default +stringData: + token: "test-token" +--- +apiVersion: deployments.plural.sh/v1alpha1 +kind: ScmConnection +metadata: + name: github +spec: + name: scm-test-github + type: GITHUB + tokenSecretRef: + name: github-token + namespace: default + username: "johndoe" +--- apiVersion: deployments.plural.sh/v1alpha1 kind: Workbench metadata: @@ -428,6 +880,8 @@ spec: namespace: default - name: workbenchtool-elastic-sample namespace: default + - name: workbenchtool-opensearch-sample + namespace: default - name: workbenchtool-tempo-sample namespace: default - name: workbenchtool-datadog-sample @@ -438,10 +892,40 @@ spec: namespace: default - name: workbenchtool-azure-sample namespace: default + - name: workbenchtool-sentry-sample + namespace: default - name: workbenchtool-linear-sample namespace: default - name: workbenchtool-atlassian-sample namespace: default + - name: workbenchtool-slack-sample + namespace: default + - name: workbenchtool-pagerduty-sample + namespace: default + - name: workbenchtool-teams-sample + namespace: default + - name: workbenchtool-exa-sample + namespace: default + - name: workbenchtool-github-sample + namespace: default + - name: workbenchtool-gitlab-sample + namespace: default + - name: workbenchtool-bitbucket-sample + namespace: default + - name: workbenchtool-bitbucket-dc-sample + namespace: default + - name: workbenchtool-azure-devops-sample + namespace: default + - name: workbenchtool-lambda-sample + namespace: default + - name: workbenchtool-cloud-run-sample + namespace: default + - name: workbenchtool-azure-function-sample + namespace: default + - name: workbenchtool-docker-sample + namespace: default + - name: workbenchtool-docker-basic-sample + namespace: default - name: workbenchtool-mcp-sample namespace: default - name: workbenchtool-cloud-sample diff --git a/go/controller/docs/api.md b/go/controller/docs/api.md index 30f3ae25c7..e66bba9774 100644 --- a/go/controller/docs/api.md +++ b/go/controller/docs/api.md @@ -380,6 +380,7 @@ _Appears in:_ - [ProjectSpec](#projectspec) - [ServiceSpec](#servicespec) - [WorkbenchSpec](#workbenchspec) +- [WorkbenchToolSpec](#workbenchtoolspec) | Field | Description | Default | Validation | | --- | --- | --- | --- | @@ -1976,6 +1977,7 @@ _Appears in:_ - [HelmRepositorySpec](#helmrepositoryspec) - [ObserverHelm](#observerhelm) - [ObserverOci](#observeroci) +- [WorkbenchToolDockerConfig](#workbenchtooldockerconfig) | Field | Description | Default | Validation | | --- | --- | --- | --- | @@ -5761,6 +5763,94 @@ _Appears in:_ | `tenantId` _string_ | Azure tenant id. | | Required: \{\}
| | `clientId` _string_ | Azure client id. | | Required: \{\}
| | `clientSecretSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the azure client secret. | | Required: \{\}
| +| `prometheusUrl` _string_ | Optional azure managed prometheus url if you wish to use it for metrics. | | Optional: \{\}
| + + +#### WorkbenchToolAzureDevopsConfig + + + +WorkbenchToolAzureDevopsConfig defines an azure devops connection. + + + +_Appears in:_ +- [WorkbenchToolConfiguration](#workbenchtoolconfiguration) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `url` _string_ | Optional REST API root (defaults to https://dev.azure.com). | | Optional: \{\}
| +| `tokenSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the Azure DevOps personal access token. | | Optional: \{\}
| + + +#### WorkbenchToolAzureFunctionConfig + + + +WorkbenchToolAzureFunctionConfig defines an Azure Function / Cloud Function configuration. + + + +_Appears in:_ +- [WorkbenchToolConfiguration](#workbenchtoolconfiguration) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `identifier` _string_ | Cloud Function identifier. | | Required: \{\}
| +| `description` _string_ | Description of the function exposed to the agent. | | Required: \{\}
| +| `inputSchema` _[RawExtension](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#rawextension-runtime-pkg)_ | JSON schema for the tool input. | | Optional: \{\}
| + + +#### WorkbenchToolBitbucketConfig + + + +WorkbenchToolBitbucketConfig defines a bitbucket cloud connection. + + + +_Appears in:_ +- [WorkbenchToolConfiguration](#workbenchtoolconfiguration) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `url` _string_ | Optional Bitbucket Cloud API base URL. | | Optional: \{\}
| +| `tokenSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the Bitbucket app password or access token. | | Optional: \{\}
| + + +#### WorkbenchToolBitbucketDatacenterConfig + + + +WorkbenchToolBitbucketDatacenterConfig defines a bitbucket data center connection. + + + +_Appears in:_ +- [WorkbenchToolConfiguration](#workbenchtoolconfiguration) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `url` _string_ | Bitbucket Data Center REST API base URL. | | Required: \{\}
| +| `tokenSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the HTTP access token or personal access token. | | Optional: \{\}
| + + +#### WorkbenchToolCloudRunConfig + + + +WorkbenchToolCloudRunConfig defines a Google Cloud Run service configuration. + + + +_Appears in:_ +- [WorkbenchToolConfiguration](#workbenchtoolconfiguration) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `identifier` _string_ | Cloud Run service identifier. | | Required: \{\}
| +| `description` _string_ | Description of the function exposed to the agent. | | Required: \{\}
| +| `inputSchema` _[RawExtension](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#rawextension-runtime-pkg)_ | JSON schema for the tool input. | | Optional: \{\}
| #### WorkbenchToolCloudwatchConfig @@ -5800,6 +5890,7 @@ _Appears in:_ | --- | --- | --- | --- | | `http` _[WorkbenchToolHTTPConfig](#workbenchtoolhttpconfig)_ | Http tool configuration. | | Optional: \{\}
| | `elastic` _[WorkbenchToolElasticConfig](#workbenchtoolelasticconfig)_ | Elasticsearch connection (logs). | | Optional: \{\}
| +| `opensearch` _[WorkbenchToolOpensearchConfig](#workbenchtoolopensearchconfig)_ | AWS OpenSearch connection (logs). | | Optional: \{\}
| | `prometheus` _[WorkbenchToolPrometheusConfig](#workbenchtoolprometheusconfig)_ | Prometheus connection (metrics). | | Optional: \{\}
| | `loki` _[WorkbenchToolLokiConfig](#workbenchtoollokiconfig)_ | Loki connection (logs). | | Optional: \{\}
| | `tempo` _[WorkbenchToolTempoConfig](#workbenchtooltempoconfig)_ | Tempo connection (traces). | | Optional: \{\}
| @@ -5809,8 +5900,22 @@ _Appears in:_ | `dynatrace` _[WorkbenchToolDynatraceConfig](#workbenchtooldynatraceconfig)_ | Dynatrace connection (metrics, logs, traces). | | Optional: \{\}
| | `cloudwatch` _[WorkbenchToolCloudwatchConfig](#workbenchtoolcloudwatchconfig)_ | Cloudwatch connection (metrics, logs). | | Optional: \{\}
| | `azure` _[WorkbenchToolAzureConfig](#workbenchtoolazureconfig)_ | Azure monitor connection (metrics). | | Optional: \{\}
| +| `sentry` _[WorkbenchToolSentryConfig](#workbenchtoolsentryconfig)_ | Sentry connection (error tracking). | | Optional: \{\}
| | `linear` _[WorkbenchToolLinearConfig](#workbenchtoollinearconfig)_ | Linear connection (ticketing). | | Optional: \{\}
| +| `slack` _[WorkbenchToolSlackConfig](#workbenchtoolslackconfig)_ | Slack connection (integration). | | Optional: \{\}
| +| `pagerduty` _[WorkbenchToolPagerdutyConfig](#workbenchtoolpagerdutyconfig)_ | PagerDuty connection (integration). | | Optional: \{\}
| +| `teams` _[WorkbenchToolTeamsConfig](#workbenchtoolteamsconfig)_ | Microsoft Teams / Graph connection (integration). | | Optional: \{\}
| | `atlassian` _[WorkbenchToolAtlassianConfig](#workbenchtoolatlassianconfig)_ | Atlassian/jira connection (ticketing). | | Optional: \{\}
| +| `exa` _[WorkbenchToolExaConfig](#workbenchtoolexaconfig)_ | Exa connection (search). | | Optional: \{\}
| +| `github` _[WorkbenchToolGithubConfig](#workbenchtoolgithubconfig)_ | GitHub connection (integration). | | Optional: \{\}
| +| `gitlab` _[WorkbenchToolGitlabConfig](#workbenchtoolgitlabconfig)_ | GitLab connection (scm). | | Optional: \{\}
| +| `bitbucket` _[WorkbenchToolBitbucketConfig](#workbenchtoolbitbucketconfig)_ | Bitbucket Cloud connection (scm). | | Optional: \{\}
| +| `bitbucketDatacenter` _[WorkbenchToolBitbucketDatacenterConfig](#workbenchtoolbitbucketdatacenterconfig)_ | Bitbucket Data Center connection (scm). | | Optional: \{\}
| +| `azureDevops` _[WorkbenchToolAzureDevopsConfig](#workbenchtoolazuredevopsconfig)_ | Azure DevOps connection (scm). | | Optional: \{\}
| +| `lambda` _[WorkbenchToolLambdaConfig](#workbenchtoollambdaconfig)_ | AWS Lambda function configuration. | | Optional: \{\}
| +| `cloudRun` _[WorkbenchToolCloudRunConfig](#workbenchtoolcloudrunconfig)_ | Google Cloud Run service configuration. | | Optional: \{\}
| +| `azureFunction` _[WorkbenchToolAzureFunctionConfig](#workbenchtoolazurefunctionconfig)_ | Azure Function / Cloud Function configuration. | | Optional: \{\}
| +| `docker` _[WorkbenchToolDockerConfig](#workbenchtooldockerconfig)_ | Docker/OCI registry connection. | | Optional: \{\}
| #### WorkbenchToolDatadogConfig @@ -5831,6 +5936,25 @@ _Appears in:_ | `appKeySecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the datadog application key. | | Optional: \{\}
| +#### WorkbenchToolDockerConfig + + + +WorkbenchToolDockerConfig defines a docker/OCI registry connection. +Auth secrets are resolved by the reconciler via HelmRepositoryAuth helpers. + + + +_Appears in:_ +- [WorkbenchToolConfiguration](#workbenchtoolconfiguration) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `url` _string_ | Registry host or base URL (defaults to registry-1.docker.io). | | Optional: \{\}
| +| `provider` _[HelmAuthProvider](#helmauthprovider)_ | Registry authentication provider: BASIC, BEARER, AWS, AZURE, or GCP. | | Enum: [BASIC BEARER GCP AZURE AWS]
Optional: \{\}
| +| `auth` _[HelmRepositoryAuth](#helmrepositoryauth)_ | Registry authentication credentials and optional proxy. | | Optional: \{\}
| + + #### WorkbenchToolDynatraceConfig @@ -5867,6 +5991,60 @@ _Appears in:_ | `index` _string_ | Elasticsearch index. | | Required: \{\}
| +#### WorkbenchToolExaConfig + + + +WorkbenchToolExaConfig defines an exa connection. + + + +_Appears in:_ +- [WorkbenchToolConfiguration](#workbenchtoolconfiguration) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiKeySecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the exa API key. | | Optional: \{\}
| + + +#### WorkbenchToolGithubConfig + + + +WorkbenchToolGithubConfig defines a github connection. + + + +_Appears in:_ +- [WorkbenchToolConfiguration](#workbenchtoolconfiguration) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `url` _string_ | Optional GitHub REST API base URL (defaults to https://api.github.com/; set for GitHub Enterprise Server). | | Optional: \{\}
| +| `accessTokenSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing an optional GitHub personal access token or fine-grained token. | | Optional: \{\}
| +| `toolset` _string_ | Optional native tool subset: issues, pull_requests, repos, security, default/all, or omit for all tools. | | Optional: \{\}
| +| `appId` _string_ | GitHub App ID (use with installationId and privateKey instead of accessToken). | | Optional: \{\}
| +| `installationId` _string_ | GitHub App installation ID for this organization or account. | | Optional: \{\}
| +| `privateKeySecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the PEM private key for the GitHub App. | | Optional: \{\}
| + + +#### WorkbenchToolGitlabConfig + + + +WorkbenchToolGitlabConfig defines a gitlab connection. + + + +_Appears in:_ +- [WorkbenchToolConfiguration](#workbenchtoolconfiguration) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `url` _string_ | Optional GitLab API base URL (defaults to https://gitlab.com when omitted). | | Optional: \{\}
| +| `tokenSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the GitLab personal access token or project/group token. | | Optional: \{\}
| + + #### WorkbenchToolHTTPConfig @@ -5882,6 +6060,7 @@ _Appears in:_ | --- | --- | --- | --- | | `url` _string_ | The request URL. | | Format: uri
Required: \{\}
Type: string
| | `method` _[WorkbenchToolHTTPMethod](#workbenchtoolhttpmethod)_ | The HTTP method. | | Enum: [GET POST PUT DELETE PATCH]
Required: \{\}
| +| `function` _boolean_ | When true, exposes this HTTP tool as a workbench action; execution may require approval when tool approval is enabled. | | Optional: \{\}
| | `headers` _[WorkbenchToolHTTPHeader](#workbenchtoolhttpheader) array_ | Request headers. | | Optional: \{\}
| | `body` _string_ | Request body. | | Optional: \{\}
Type: string
| | `inputSchema` _[RawExtension](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#rawextension-runtime-pkg)_ | JSON schema for the tool input. | | Required: \{\}
| @@ -5923,6 +6102,24 @@ _Appears in:_ | `passwordSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the basic auth password. | | Optional: \{\}
| +#### WorkbenchToolLambdaConfig + + + +WorkbenchToolLambdaConfig defines an AWS Lambda function configuration. + + + +_Appears in:_ +- [WorkbenchToolConfiguration](#workbenchtoolconfiguration) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `lambdaArn` _string_ | AWS Lambda function ARN. | | Required: \{\}
| +| `description` _string_ | Description of the function exposed to the agent. | | Required: \{\}
| +| `inputSchema` _[RawExtension](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#rawextension-runtime-pkg)_ | JSON schema for the tool input. | | Optional: \{\}
| + + #### WorkbenchToolLinearConfig @@ -5959,6 +6156,44 @@ _Appears in:_ | `tenantId` _string_ | Optional tenant id. | | Optional: \{\}
| +#### WorkbenchToolOpensearchConfig + + + +WorkbenchToolOpensearchConfig defines an AWS OpenSearch connection. + + + +_Appears in:_ +- [WorkbenchToolConfiguration](#workbenchtoolconfiguration) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `host` _string_ | AWS OpenSearch endpoint. | | Required: \{\}
| +| `index` _string_ | OpenSearch index. | | Required: \{\}
| +| `awsAccessKeyIdSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the AWS access key id for SigV4 authentication. | | Optional: \{\}
| +| `awsSecretAccessKeySecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the AWS secret access key for SigV4 authentication. | | Optional: \{\}
| +| `awsRegion` _string_ | AWS region for SigV4 authentication. | | Optional: \{\}
| +| `assumeRoleArn` _string_ | Optional IAM role ARN to assume before signing OpenSearch requests. | | Optional: \{\}
| +| `usePodIdentity` _boolean_ | Whether to use pod identity (IRSA/Workload Identity) for AWS authentication instead of static credentials. | | Optional: \{\}
| + + +#### WorkbenchToolPagerdutyConfig + + + +WorkbenchToolPagerdutyConfig defines a pagerduty connection. + + + +_Appears in:_ +- [WorkbenchToolConfiguration](#workbenchtoolconfiguration) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiTokenSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the pagerduty REST API key. | | Optional: \{\}
| + + #### WorkbenchToolPrometheusConfig @@ -5977,6 +6212,43 @@ _Appears in:_ | `username` _string_ | Basic auth username. | | Optional: \{\}
| | `passwordSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the basic auth password. | | Optional: \{\}
| | `tenantId` _string_ | Optional tenant id (e.g. for Mimir). | | Optional: \{\}
| +| `awsSigv4` _boolean_ | Whether to sign requests with AWS SigV4. | | Optional: \{\}
| +| `awsAccessKeyIdSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the AWS access key id for SigV4 authentication. | | Optional: \{\}
| +| `awsSecretAccessKeySecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the AWS secret access key for SigV4 authentication. | | Optional: \{\}
| +| `awsRegion` _string_ | AWS region for SigV4 authentication. | | Optional: \{\}
| + + +#### WorkbenchToolSentryConfig + + + +WorkbenchToolSentryConfig defines a sentry connection. + + + +_Appears in:_ +- [WorkbenchToolConfiguration](#workbenchtoolconfiguration) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `url` _string_ | Optional Sentry API host (defaults to https://sentry.io; set for self-hosted). | | Optional: \{\}
| +| `accessTokenSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the Sentry user auth token or internal integration token. | | Optional: \{\}
| + + +#### WorkbenchToolSlackConfig + + + +WorkbenchToolSlackConfig defines a slack connection. + + + +_Appears in:_ +- [WorkbenchToolConfiguration](#workbenchtoolconfiguration) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `botTokenSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the slack bot user OAuth token (xoxb-...). | | Optional: \{\}
| #### WorkbenchToolSpec @@ -5993,11 +6265,14 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `name` _string_ | The name of the tool (a-z, 0-9, underscores). If not set, metadata.name is used. | | Optional: \{\}
Pattern: `^[a-z0-9_]+$`
Type: string
| -| `tool` _[WorkbenchToolType](#workbenchtooltype)_ | The type of tool. | | Enum: [HTTP ELASTIC DATADOG PROMETHEUS LOKI TEMPO SENTRY MCP LINEAR ATLASSIAN SPLUNK DYNATRACE CLOUDWATCH AZURE CLOUD JAEGER]
Required: \{\}
| +| `tool` _[WorkbenchToolType](#workbenchtooltype)_ | The type of tool. | | Enum: [HTTP ELASTIC DATADOG PROMETHEUS LOKI TEMPO SENTRY MCP LINEAR ATLASSIAN SPLUNK DYNATRACE CLOUDWATCH AZURE CLOUD JAEGER EXA GITHUB SLACK TEAMS GITLAB BITBUCKET BITBUCKET_DATACENTER AZURE_DEVOPS PAGERDUTY OPENSEARCH LAMBDA CLOUD_RUN AZURE_FUNCTION DOCKER]
Required: \{\}
| | `categories` _WorkbenchToolCategory array_ | Categories for the tool. | | Optional: \{\}
| +| `approval` _boolean_ | Whether this tool requires approval before execution. | | Optional: \{\}
| | `projectRef` _[ObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectreference-v1-core)_ | The project for this tool. | | Optional: \{\}
| | `mcpServerRef` _[ObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectreference-v1-core)_ | The mcp server for this tool. | | Optional: \{\}
| | `cloudConnectionRef` _[ObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectreference-v1-core)_ | The cloud connection for this tool (e.g. infrastructure cloud tools). | | Optional: \{\}
| +| `scmConnectionRef` _[ObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectreference-v1-core)_ | The SCM connection for this tool (e.g. shared Git provider credentials). | | Optional: \{\}
| +| `bindings` _[Bindings](#bindings)_ | Bindings define the read and write access policies for this tool. | | Optional: \{\}
| | `configuration` _[WorkbenchToolConfiguration](#workbenchtoolconfiguration)_ | Tool configuration (e.g. HTTP). | | Optional: \{\}
| | `reconciliation` _[Reconciliation](#reconciliation)_ | | | Optional: \{\}
| @@ -6021,6 +6296,24 @@ _Appears in:_ | `passwordSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the basic auth password. | | Optional: \{\}
| +#### WorkbenchToolTeamsConfig + + + +WorkbenchToolTeamsConfig defines a microsoft teams / graph connection. + + + +_Appears in:_ +- [WorkbenchToolConfiguration](#workbenchtoolconfiguration) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `clientId` _string_ | Microsoft Entra application (client) id. | | Required: \{\}
| +| `clientSecretSecretRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#secretkeyselector-v1-core)_ | Reference to a secret key containing the microsoft entra client secret. | | Required: \{\}
| +| `tenantId` _string_ | Microsoft Entra tenant (directory) id. | | Required: \{\}
| + + #### WorkbenchToolTempoConfig diff --git a/go/controller/internal/controller/workbench_tool_controller.go b/go/controller/internal/controller/workbench_tool_controller.go index fd99c905b9..b17fefdd92 100644 --- a/go/controller/internal/controller/workbench_tool_controller.go +++ b/go/controller/internal/controller/workbench_tool_controller.go @@ -10,6 +10,7 @@ import ( "github.com/pluralsh/console/go/controller/internal/credentials" "github.com/samber/lo" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -101,14 +102,6 @@ func (in *WorkbenchToolReconciler) Reconcile(ctx context.Context, req reconcile. // Mark resource as managed by this operator. utils.MarkCondition(workbenchTool.SetCondition, v1alpha1.ReadonlyConditionType, v1.ConditionFalse, v1alpha1.ReadonlyConditionReason, "") - // Get WorkbenchTool SHA that can be saved back in the status to check for changes - changed, sha, err := workbenchTool.Diff(utils.HashObject) - if err != nil { - logger.Error(err, "unable to calculate workbench tool SHA") - utils.MarkCondition(workbenchTool.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, err.Error()) - return ctrl.Result{}, err - } - // Get the project for the workbench tool. project, res, err := common.Project(ctx, in.Client, in.Scheme, workbenchTool) if res != nil || err != nil { @@ -127,8 +120,39 @@ func (in *WorkbenchToolReconciler) Reconcile(ctx context.Context, req reconcile. return common.HandleRequeue(res, err, workbenchTool.SetCondition) } + // Resolve ScmConnectionRef to get the SCM connection ID. + scmConnectionID, res, err := in.resolveScmConnectionRef(ctx, workbenchTool) + if res != nil || err != nil { + return common.HandleRequeue(res, err, workbenchTool.SetCondition) + } + + // Resolve policy bindings. + readBindings, writeBindings, err := in.resolveBindings(workbenchTool) + if err != nil { + return common.HandleRequeue(nil, err, workbenchTool.SetCondition) + } + + // Add controller refs to Docker auth secrets so credential rotation triggers reconcile. + if err := in.tryAddDockerOwnerRef(ctx, workbenchTool); err != nil { + utils.MarkCondition(workbenchTool.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, err.Error()) + return ctrl.Result{}, err + } + + attrs, err := in.attributes(ctx, workbenchTool, project.Status.ID, mcpServerID, cloudConnectionID, scmConnectionID, readBindings, writeBindings) + if err != nil { + return common.HandleRequeue(nil, err, workbenchTool.SetCondition) + } + + // Hash resolved attributes (includes secret values) so credential rotation is detected. + changed, sha, err := workbenchTool.Diff(attrs, utils.HashObject) + if err != nil { + logger.Error(err, "unable to calculate workbench tool SHA") + utils.MarkCondition(workbenchTool.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, err.Error()) + return ctrl.Result{}, err + } + // Sync WorkbenchTool CRD with the Console API - apiWorkbenchTool, err := in.sync(ctx, workbenchTool, project, mcpServerID, cloudConnectionID, changed) + apiWorkbenchTool, err := in.sync(ctx, workbenchTool, attrs, changed) if err != nil { return common.HandleRequeue(nil, err, workbenchTool.SetCondition) } @@ -270,7 +294,46 @@ func (in *WorkbenchToolReconciler) resolveCloudConnectionRef(ctx context.Context return cloudConnection.Status.ID, nil, nil } -func (in *WorkbenchToolReconciler) sync(ctx context.Context, workbenchTool *v1alpha1.WorkbenchTool, project *v1alpha1.Project, mcpServerID, cloudConnectionID *string, changed bool) (*console.WorkbenchToolFragment, error) { +func (in *WorkbenchToolReconciler) resolveScmConnectionRef(ctx context.Context, workbenchTool *v1alpha1.WorkbenchTool) (*string, *ctrl.Result, error) { + ref := workbenchTool.Spec.ScmConnectionRef + if ref == nil { + return nil, nil, nil + } + + scmConnection := &v1alpha1.ScmConnection{} + if err := in.Get(ctx, client.ObjectKey{Name: ref.Name, Namespace: ref.Namespace}, scmConnection); err != nil { + if errors.IsNotFound(err) { + return nil, new(common.Wait()), fmt.Errorf("ScmConnection not found: %s", err.Error()) + } + return nil, nil, fmt.Errorf("failed to get ScmConnection: %s", err.Error()) + } + + if !scmConnection.Status.HasID() { + return nil, new(common.Wait()), fmt.Errorf("ScmConnection is not ready") + } + + return scmConnection.Status.ID, nil, nil +} + +func (in *WorkbenchToolReconciler) resolveBindings(workbenchTool *v1alpha1.WorkbenchTool) (readBindings, writeBindings []*console.PolicyBindingAttributes, err error) { + if workbenchTool.Spec.Bindings == nil { + return nil, nil, nil + } + + readBindings, err = common.BindingsAttributes(workbenchTool.Spec.Bindings.Read) + if err != nil { + return nil, nil, err + } + + writeBindings, err = common.BindingsAttributes(workbenchTool.Spec.Bindings.Write) + if err != nil { + return nil, nil, err + } + + return readBindings, writeBindings, nil +} + +func (in *WorkbenchToolReconciler) sync(ctx context.Context, workbenchTool *v1alpha1.WorkbenchTool, attrs console.WorkbenchToolAttributes, changed bool) (*console.WorkbenchToolFragment, error) { logger := log.FromContext(ctx) existingWorkbenchTool, err := in.ConsoleClient.GetWorkbenchTool(ctx, nil, new(workbenchTool.ConsoleName())) @@ -279,19 +342,11 @@ func (in *WorkbenchToolReconciler) sync(ctx context.Context, workbenchTool *v1al return nil, err } - attrs, err := workbenchTool.Attributes(ctx, in.Client, project.Status.ID, mcpServerID, cloudConnectionID) - if err != nil { - return nil, err - } logger.Info(fmt.Sprintf("%s workbench tool does not exist, creating it", workbenchTool.ConsoleName())) return in.ConsoleClient.CreateWorkbenchTool(ctx, attrs) } - if changed || in.cloudConnectionChanged(existingWorkbenchTool, cloudConnectionID) { - attrs, err := workbenchTool.Attributes(ctx, in.Client, project.Status.ID, mcpServerID, cloudConnectionID) - if err != nil { - return nil, err - } + if changed { logger.Info(fmt.Sprintf("updating workbench tool %s", workbenchTool.ConsoleName())) return in.ConsoleClient.UpdateWorkbenchTool(ctx, existingWorkbenchTool.ID, attrs) } @@ -299,13 +354,70 @@ func (in *WorkbenchToolReconciler) sync(ctx context.Context, workbenchTool *v1al return existingWorkbenchTool, nil } -func (in *WorkbenchToolReconciler) cloudConnectionChanged(workbenchTool *console.WorkbenchToolFragment, cloudConnectionID *string) bool { - existingCloudConnectionID := "" - if workbenchTool.CloudConnection != nil { - existingCloudConnectionID = workbenchTool.CloudConnection.ID +func (in *WorkbenchToolReconciler) attributes(ctx context.Context, workbenchTool *v1alpha1.WorkbenchTool, projectID, mcpServerID, cloudConnectionID, scmConnectionID *string, readBindings, writeBindings []*console.PolicyBindingAttributes) (console.WorkbenchToolAttributes, error) { + attrs, err := workbenchTool.Attributes(ctx, in.Client, projectID, mcpServerID, cloudConnectionID, scmConnectionID, readBindings, writeBindings) + if err != nil { + return console.WorkbenchToolAttributes{}, err + } + + if err := in.attachDockerAuth(ctx, workbenchTool, &attrs); err != nil { + return console.WorkbenchToolAttributes{}, err + } + + return attrs, nil +} + +func (in *WorkbenchToolReconciler) attachDockerAuth(ctx context.Context, workbenchTool *v1alpha1.WorkbenchTool, attrs *console.WorkbenchToolAttributes) error { + if workbenchTool.Spec.Configuration == nil || workbenchTool.Spec.Configuration.Docker == nil { + return nil + } + + docker := workbenchTool.Spec.Configuration.Docker + authHelper := &HelmRepositoryAuth{Client: in.Client, Scheme: in.Scheme} + authAttrs, err := authHelper.HelmAuthAttributes(ctx, workbenchTool.Namespace, docker.Provider, docker.Auth) + if err != nil { + return err + } + if authAttrs == nil { + return nil + } + + if attrs.Configuration == nil { + attrs.Configuration = &console.WorkbenchToolConfigurationAttributes{} + } + if attrs.Configuration.Docker == nil { + attrs.Configuration.Docker = &console.WorkbenchToolDockerConnectionAttributes{ + URL: docker.URL, + Provider: docker.Provider, + } + } + attrs.Configuration.Docker.Auth = authAttrs + return nil +} + +func (in *WorkbenchToolReconciler) tryAddDockerOwnerRef(ctx context.Context, workbenchTool *v1alpha1.WorkbenchTool) error { + if workbenchTool.Spec.Configuration == nil || workbenchTool.Spec.Configuration.Docker == nil { + return nil + } + + authHelper := &HelmRepositoryAuth{Client: in.Client, Scheme: in.Scheme} + secretRef := authHelper.getAuthSecretRef(&v1alpha1.HelmRepository{ + ObjectMeta: v1.ObjectMeta{Namespace: workbenchTool.Namespace}, + Spec: v1alpha1.HelmRepositorySpec{ + Provider: workbenchTool.Spec.Configuration.Docker.Provider, + Auth: workbenchTool.Spec.Configuration.Docker.Auth, + }, + }) + if secretRef == nil { + return nil + } + + secret, err := utils.GetSecret(ctx, in.Client, secretRef) + if err != nil { + return err } - return existingCloudConnectionID != lo.FromPtr(cloudConnectionID) + return utils.TryAddControllerRef(ctx, in.Client, workbenchTool, secret, in.Scheme) } // SetupWithManager is responsible for initializing a new reconciler within the provided ctrl.Manager. @@ -315,5 +427,6 @@ func (in *WorkbenchToolReconciler) SetupWithManager(mgr ctrl.Manager) error { WithOptions(controller.Options{MaxConcurrentReconciles: 1}). Watches(&v1alpha1.NamespaceCredentials{}, credentials.OnCredentialsChange(in.Client, new(v1alpha1.WorkbenchToolList))). For(&v1alpha1.WorkbenchTool{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Owns(&corev1.Secret{}, builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})). Complete(in) } diff --git a/go/controller/internal/controller/workbench_tool_controller_test.go b/go/controller/internal/controller/workbench_tool_controller_test.go index 5ac0319fe6..0c7649df7c 100644 --- a/go/controller/internal/controller/workbench_tool_controller_test.go +++ b/go/controller/internal/controller/workbench_tool_controller_test.go @@ -486,4 +486,437 @@ var _ = Describe("Workbench Tool Controller", Ordered, func() { Expect(err).NotTo(HaveOccurred()) }) }) + + Context("When reconciling a Docker tool with bearer auth", func() { + const ( + workbenchToolName = "wt-docker" + workbenchToolSpecName = "wt_docker" + namespace = "default" + id = "docker-tool-123" + secretName = "docker-creds" + initialToken = "initial-registry-token" + rotatedToken = "rotated-registry-token" + ) + + ctx := context.Background() + typeNamespacedName := types.NamespacedName{Name: workbenchToolName, Namespace: namespace} + secretNamespacedName := types.NamespacedName{Name: secretName, Namespace: namespace} + + BeforeAll(func() { + By("creating the Docker credentials secret") + Expect(common.MaybeCreate(k8sClient, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: namespace, + }, + Data: map[string][]byte{ + "token": []byte(initialToken), + }, + }, nil)).To(Succeed()) + + By("creating the Docker WorkbenchTool resource") + err := k8sClient.Get(ctx, typeNamespacedName, &v1alpha1.WorkbenchTool{}) + if err != nil && errors.IsNotFound(err) { + Expect(common.MaybeCreate(k8sClient, &v1alpha1.WorkbenchTool{ + ObjectMeta: metav1.ObjectMeta{ + Name: workbenchToolName, + Namespace: namespace, + }, + Spec: v1alpha1.WorkbenchToolSpec{ + Name: lo.ToPtr(workbenchToolSpecName), + Tool: gqlclient.WorkbenchToolTypeDocker, + Categories: []gqlclient.WorkbenchToolCategory{gqlclient.WorkbenchToolCategoryIntegration}, + Approval: lo.ToPtr(true), + Configuration: &v1alpha1.WorkbenchToolConfiguration{ + Docker: &v1alpha1.WorkbenchToolDockerConfig{ + URL: lo.ToPtr("registry-1.docker.io"), + Provider: lo.ToPtr(gqlclient.HelmAuthProviderBearer), + Auth: &v1alpha1.HelmRepositoryAuth{ + Proxy: &v1alpha1.HttpProxyConfiguration{ + URL: "http://proxy.example.com:8080", + NoProxy: lo.ToPtr("localhost,127.0.0.1"), + }, + Bearer: &v1alpha1.HelmRepositoryAuthBearer{ + TokenSecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: secretName}, + Key: "token", + }, + }, + }, + }, + }, + }, + }, nil)).To(Succeed()) + } + }) + + AfterAll(func() { + workbenchTool := &v1alpha1.WorkbenchTool{} + if err := k8sClient.Get(ctx, typeNamespacedName, workbenchTool); err == nil { + By("cleaning up WorkbenchTool") + Expect(k8sClient.Delete(ctx, workbenchTool)).To(Succeed()) + } + secret := &corev1.Secret{} + if err := k8sClient.Get(ctx, secretNamespacedName, secret); err == nil { + Expect(k8sClient.Delete(ctx, secret)).To(Succeed()) + } + }) + + It("should resolve Docker auth, proxy, approval and own the secret", func() { + fakeConsoleClient := mocks.NewConsoleClientMock(mocks.TestingT) + fakeConsoleClient.On("UseCredentials", mock.Anything, mock.Anything).Return("", nil) + fakeConsoleClient.On("GetWorkbenchToolTiny", mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.NewNotFound(schema.GroupResource{}, id)) + fakeConsoleClient.On("GetWorkbenchTool", mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.NewNotFound(schema.GroupResource{}, id)) + fakeConsoleClient.On("CreateWorkbenchTool", mock.Anything, mock.MatchedBy(func(attrs gqlclient.WorkbenchToolAttributes) bool { + if attrs.Approval == nil || !*attrs.Approval { + return false + } + if attrs.Configuration == nil || attrs.Configuration.Docker == nil || attrs.Configuration.Docker.Auth == nil { + return false + } + auth := attrs.Configuration.Docker.Auth + if auth.Bearer == nil || auth.Bearer.Token != initialToken { + return false + } + if auth.Proxy == nil || auth.Proxy.URL != "http://proxy.example.com:8080" { + return false + } + return auth.Proxy.Noproxy != nil && *auth.Proxy.Noproxy == "localhost,127.0.0.1" + })).Return(&gqlclient.WorkbenchToolFragment{ID: id}, nil) + + reconciler := &controller.WorkbenchToolReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + ConsoleClient: fakeConsoleClient, + CredentialsCache: nil, + } + + _, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + + wt := &v1alpha1.WorkbenchTool{} + Expect(k8sClient.Get(ctx, typeNamespacedName, wt)).To(Succeed()) + Expect(wt.Status.ID).To(Equal(lo.ToPtr(id))) + Expect(wt.Status.SHA).NotTo(BeNil()) + + secret := &corev1.Secret{} + Expect(k8sClient.Get(ctx, secretNamespacedName, secret)).To(Succeed()) + Expect(secret.OwnerReferences).NotTo(BeEmpty()) + Expect(secret.OwnerReferences[0].Kind).To(Equal("WorkbenchTool")) + Expect(secret.OwnerReferences[0].Name).To(Equal(workbenchToolName)) + }) + + It("should update when Docker secret credentials are rotated without a spec change", func() { + wt := &v1alpha1.WorkbenchTool{} + Expect(k8sClient.Get(ctx, typeNamespacedName, wt)).To(Succeed()) + Expect(wt.Status.SHA).NotTo(BeNil()) + currentSHA := *wt.Status.SHA + + secret := &corev1.Secret{} + Expect(k8sClient.Get(ctx, secretNamespacedName, secret)).To(Succeed()) + secret.Data["token"] = []byte(rotatedToken) + Expect(k8sClient.Update(ctx, secret)).To(Succeed()) + + Expect(common.MaybePatch(k8sClient, &v1alpha1.WorkbenchTool{ + ObjectMeta: metav1.ObjectMeta{Name: workbenchToolName, Namespace: namespace}, + }, func(p *v1alpha1.WorkbenchTool) { + p.Status.ID = lo.ToPtr(id) + p.Status.SHA = lo.ToPtr(currentSHA) + })).To(Succeed()) + + fakeConsoleClient := mocks.NewConsoleClientMock(mocks.TestingT) + fakeConsoleClient.On("UseCredentials", mock.Anything, mock.Anything).Return("", nil) + fakeConsoleClient.On("GetWorkbenchTool", mock.Anything, mock.Anything, mock.Anything).Return(&gqlclient.WorkbenchToolFragment{ID: id}, nil) + fakeConsoleClient.On("UpdateWorkbenchTool", mock.Anything, id, mock.MatchedBy(func(attrs gqlclient.WorkbenchToolAttributes) bool { + return attrs.Configuration != nil && + attrs.Configuration.Docker != nil && + attrs.Configuration.Docker.Auth != nil && + attrs.Configuration.Docker.Auth.Bearer != nil && + attrs.Configuration.Docker.Auth.Bearer.Token == rotatedToken + })).Return(&gqlclient.WorkbenchToolFragment{ID: id}, nil) + + reconciler := &controller.WorkbenchToolReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + ConsoleClient: fakeConsoleClient, + CredentialsCache: nil, + } + + _, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + + updated := &v1alpha1.WorkbenchTool{} + Expect(k8sClient.Get(ctx, typeNamespacedName, updated)).To(Succeed()) + Expect(updated.Status.SHA).NotTo(BeNil()) + Expect(*updated.Status.SHA).NotTo(Equal(currentSHA)) + }) + }) + + Context("When reconciling a GitHub tool with ScmConnectionRef", func() { + const ( + workbenchToolName = "wt-github" + workbenchToolSpecName = "wt_github" + namespace = "default" + id = "github-tool-123" + scmConnectionName = "wt-github-scm" + scmConnectionID = "scm-conn-id-123" + secretName = "github-tool-creds" + ) + + ctx := context.Background() + typeNamespacedName := types.NamespacedName{Name: workbenchToolName, Namespace: namespace} + + BeforeAll(func() { + By("creating the GitHub credentials secret") + Expect(common.MaybeCreate(k8sClient, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: namespace, + }, + Data: map[string][]byte{ + "accessToken": []byte("ghp_test_token"), + }, + }, nil)).To(Succeed()) + + By("creating the ScmConnection resource with a status ID") + Expect(common.MaybeCreate(k8sClient, &v1alpha1.ScmConnection{ + ObjectMeta: metav1.ObjectMeta{ + Name: scmConnectionName, + }, + Spec: v1alpha1.ScmConnectionSpec{ + Name: scmConnectionName, + Type: gqlclient.ScmTypeGithub, + }, + }, func(p *v1alpha1.ScmConnection) { + p.Status.ID = lo.ToPtr(scmConnectionID) + })).To(Succeed()) + + By("creating the WorkbenchTool resource") + err := k8sClient.Get(ctx, typeNamespacedName, &v1alpha1.WorkbenchTool{}) + if err != nil && errors.IsNotFound(err) { + Expect(common.MaybeCreate(k8sClient, &v1alpha1.WorkbenchTool{ + ObjectMeta: metav1.ObjectMeta{ + Name: workbenchToolName, + Namespace: namespace, + }, + Spec: v1alpha1.WorkbenchToolSpec{ + Name: lo.ToPtr(workbenchToolSpecName), + Tool: gqlclient.WorkbenchToolTypeGithub, + Categories: []gqlclient.WorkbenchToolCategory{ + gqlclient.WorkbenchToolCategoryScm, + }, + ScmConnectionRef: &corev1.ObjectReference{ + Name: scmConnectionName, + }, + Bindings: &v1alpha1.Bindings{ + Read: []v1alpha1.Binding{ + {UserID: lo.ToPtr("user-read-id")}, + }, + Write: []v1alpha1.Binding{ + {UserID: lo.ToPtr("user-write-id")}, + }, + }, + Configuration: &v1alpha1.WorkbenchToolConfiguration{ + Github: &v1alpha1.WorkbenchToolGithubConfig{ + URL: lo.ToPtr("https://api.github.com/"), + Toolset: lo.ToPtr("default"), + AccessTokenSecretRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: secretName}, + Key: "accessToken", + }, + }, + }, + }, + }, nil)).To(Succeed()) + } + }) + + AfterAll(func() { + workbenchTool := &v1alpha1.WorkbenchTool{} + if err := k8sClient.Get(ctx, typeNamespacedName, workbenchTool); err == nil { + By("cleaning up WorkbenchTool") + Expect(k8sClient.Delete(ctx, workbenchTool)).To(Succeed()) + } + scm := &v1alpha1.ScmConnection{} + if err := k8sClient.Get(ctx, types.NamespacedName{Name: scmConnectionName}, scm); err == nil { + Expect(k8sClient.Delete(ctx, scm)).To(Succeed()) + } + secret := &corev1.Secret{} + if err := k8sClient.Get(ctx, types.NamespacedName{Name: secretName, Namespace: namespace}, secret); err == nil { + Expect(k8sClient.Delete(ctx, secret)).To(Succeed()) + } + }) + + It("should resolve ScmConnectionRef, bindings and GitHub configuration", func() { + fakeConsoleClient := mocks.NewConsoleClientMock(mocks.TestingT) + fakeConsoleClient.On("UseCredentials", mock.Anything, mock.Anything).Return("", nil) + fakeConsoleClient.On("GetWorkbenchToolTiny", mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.NewNotFound(schema.GroupResource{}, id)) + fakeConsoleClient.On("GetWorkbenchTool", mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.NewNotFound(schema.GroupResource{}, id)) + fakeConsoleClient.On("CreateWorkbenchTool", mock.Anything, mock.MatchedBy(func(attrs gqlclient.WorkbenchToolAttributes) bool { + if attrs.ScmConnectionID == nil || *attrs.ScmConnectionID != scmConnectionID { + return false + } + if len(attrs.ReadBindings) != 1 || attrs.ReadBindings[0].UserID == nil || *attrs.ReadBindings[0].UserID != "user-read-id" { + return false + } + if len(attrs.WriteBindings) != 1 || attrs.WriteBindings[0].UserID == nil || *attrs.WriteBindings[0].UserID != "user-write-id" { + return false + } + if attrs.Configuration == nil || attrs.Configuration.Github == nil { + return false + } + return attrs.Configuration.Github.AccessToken != nil && *attrs.Configuration.Github.AccessToken == "ghp_test_token" + })).Return(&gqlclient.WorkbenchToolFragment{ID: id}, nil) + + reconciler := &controller.WorkbenchToolReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + ConsoleClient: fakeConsoleClient, + CredentialsCache: nil, + } + + _, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + + wt := &v1alpha1.WorkbenchTool{} + Expect(k8sClient.Get(ctx, typeNamespacedName, wt)).To(Succeed()) + Expect(wt.Status.ID).To(Equal(lo.ToPtr(id))) + }) + }) + + Context("When reconciling Prometheus secret rotation", func() { + const ( + workbenchToolName = "wt-prometheus-rotate" + workbenchToolSpecName = "wt_prometheus_rotate" + namespace = "default" + id = "prometheus-rotate-123" + secretName = "prometheus-rotate-creds" + initialToken = "prom-token-v1" + rotatedToken = "prom-token-v2" + ) + + ctx := context.Background() + typeNamespacedName := types.NamespacedName{Name: workbenchToolName, Namespace: namespace} + secretNamespacedName := types.NamespacedName{Name: secretName, Namespace: namespace} + + BeforeAll(func() { + By("creating the Prometheus credentials secret") + Expect(common.MaybeCreate(k8sClient, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: namespace, + }, + Data: map[string][]byte{ + "token": []byte(initialToken), + }, + }, nil)).To(Succeed()) + + By("creating the Prometheus WorkbenchTool resource") + err := k8sClient.Get(ctx, typeNamespacedName, &v1alpha1.WorkbenchTool{}) + if err != nil && errors.IsNotFound(err) { + Expect(common.MaybeCreate(k8sClient, &v1alpha1.WorkbenchTool{ + ObjectMeta: metav1.ObjectMeta{ + Name: workbenchToolName, + Namespace: namespace, + }, + Spec: v1alpha1.WorkbenchToolSpec{ + Name: lo.ToPtr(workbenchToolSpecName), + Tool: gqlclient.WorkbenchToolTypePrometheus, + Configuration: &v1alpha1.WorkbenchToolConfiguration{ + Prometheus: &v1alpha1.WorkbenchToolPrometheusConfig{ + URL: "https://prometheus.example.com", + TokenSecretRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: secretName}, + Key: "token", + }, + AWSSigv4: lo.ToPtr(true), + AWSRegion: lo.ToPtr("us-east-1"), + }, + }, + }, + }, nil)).To(Succeed()) + } + }) + + AfterAll(func() { + workbenchTool := &v1alpha1.WorkbenchTool{} + if err := k8sClient.Get(ctx, typeNamespacedName, workbenchTool); err == nil { + Expect(k8sClient.Delete(ctx, workbenchTool)).To(Succeed()) + } + secret := &corev1.Secret{} + if err := k8sClient.Get(ctx, secretNamespacedName, secret); err == nil { + Expect(k8sClient.Delete(ctx, secret)).To(Succeed()) + } + }) + + It("should create with resolved Prometheus secrets and AWS SigV4 fields", func() { + fakeConsoleClient := mocks.NewConsoleClientMock(mocks.TestingT) + fakeConsoleClient.On("UseCredentials", mock.Anything, mock.Anything).Return("", nil) + fakeConsoleClient.On("GetWorkbenchToolTiny", mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.NewNotFound(schema.GroupResource{}, id)) + fakeConsoleClient.On("GetWorkbenchTool", mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.NewNotFound(schema.GroupResource{}, id)) + fakeConsoleClient.On("CreateWorkbenchTool", mock.Anything, mock.MatchedBy(func(attrs gqlclient.WorkbenchToolAttributes) bool { + if attrs.Configuration == nil || attrs.Configuration.Prometheus == nil { + return false + } + prom := attrs.Configuration.Prometheus + return prom.Token != nil && *prom.Token == initialToken && + prom.AWSSigv4 != nil && *prom.AWSSigv4 && + prom.AWSRegion != nil && *prom.AWSRegion == "us-east-1" + })).Return(&gqlclient.WorkbenchToolFragment{ID: id}, nil) + + reconciler := &controller.WorkbenchToolReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + ConsoleClient: fakeConsoleClient, + CredentialsCache: nil, + } + + _, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should update when Prometheus secret credentials are rotated without a spec change", func() { + wt := &v1alpha1.WorkbenchTool{} + Expect(k8sClient.Get(ctx, typeNamespacedName, wt)).To(Succeed()) + Expect(wt.Status.SHA).NotTo(BeNil()) + currentSHA := *wt.Status.SHA + + secret := &corev1.Secret{} + Expect(k8sClient.Get(ctx, secretNamespacedName, secret)).To(Succeed()) + secret.Data["token"] = []byte(rotatedToken) + Expect(k8sClient.Update(ctx, secret)).To(Succeed()) + + Expect(common.MaybePatch(k8sClient, &v1alpha1.WorkbenchTool{ + ObjectMeta: metav1.ObjectMeta{Name: workbenchToolName, Namespace: namespace}, + }, func(p *v1alpha1.WorkbenchTool) { + p.Status.ID = lo.ToPtr(id) + p.Status.SHA = lo.ToPtr(currentSHA) + })).To(Succeed()) + + fakeConsoleClient := mocks.NewConsoleClientMock(mocks.TestingT) + fakeConsoleClient.On("UseCredentials", mock.Anything, mock.Anything).Return("", nil) + fakeConsoleClient.On("GetWorkbenchTool", mock.Anything, mock.Anything, mock.Anything).Return(&gqlclient.WorkbenchToolFragment{ID: id}, nil) + fakeConsoleClient.On("UpdateWorkbenchTool", mock.Anything, id, mock.MatchedBy(func(attrs gqlclient.WorkbenchToolAttributes) bool { + return attrs.Configuration != nil && + attrs.Configuration.Prometheus != nil && + attrs.Configuration.Prometheus.Token != nil && + *attrs.Configuration.Prometheus.Token == rotatedToken + })).Return(&gqlclient.WorkbenchToolFragment{ID: id}, nil) + + reconciler := &controller.WorkbenchToolReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + ConsoleClient: fakeConsoleClient, + CredentialsCache: nil, + } + + _, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + + updated := &v1alpha1.WorkbenchTool{} + Expect(k8sClient.Get(ctx, typeNamespacedName, updated)).To(Succeed()) + Expect(updated.Status.SHA).NotTo(BeNil()) + Expect(*updated.Status.SHA).NotTo(Equal(currentSHA)) + }) + }) })