diff --git a/api/cluster/EnvironmentRestHandler.go b/api/cluster/EnvironmentRestHandler.go index 47d246a445..081dab38f7 100644 --- a/api/cluster/EnvironmentRestHandler.go +++ b/api/cluster/EnvironmentRestHandler.go @@ -23,6 +23,7 @@ import ( "net/http" "strconv" "strings" + "sync" "time" k8s2 "github.com/devtron-labs/common-lib/utils/k8s" @@ -513,9 +514,8 @@ func (impl EnvironmentRestHandlerImpl) GetEnvironmentConnection(w http.ResponseW responseObj.ClusterReachable = false } //updating the cluster connection error to db - mapObj := map[int]error{ - clusterBean.Id: err, - } + mapObj := &sync.Map{} + mapObj.Store(clusterBean.Id, err) impl.environmentClusterMappingsService.HandleErrorInClusterConnections([]*request.ClusterBean{clusterBean}, mapObj, true) common.WriteJsonResp(w, nil, responseObj, http.StatusOK) } diff --git a/api/cluster/wire_cluster.go b/api/cluster/wire_cluster.go index 2a38444542..bbfa050f87 100644 --- a/api/cluster/wire_cluster.go +++ b/api/cluster/wire_cluster.go @@ -29,6 +29,7 @@ import ( var ClusterWireSet = wire.NewSet( repository.NewClusterRepositoryImpl, wire.Bind(new(repository.ClusterRepository), new(*repository.ClusterRepositoryImpl)), + cluster.NewClusterServiceImpl, cluster.NewClusterServiceImplExtended, wire.Bind(new(cluster.ClusterService), new(*cluster.ClusterServiceImplExtended)), diff --git a/api/k8s/application/k8sApplicationRestHandler.go b/api/k8s/application/k8sApplicationRestHandler.go index b6830b7d0d..d153d84c98 100644 --- a/api/k8s/application/k8sApplicationRestHandler.go +++ b/api/k8s/application/k8sApplicationRestHandler.go @@ -559,6 +559,7 @@ func (handler *K8sApplicationRestHandlerImpl) GetPodLogs(w http.ResponseWriter, common.WriteJsonResp(w, err, nil, http.StatusBadRequest) return } + handler.logger.Infow("get pod logs request", "request", request) handler.requestValidationAndRBAC(w, r, token, request) lastEventId := r.Header.Get(bean2.LastEventID) isReconnect := false diff --git a/api/k8s/wire_k8sApp.go b/api/k8s/wire_k8sApp.go index 0e940ca648..c728d597ef 100644 --- a/api/k8s/wire_k8sApp.go +++ b/api/k8s/wire_k8sApp.go @@ -53,7 +53,4 @@ var K8sApplicationWireSet = wire.NewSet( informer.NewGlobalMapClusterNamespace, informer.NewK8sInformerFactoryImpl, wire.Bind(new(informer.K8sInformerFactory), new(*informer.K8sInformerFactoryImpl)), - - cluster.NewClusterCronServiceImpl, - wire.Bind(new(cluster.ClusterCronService), new(*cluster.ClusterCronServiceImpl)), ) diff --git a/cmd/external-app/wire_gen.go b/cmd/external-app/wire_gen.go index 72533cab86..5271cca4cd 100644 --- a/cmd/external-app/wire_gen.go +++ b/cmd/external-app/wire_gen.go @@ -1,6 +1,6 @@ // Code generated by Wire. DO NOT EDIT. -//go:generate go run -mod=mod github.com/google/wire/cmd/wire +//go:generate go run github.com/google/wire/cmd/wire //go:build !wireinject // +build !wireinject @@ -201,7 +201,11 @@ func InitializeApp() (*App, error) { clusterRepositoryImpl := repository2.NewClusterRepositoryImpl(db, sugaredLogger) syncMap := informer.NewGlobalMapClusterNamespace() k8sInformerFactoryImpl := informer.NewK8sInformerFactoryImpl(sugaredLogger, syncMap, k8sServiceImpl) - clusterServiceImpl := cluster.NewClusterServiceImpl(clusterRepositoryImpl, sugaredLogger, k8sServiceImpl, k8sInformerFactoryImpl, userAuthRepositoryImpl, userRepositoryImpl, roleGroupRepositoryImpl) + cronLoggerImpl := cron.NewCronLoggerImpl(sugaredLogger) + clusterServiceImpl, err := cluster.NewClusterServiceImpl(clusterRepositoryImpl, sugaredLogger, k8sServiceImpl, k8sInformerFactoryImpl, userAuthRepositoryImpl, userRepositoryImpl, roleGroupRepositoryImpl, environmentVariables, cronLoggerImpl) + if err != nil { + return nil, err + } appStatusRepositoryImpl := appStatus.NewAppStatusRepositoryImpl(db, sugaredLogger) environmentRepositoryImpl := repository2.NewEnvironmentRepositoryImpl(db, sugaredLogger, appStatusRepositoryImpl) attributesRepositoryImpl := repository3.NewAttributesRepositoryImpl(db) @@ -334,7 +338,6 @@ func InitializeApp() (*App, error) { } moduleRepositoryImpl := moduleRepo.NewModuleRepositoryImpl(db) providerIdentifierServiceImpl := providerIdentifier.NewProviderIdentifierServiceImpl(sugaredLogger) - cronLoggerImpl := cron.NewCronLoggerImpl(sugaredLogger) telemetryEventClientImpl, err := telemetry.NewTelemetryEventClientImpl(sugaredLogger, httpClient, clusterServiceImpl, k8sServiceImpl, acdAuthConfig, userServiceImpl, attributesRepositoryImpl, ssoLoginServiceImpl, posthogClient, moduleRepositoryImpl, serverDataStoreServerDataStore, userAuditServiceImpl, helmAppClientImpl, installedAppRepositoryImpl, providerIdentifierServiceImpl, cronLoggerImpl) if err != nil { return nil, err diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index e4ad4067a0..136be17a3c 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -146,11 +146,14 @@ * [DockerSlim](user-guide/plugins/docker-slim.md) * [GoLang-migrate](user-guide/plugins/golang-migrate.md) * [Jenkins](user-guide/plugins/jenkins.md) + * [Jira Issue Validator](user-guide/plugins/jira-validator.md) + * [Jira Issue Updater](user-guide/plugins/jira-updater.md) * [K6 Load Testing](user-guide/plugins/k6-load-testing.md) * [Pull images from container repository](user-guide/plugins/pull-images-from-container-repository.md) * [Semgrep](user-guide/plugins/semgrep.md) * [SonarQube](user-guide/plugins/sonarqube.md) * [SonarQube v1.1.0](user-guide/plugins/sonarqube-v1.1.0.md) + * [Vulnerability Scanning](user-guide/plugins/vulnerability-scanning.md) ## Resources diff --git a/docs/setup/install/ingress-setup.md b/docs/setup/install/ingress-setup.md index 8204bc4bef..e889af66eb 100644 --- a/docs/setup/install/ingress-setup.md +++ b/docs/setup/install/ingress-setup.md @@ -1,16 +1,144 @@ # Ingress Setup -After Devtron is installed, Devtron is accessible through service `devtron-service`. -If you want to access Devtron through ingress, edit `devtron-service` and change the loadbalancer to ClusterIP. You can do this using `kubectl patch` command: +## Introduction + +If you wish to use [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) as a means to access the Devtron services available in your cluster, you can configure it either during the installation or after the installation of Devtron. + +Refer the section relevant to you: +* [During Devtron Installation](#enabling-ingress-during-devtron-installation) +* [After Devtron Installation](#configuring-ingress-after-devtron-installation) + +If you have successfully configured Ingress, refer [Post Ingress Setup](#enable-https-for-devtron). + +--- + +## Enabling Ingress during Devtron Installation + +If you are installing Devtron, you can enable Ingress either via [set flag](#using-set-flag) or by using [ingress-values.yaml](#using-ingress-values.yaml) to specify the desired Ingress settings. + +### Using set flag + +You can use the `--set` flag to specify the desired Ingress settings. + +Here, we have added 5 configurations you can perform depending on your requirements: +* [Only Basic Configuration](#only-basic-configuration) +* [Configuration Including Labels](#configuration-including-labels) +* [Configuration Including Annotations](#configuration-including-annotations) +* [Configuration Including TLS Settings](#configuration-including-tls-settings) +* [Comprehensive Configuration](#comprehensive-configuration) + +#### Only Basic Configuration + +To enable Ingress and set basic parameters, use the following command: + +```bash +helm install devtron devtron/devtron-operator -n devtroncd \ + --set components.devtron.ingress.enabled=true \ + --set components.devtron.ingress.className=nginx \ + --set components.devtron.ingress.host=devtron.example.com +``` + +#### Configuration Including Labels + +To add labels to the Ingress resource, use the following command: + +```bash +helm install devtron devtron/devtron-operator -n devtroncd \ + --set components.devtron.ingress.enabled=true \ + --set components.devtron.ingress.className=nginx \ + --set components.devtron.ingress.host=devtron.example.com \ + --set components.devtron.ingress.labels.env=production +``` + +#### Configuration Including Annotations + +To add annotations to the Ingress resource, use the following command: + +```bash +helm install devtron devtron/devtron-operator -n devtroncd \ + --set components.devtron.ingress.enabled=true \ + --set components.devtron.ingress.className=nginx \ + --set components.devtron.ingress.host=devtron.example.com \ + --set components.devtron.ingress.annotations."kubernetes\.io/ingress\.class"=nginx \ + --set components.devtron.ingress.annotations."nginx\.ingress\.kubernetes\.io\/app-root"="/dashboard" +``` + +#### Configuration Including TLS Settings + +To configure TLS settings, including `secretName` and `hosts`, use the following command: + +```bash +helm install devtron devtron/devtron-operator -n devtroncd \ + --set components.devtron.ingress.enabled=true \ + --set components.devtron.ingress.className=nginx \ + --set components.devtron.ingress.host=devtron.example.com \ + --set components.devtron.ingress.tls[0].secretName=devtron-tls \ + --set components.devtron.ingress.tls[0].hosts[0]=devtron.example.com +``` + +#### Comprehensive Configuration + +To include all the above settings in a single command, use: + +```bash +helm install devtron devtron/devtron-operator -n devtroncd \ + --set components.devtron.ingress.enabled=true \ + --set components.devtron.ingress.className=nginx \ + --set components.devtron.ingress.host=devtron.example.com \ + --set components.devtron.ingress.annotations."kubernetes\.io/ingress\.class"=nginx \ + --set components.devtron.ingress.annotations."nginx\.ingress\.kubernetes\.io\/app-root"="/dashboard" \ + --set components.devtron.ingress.labels.env=production \ + --set components.devtron.ingress.pathType=ImplementationSpecific \ + --set components.devtron.ingress.tls[0].secretName=devtron-tls \ + --set components.devtron.ingress.tls[0].hosts[0]=devtron.example.com +``` + + +### Using ingress-values.yaml + +As an alternative to the [set flag](#using-set-flag) method, you can enable Ingress using `ingress-values.yaml` instead. + +Create an `ingress-values.yaml` file. You may refer the below format for an advanced ingress configuration which includes labels, annotations, secrets, and many more. + +```yml +components: + devtron: + ingress: + enabled: true + className: nginx + labels: {} + # env: production + annotations: {} + # nginx.ingress.kubernetes.io/app-root: /dashboard + pathType: ImplementationSpecific + host: devtron.example.com + tls: [] + # - secretName: devtron-info-tls + # hosts: + # - devtron.example.com +``` + +Once you have the `ingress-values.yaml` file ready, run the following command: + +```bash +helm install devtron devtron/devtron-operator -n devtroncd --reuse-values -f ingress-values.yaml +``` + +--- + +## Configuring Ingress after Devtron Installation + +After Devtron is installed, Devtron is accessible through `devtron-service`. If you wish to access Devtron through ingress, you'll need to modify this service to use a ClusterIP instead of a LoadBalancer. + +You can do this using the `kubectl patch` command: ```bash kubectl patch -n devtroncd svc devtron-service -p '{"spec": {"ports": [{"port": 80,"targetPort": "devtron","protocol": "TCP","name": "devtron"}],"type": "ClusterIP","selector": {"app": "devtron"}}}' ``` -After this, create ingress by applying the ingress yaml file. -You can use [this yaml file](https://github.com/devtron-labs/devtron/blob/main/manifests/yamls/devtron-ingress.yaml) to create ingress to access Devtron: +Next, create ingress to access Devtron by applying the `devtron-ingress.yaml` file. The file is also available on this [link](https://github.com/devtron-labs/devtron/blob/main/manifests/yamls/devtron-ingress.yaml). You can access Devtron from any host after applying this yaml. -```yaml +```yml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: @@ -49,9 +177,9 @@ spec: pathType: ImplementationSpecific ``` -You can access Devtron from any host after applying this yaml. For k8s versions <1.19, [apply this yaml](https://github.com/devtron-labs/devtron/blob/main/manifests/yamls/devtron-ingress-legacy.yaml): +For k8s versions < 1.19, [apply this yaml](https://github.com/devtron-labs/devtron/blob/main/manifests/yamls/devtron-ingress-legacy.yaml): -```yaml +```yml apiVersion: extensions/v1beta1 kind: Ingress metadata: @@ -79,7 +207,7 @@ spec: Optionally, you also can access Devtron through a specific host by running the following YAML file: -```yaml +```yml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: @@ -119,9 +247,11 @@ spec: pathType: ImplementationSpecific ``` +--- + ## Enable HTTPS For Devtron -Once ingress setup for devtron is done and you want to run Devtron over `https`, you need to add different annotations for different ingress controllers and load balancers. +Once Ingress setup for Devtron is done and you want to run Devtron over `https`, you need to add different annotations for different ingress controllers and load balancers. ### 1. Nginx Ingress Controller @@ -175,7 +305,4 @@ In case of AWS application load balancer, the following annotations need to be a ``` For an Ingress resource to be observed by AGIC (Application Gateway Ingress Controller) must be annotated with kubernetes.io/ingress.class: azure/application-gateway. Only then AGIC will work with the Ingress resource in question. -> Note: Make sure NOT to use port 80 with HTTPS and port 443 with HTTP on the Pods. - - - +> Note: Make sure NOT to use port 80 with HTTPS and port 443 with HTTP on the Pods. \ No newline at end of file diff --git a/docs/user-guide/plugins/jira-updater.md b/docs/user-guide/plugins/jira-updater.md new file mode 100644 index 0000000000..c137b5e481 --- /dev/null +++ b/docs/user-guide/plugins/jira-updater.md @@ -0,0 +1,57 @@ +# Jira Issue Updater + +## Introduction +The Jira Issue Updater plugin extends the capabilities of Devtron CI by allowing updates to Jira issues directly from the pipeline. It can add build pipeline status and docker image ID as a comment on Jira tickets, keeping the issue tracking synchronized with your CI processes. + +### Prerequisites + +- A Jira account with the necessary [API access](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/#Create-an-API-token). +- The API credentials (username, password, and base URL) for your Jira instance. Obtain the API credentials from your Jira admin if required. +- A pull request raised with your Git provider. Title of pull request must contain the Jira ID. +- Jira Issue (e.g., REDOC-12) +- Webhook added to the git repository. [Click here](https://docs.devtron.ai/usage/applications/creating-application/workflow/ci-pipeline#configuring-webhook) to know more. + +--- + +## Steps + +1. On the **Edit build pipeline** page, go to the **Post-Build Stage**. +2. Click **+ Add task**. +3. Select **Jira Issue Updater** from the list of plugins. + * Enter a task name (mandatory). + * Optionally, enter a description. + * Provide values for the input variables. + + | Variable | Format | Description | + | ------------------------ | ------ | --------------------------------------------------------- | + | JiraUsername | String | Your Jira username (e.g., johndoe@devtron.ai) | + | JiraPassword | String | Your Jira API token provided by the Jira admin | + | JiraBaseUrl | String | The base URL of your Jira instance (e.g., https://yourdomain.atlassian.net/) | + | UpdateWithDockerImageId | Bool | Set to `true` to include the Docker Image ID in the update | + | UpdateWithBuildStatus | Bool | Set to `true` to include the build status in the update | + + * `Trigger/Skip Condition` allows you to set conditions under which this task will execute or be skipped. + * `Pass/Failure Condition` allows you define conditions to determine if the build passes or fails based on the Jira update. + +4. Go to the **Build Stage**. + +5. Select **Pull Request** in the **Source Type** dropdown. + +6. Use filters to fetch only the PRs matching your regex. Here are few examples: + * **Title** can be a regex pattern (e.g., `^(?P([a-zA-Z0-9-].*))`) to extract the Jira ID from the PR title. Only those PRs fulfilling the regex will be shown for image build process. + * **State** can be `^open$`, where only PRs in open state will be shown for image build process. + +7. Click **Update Pipeline**. + +--- + +## Results + +![Figure 1: Build Log](https://devtron-public-asset.s3.us-east-2.amazonaws.com/images/plugins/jira/jira-updater-log.jpg) + +![Figure 2: Comments added by the Plugin on the Jira Issue](https://devtron-public-asset.s3.us-east-2.amazonaws.com/images/plugins/jira/jira-updater.jpg) + + + + + diff --git a/docs/user-guide/plugins/jira-validator.md b/docs/user-guide/plugins/jira-validator.md new file mode 100644 index 0000000000..3122d13656 --- /dev/null +++ b/docs/user-guide/plugins/jira-validator.md @@ -0,0 +1,54 @@ +# Jira Issue Validator + +## Introduction +The Jira Issue Validator plugin extends the filtering capabilities of the Devtron CI and lets users perform validation based on Jira Ticket ID status. This plugin ensures that only builds associated with valid Jira tickets are executed, improving the accuracy of the CI process. + +### Prerequisites + +- A Jira account with the necessary [API access](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/#Create-an-API-token). +- The API credentials (username, password, and base URL) for your Jira instance. Obtain the API credentials from your Jira admin if required. +- A pull request raised with your Git provider. Title of pull request must contain the Jira ID. +- Jira Issue (e.g., REDOC-12) +- Webhook added to the git repository. [Click here](https://docs.devtron.ai/usage/applications/creating-application/workflow/ci-pipeline#configuring-webhook) to know more. + +--- + +## Steps + +1. On the **Edit build pipeline** page, go to the **Pre-Build Stage** (or Post-Build Stage). +2. Click **+ Add task**. +3. Select **Jira Issue Validator** from the list of plugins. + * Enter a task name (mandatory). + * Optionally, enter a description. + * Provide values for the input variables. + + | Variable | Format | Description | + | -------------- | ------ | --------------------------------------------------------- | + | JiraUsername | String | Your Jira username (e.g., johndoe@devtron.ai) | + | JiraPassword | String | Your Jira API token provided by the Jira admin | + | JiraBaseUrl | String | The base URL of your Jira instance (e.g., https://yourdomain.atlassian.net) | + + * `Trigger/Skip Condition` allows you to set conditions under which this task will execute or be skipped. + * `Pass/Failure Condition` allows you to define conditions that determine whether the build passes or fails based on Jira validation. + +4. Go to the **Build Stage**. + +5. Select **Pull Request** in the **Source Type** dropdown. + +6. Use filters to fetch only the PRs matching your regex. Here are few examples: + * **Title** can be a regex pattern (e.g., `^(?P([a-zA-Z0-9-].*))`) to extract the Jira ID from the PR title. Only those PRs fulfilling the regex will be shown for image build process. + * **State** can be `^open$`, where only PRs in open state will be shown for image build process. + +7. Click **Update Pipeline**. + +--- + +## Results + +**Case 1**: If Jira issue exists and the same is found in the PR title + +![Figure 1: Jira Issue Match](https://devtron-public-asset.s3.us-east-2.amazonaws.com/images/plugins/jira/jira-issue-validator.jpg) + +**Case 2**: If Jira issue is not found + +![Figure 2: Error in Finding Jira Issue](https://devtron-public-asset.s3.us-east-2.amazonaws.com/images/plugins/jira/issue-validation-failed.jpg) diff --git a/docs/user-guide/plugins/plugin-list.md b/docs/user-guide/plugins/plugin-list.md index 908aedee63..2a7a239775 100644 --- a/docs/user-guide/plugins/plugin-list.md +++ b/docs/user-guide/plugins/plugin-list.md @@ -11,6 +11,8 @@ We have multiple plugins available in Devtron. At the moment, here are the plugi * [Dependency track - Python](./dependency-track-python.md) * [GoLang-migrate](./golang-migrate.md) * [Jenkins](./jenkins.md) +* [Jira Issue Validator](./jira-validator.md) +* [Jira Issue Updater](./jira-updater.md) * [K6 Load Testing](./k6-load-testing.md) * [Pull images from container repository](./pull-images-from-container-repository.md) * [Semgrep](./semgrep.md) diff --git a/docs/user-guide/plugins/vulnerability-scanning.md b/docs/user-guide/plugins/vulnerability-scanning.md new file mode 100644 index 0000000000..178c2025bf --- /dev/null +++ b/docs/user-guide/plugins/vulnerability-scanning.md @@ -0,0 +1,51 @@ +# Vulnerability-Scanning + +## Introduction +The **Vulnerability Scanning** plugin by Devtron enables you to scan and detect vulnerabilities of your applications using Trivy/Clair. The Vulnerability Scanning plugin is recommended to be integrated into the Job Pipeline, especially when you are using external CI pipelines like Jenkins, GitLab, or GitHub Actions. Based on Vulnerability Scanning results, you can enforce security policies to either proceed with or abort the deployment process, giving you more control over your deployment process. + +### Prerequisites +Before integrating the Vulnerability Scanning plugin, ensure that you have installed the `Vulnerability Scanning (Trivy/Clair)` integration from Devtron Stack Manager. + +--- + +## Steps +1. Go to **Applications** → **Devtron Apps**. +2. Click your application. +3. Go to **App Configuration** → **Workflow Editor**. +4. Click **New Workflow** and navigate to the **CREATE JOB PIPELINE**. +5. Enter the required fields in the **Basic configuration** window. +6. Click **Task to be executed**. +7. Under 'TASKS', click the **+ Add task** button. +8. Click the **Vulnerability Scanning** plugin. +9. Enter the following [user inputs](#user-inputs) with appropriate values. +--- + +## User Inputs + +### Task Name +Enter the name of your task + +e.g., `Vulnerability Scanning for External CI ` + +### Description +Add a brief explanation of the task and the reason for choosing the plugin. Include information for someone else to understand the purpose of the task. + +e.g., `The Vulnerability Scanning plugin is integrated for detecting vulnerabilities in applications.` + +### Input Variables + +| Variable | Format | Description | Sample Value | +| ------------------------ | ------------ | ----------- | ------------ | +| IMAGE_SCAN_MAX_RETRIES | STRING | Maximum retries for image scanning. | 2 | +| IMAGE_SCAN_RETRY_DELAY | STRING | Delay between image scanning retries (seconds). | 120 | + +### Trigger/Skip Condition +Here you can set conditions to execute or skip the task. You can select `Set trigger conditions` for the execution of a task or `Set skip conditions` to skip the task. + +### Output Variables +Vulnerability Scanning will not be generating an output variable. + +Click **Update Pipeline**. + + + diff --git a/go.mod b/go.mod index 312eb86aac..d74311c5b1 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/satori/go.uuid v1.2.0 github.com/stretchr/testify v1.8.4 - github.com/tidwall/gjson v1.14.3 + github.com/tidwall/gjson v1.14.4 github.com/tidwall/sjson v1.2.4 github.com/xanzy/go-gitlab v0.107.0 github.com/xeipuuv/gojsonschema v1.2.0 @@ -125,7 +125,7 @@ require ( github.com/blang/semver/v4 v4.0.0 // indirect github.com/bmatcuk/doublestar/v4 v4.6.0 // indirect github.com/bombsimon/logrusr/v2 v2.0.1 // indirect - github.com/bradleyfalzon/ghinstallation/v2 v2.5.0 // indirect + github.com/bradleyfalzon/ghinstallation/v2 v2.6.0 // indirect github.com/casbin/govaluate v1.1.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -159,7 +159,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/gnostic v0.6.9 // indirect - github.com/google/go-github/v53 v53.0.0 // indirect + github.com/google/go-github/v53 v53.2.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.4 // indirect @@ -268,7 +268,7 @@ require ( k8s.io/kube-aggregator v0.26.4 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect mellium.im/sasl v0.3.1 // indirect - oras.land/oras-go/v2 v2.2.0 // indirect + oras.land/oras-go/v2 v2.3.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect diff --git a/go.sum b/go.sum index 44833706d0..0363eac32b 100644 --- a/go.sum +++ b/go.sum @@ -105,8 +105,8 @@ github.com/bmatcuk/doublestar/v4 v4.6.0 h1:HTuxyug8GyFbRkrffIpzNCSK4luc0TY3wzXvz github.com/bmatcuk/doublestar/v4 v4.6.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bombsimon/logrusr/v2 v2.0.1 h1:1VgxVNQMCvjirZIYaT9JYn6sAVGVEcNtRE0y4mvaOAM= github.com/bombsimon/logrusr/v2 v2.0.1/go.mod h1:ByVAX+vHdLGAfdroiMg6q0zgq2FODY2lc5YJvzmOJio= -github.com/bradleyfalzon/ghinstallation/v2 v2.5.0 h1:yaYcGQ7yEIGbsJfW/9z7v1sLiZg/5rSNNXwmMct5XaE= -github.com/bradleyfalzon/ghinstallation/v2 v2.5.0/go.mod h1:amcvPQMrRkWNdueWOjPytGL25xQGzox7425qMgzo+Vo= +github.com/bradleyfalzon/ghinstallation/v2 v2.6.0 h1:IRY7Xy588KylkoycsUhFpW7cdGpy5Y5BPsz4IfuJtGk= +github.com/bradleyfalzon/ghinstallation/v2 v2.6.0/go.mod h1:oQ3etOwN3TRH4EwgW5/7MxSVMGlMlzG/O8TU7eYdoSk= github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao= github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= github.com/bsm/go-vlq v0.0.0-20150828105119-ec6e8d4f5f4e/go.mod h1:N+BjUcTjSxc2mtRGSCPsat1kze3CUtvJN3/jTXlp29k= @@ -374,8 +374,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-github/v53 v53.0.0 h1:T1RyHbSnpHYnoF0ZYKiIPSgPtuJ8G6vgc0MKodXsQDQ= -github.com/google/go-github/v53 v53.0.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao= +github.com/google/go-github/v53 v53.2.0 h1:wvz3FyF53v4BK+AsnvCmeNhf8AkTaeh2SoYu/XUvTtI= +github.com/google/go-github/v53 v53.2.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -761,8 +761,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= -github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= @@ -1239,8 +1239,8 @@ launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80 launchpad.net/xmlpath v0.0.0-20130614043138-000000000004/go.mod h1:vqyExLOM3qBx7mvYRkoxjSCF945s0mbe7YynlKYXtsA= mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= -oras.land/oras-go/v2 v2.2.0 h1:E1fqITD56Eg5neZbxBtAdZVgDHD6wBabJo6xESTcQyo= -oras.land/oras-go/v2 v2.2.0/go.mod h1:pXjn0+KfarspMHHNR3A56j3tgvr+mxArHuI8qVn59v8= +oras.land/oras-go/v2 v2.3.0 h1:lqX1aXdN+DAmDTKjiDyvq85cIaI4RkIKp/PghWlAGIU= +oras.land/oras-go/v2 v2.3.0/go.mod h1:GeAwLuC4G/JpNwkd+bSZ6SkDMGaaYglt6YK2WvZP7uQ= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= diff --git a/pkg/appStore/bean/bean.go b/pkg/appStore/bean/bean.go index 84a7a32360..11a5238c8d 100644 --- a/pkg/appStore/bean/bean.go +++ b/pkg/appStore/bean/bean.go @@ -21,6 +21,7 @@ import ( "fmt" apiBean "github.com/devtron-labs/devtron/api/bean/gitOps" openapi "github.com/devtron-labs/devtron/api/helm-app/openapiClient" + bean3 "github.com/devtron-labs/devtron/api/helm-app/service/bean" "github.com/devtron-labs/devtron/pkg/cluster/repository/bean" bean2 "github.com/devtron-labs/devtron/pkg/deployment/common/bean" "github.com/devtron-labs/devtron/util/gitUtil" @@ -120,6 +121,19 @@ type InstallAppVersionDTO struct { DisplayName string `json:"-"` // used only for external apps } +func (chart *InstallAppVersionDTO) GetAppIdentifierString() string { + displayName := chart.DisplayName + if len(displayName) == 0 { + displayName = chart.AppName + } + appIdentifier := &bean3.AppIdentifier{ + ClusterId: chart.ClusterId, + Namespace: chart.Namespace, + ReleaseName: displayName, + } + return appIdentifier.GetUniqueAppNameIdentifier() +} + // UpdateDeploymentAppType updates deploymentAppType to InstallAppVersionDTO func (chart *InstallAppVersionDTO) UpdateDeploymentAppType(deploymentAppType string) { if chart == nil { diff --git a/pkg/appStore/installedApp/repository/InstalledAppRepository.go b/pkg/appStore/installedApp/repository/InstalledAppRepository.go index da873dd50b..f04fdb93e5 100644 --- a/pkg/appStore/installedApp/repository/InstalledAppRepository.go +++ b/pkg/appStore/installedApp/repository/InstalledAppRepository.go @@ -139,6 +139,7 @@ type InstalledAppRepository interface { GetInstalledAppVersionByClusterIds(clusterIds []int) ([]*InstalledAppVersions, error) //unused GetInstalledAppVersionByClusterIdsV2(clusterIds []int) ([]*InstalledAppVersions, error) GetInstalledApplicationByClusterIdAndNamespaceAndAppName(clusterId int, namespace string, appName string) (*InstalledApps, error) + GetInstalledApplicationByClusterIdAndNamespaceAndAppIdentifier(clusterId int, namespace string, appIdentifier string, appName string) (*InstalledApps, error) GetAppAndEnvDetailsForDeploymentAppTypeInstalledApps(deploymentAppType string, clusterIds []int) ([]*InstalledApps, error) GetDeploymentSuccessfulStatusCountForTelemetry() (int, error) GetGitOpsInstalledAppsWhereArgoAppDeletedIsTrue(installedAppId int, envId int) (InstalledApps, error) @@ -672,6 +673,38 @@ func (impl InstalledAppRepositoryImpl) GetInstalledAppVersionByClusterIdsV2(clus return installedAppVersions, err } +func (impl InstalledAppRepositoryImpl) GetInstalledApplicationByClusterIdAndNamespaceAndAppIdentifier(clusterId int, namespace string, appIdentifier string, appName string) (*InstalledApps, error) { + var installedApps []*InstalledApps + err := impl.dbConnection.Model(&installedApps). + Column("installed_apps.*", "App", "Environment", "App.Team"). + Where("environment.cluster_id = ?", clusterId). + Where("environment.namespace = ?", namespace). + Where("app.app_name = ? OR app.display_name = ?", appName, appName). + Where("installed_apps.active = ?", true). + Where("app.active = ?", true). + Where("environment.active = ?", true). + Select() + // extract app which has matching display name and app name + for _, installedApp := range installedApps { + appObj := installedApp.App + if appObj.DisplayName == appName && appObj.AppName == appIdentifier { + return installedApp, nil + } + } + // if not found any matching app in above case, then return app with only app name + for _, installedApp := range installedApps { + appObj := installedApp.App + if appObj.DisplayName == "" && appObj.AppName == appName { + return installedApp, nil + } + } + if err == nil { + err = pg.ErrNoRows + } + + return &InstalledApps{}, err +} + func (impl InstalledAppRepositoryImpl) GetInstalledApplicationByClusterIdAndNamespaceAndAppName(clusterId int, namespace string, appName string) (*InstalledApps, error) { model := &InstalledApps{} err := impl.dbConnection.Model(model). diff --git a/pkg/appStore/installedApp/service/AppStoreDeploymentDBService.go b/pkg/appStore/installedApp/service/AppStoreDeploymentDBService.go index 33f1a7b9a1..80ac241272 100644 --- a/pkg/appStore/installedApp/service/AppStoreDeploymentDBService.go +++ b/pkg/appStore/installedApp/service/AppStoreDeploymentDBService.go @@ -148,6 +148,10 @@ func (impl *AppStoreDeploymentDBServiceImpl) AppStoreDeployOperationDB(installRe appCreateRequest.AppType = helper.ExternalChartStoreApp appCreateRequest.DisplayName = installRequest.DisplayName } + if globalUtil.IsBaseStack() || globalUtil.IsHelmApp(installRequest.AppOfferingMode) { + appCreateRequest.DisplayName = installRequest.AppName + appCreateRequest.AppName = installRequest.GetAppIdentifierString() + } appCreateRequest, err = impl.createAppForAppStore(appCreateRequest, tx, getAppInstallationMode(installRequest.AppOfferingMode)) if err != nil { impl.logger.Errorw("error while creating app", "error", err) @@ -603,6 +607,7 @@ func (impl *AppStoreDeploymentDBServiceImpl) createAppForAppStore(createRequest TeamId: createRequest.TeamId, AppType: helper.ChartStoreApp, AppOfferingMode: appInstallationMode, + DisplayName: createRequest.DisplayName, } if createRequest.AppType == helper.ExternalChartStoreApp { //when linking ext helm app to chart store, there can be a case that two (or more) external apps can have same name, in diff namespaces or diff diff --git a/pkg/cluster/ClusterCronService.go b/pkg/cluster/ClusterCronService.go deleted file mode 100644 index 6b4a556787..0000000000 --- a/pkg/cluster/ClusterCronService.go +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2024. Devtron Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cluster - -import ( - "fmt" - "github.com/caarlos0/env/v6" - cron2 "github.com/devtron-labs/devtron/util/cron" - "github.com/robfig/cron/v3" - "go.uber.org/zap" -) - -type ClusterCronService interface { -} - -type ClusterCronServiceImpl struct { - logger *zap.SugaredLogger - clusterService ClusterService -} - -type ClusterStatusConfig struct { - ClusterStatusCronTime int `env:"CLUSTER_STATUS_CRON_TIME" envDefault:"15"` -} - -func NewClusterCronServiceImpl(logger *zap.SugaredLogger, clusterService ClusterService, cronLogger *cron2.CronLoggerImpl) (*ClusterCronServiceImpl, error) { - clusterCronServiceImpl := &ClusterCronServiceImpl{ - logger: logger, - clusterService: clusterService, - } - // initialise cron - newCron := cron.New(cron.WithChain(cron.Recover(cronLogger))) - newCron.Start() - cfg := &ClusterStatusConfig{} - err := env.Parse(cfg) - if err != nil { - fmt.Println("failed to parse server cluster status config: " + err.Error()) - } - // add function into cron - _, err = newCron.AddFunc(fmt.Sprintf("@every %dm", cfg.ClusterStatusCronTime), clusterCronServiceImpl.GetAndUpdateClusterConnectionStatus) - if err != nil { - fmt.Println("error in adding cron function into cluster cron service") - return clusterCronServiceImpl, err - } - return clusterCronServiceImpl, nil -} - -func (impl *ClusterCronServiceImpl) GetAndUpdateClusterConnectionStatus() { - impl.logger.Debug("starting cluster connection status fetch thread") - defer impl.logger.Debug("stopped cluster connection status fetch thread") - - //getting all clusters - clusters, err := impl.clusterService.FindAllExceptVirtual() - if err != nil { - impl.logger.Errorw("error in getting all clusters", "err", err) - return - } - impl.clusterService.ConnectClustersInBatch(clusters, true) -} diff --git a/pkg/cluster/ClusterService.go b/pkg/cluster/ClusterService.go index 54d59508db..aa7802b1ab 100644 --- a/pkg/cluster/ClusterService.go +++ b/pkg/cluster/ClusterService.go @@ -20,6 +20,8 @@ import ( "context" "encoding/json" "fmt" + cronUtil "github.com/devtron-labs/devtron/util/cron" + "github.com/robfig/cron/v3" "log" "net/http" "net/url" @@ -43,7 +45,7 @@ import ( "github.com/devtron-labs/devtron/internal/constants" "github.com/devtron-labs/devtron/internal/util" "github.com/devtron-labs/devtron/pkg/cluster/repository" - util2 "github.com/devtron-labs/devtron/util" + globalUtil "github.com/devtron-labs/devtron/util" "github.com/go-pg/pg" "go.uber.org/zap" ) @@ -179,7 +181,7 @@ type ClusterService interface { FindAllNamespacesByUserIdAndClusterId(userId int32, clusterId int, isActionUserSuperAdmin bool) ([]string, error) FindAllForClusterByUserId(userId int32, isActionUserSuperAdmin bool) ([]ClusterBean, error) FetchRolesFromGroup(userId int32) ([]*repository3.RoleModel, error) - HandleErrorInClusterConnections(clusters []*ClusterBean, respMap map[int]error, clusterExistInDb bool) + HandleErrorInClusterConnections(clusters []*ClusterBean, respMap *sync.Map, clusterExistInDb bool) ConnectClustersInBatch(clusters []*ClusterBean, clusterExistInDb bool) ConvertClusterBeanToCluster(clusterBean *ClusterBean, userId int32) *repository.Cluster ConvertClusterBeanObjectToCluster(bean *ClusterBean) *v1alpha1.Cluster @@ -201,7 +203,9 @@ type ClusterServiceImpl struct { func NewClusterServiceImpl(repository repository.ClusterRepository, logger *zap.SugaredLogger, K8sUtil *k8s.K8sServiceImpl, K8sInformerFactory informer.K8sInformerFactory, userAuthRepository repository3.UserAuthRepository, userRepository repository3.UserRepository, - roleGroupRepository repository3.RoleGroupRepository) *ClusterServiceImpl { + roleGroupRepository repository3.RoleGroupRepository, + envVariables *globalUtil.EnvironmentVariables, + cronLogger *cronUtil.CronLoggerImpl) (*ClusterServiceImpl, error) { clusterService := &ClusterServiceImpl{ clusterRepository: repository, logger: logger, @@ -211,8 +215,19 @@ func NewClusterServiceImpl(repository repository.ClusterRepository, logger *zap. userRepository: userRepository, roleGroupRepository: roleGroupRepository, } + // initialise cron + newCron := cron.New(cron.WithChain(cron.Recover(cronLogger))) + newCron.Start() + cfg := envVariables.GlobalClusterConfig + // add function into cron + _, err := newCron.AddFunc(fmt.Sprintf("@every %dm", cfg.ClusterStatusCronTime), clusterService.getAndUpdateClusterConnectionStatus) + if err != nil { + fmt.Println("error in adding cron function into cluster cron service") + return clusterService, err + } + logger.Infow("cluster cron service started successfully!", "cronTime", cfg.ClusterStatusCronTime) go clusterService.buildInformer() - return clusterService + return clusterService, nil } func (impl *ClusterServiceImpl) ConvertClusterBeanToCluster(clusterBean *ClusterBean, userId int32) *repository.Cluster { @@ -242,6 +257,23 @@ func (impl *ClusterServiceImpl) ConvertClusterBeanToCluster(clusterBean *Cluster return model } +// getAndUpdateClusterConnectionStatus is a cron function to update the connection status of all clusters +func (impl *ClusterServiceImpl) getAndUpdateClusterConnectionStatus() { + impl.logger.Info("starting cluster connection status fetch thread") + startTime := time.Now() + defer func() { + impl.logger.Debugw("cluster connection status fetch thread completed", "timeTaken", time.Since(startTime)) + }() + + //getting all clusters + clusters, err := impl.FindAll() + if err != nil { + impl.logger.Errorw("error in getting all clusters", "err", err) + return + } + impl.ConnectClustersInBatch(clusters, true) +} + func (impl *ClusterServiceImpl) Save(parent context.Context, bean *ClusterBean, userId int32) (*ClusterBean, error) { //validating config @@ -289,7 +321,7 @@ func (impl *ClusterServiceImpl) Save(parent context.Context, bean *ClusterBean, //on successful creation of new cluster, update informer cache for namespace group by cluster //here sync for ea mode only - if util2.IsBaseStack() { + if globalUtil.IsBaseStack() { impl.SyncNsInformer(bean) } impl.logger.Info("saving secret for cluster informer") @@ -530,7 +562,7 @@ func (impl *ClusterServiceImpl) Update(ctx context.Context, bean *ClusterBean, u bean.Id = model.Id //here sync for ea mode only - if bean.HasConfigOrUrlChanged && util2.IsBaseStack() { + if bean.HasConfigOrUrlChanged && globalUtil.IsBaseStack() { impl.SyncNsInformer(bean) } impl.logger.Infow("saving secret for cluster informer") @@ -643,7 +675,7 @@ func (impl *ClusterServiceImpl) buildInformer() { impl.K8sInformerFactory.BuildInformer(clusterInfo) } -func (impl ClusterServiceImpl) DeleteFromDb(bean *ClusterBean, userId int32) error { +func (impl *ClusterServiceImpl) DeleteFromDb(bean *ClusterBean, userId int32) error { existingCluster, err := impl.clusterRepository.FindById(bean.Id) if err != nil { impl.logger.Errorw("No matching entry found for delete.", "id", bean.Id) @@ -668,7 +700,7 @@ func (impl ClusterServiceImpl) DeleteFromDb(bean *ClusterBean, userId int32) err return nil } -func (impl ClusterServiceImpl) CheckIfConfigIsValid(cluster *ClusterBean) error { +func (impl *ClusterServiceImpl) CheckIfConfigIsValid(cluster *ClusterBean) error { clusterConfig := cluster.GetClusterConfig() response, err := impl.K8sUtil.DiscoveryClientGetLiveZCall(clusterConfig) if err != nil { @@ -816,21 +848,26 @@ func (impl *ClusterServiceImpl) FetchRolesFromGroup(userId int32) ([]*repository return roles, nil } +func (impl *ClusterServiceImpl) updateConnectionStatusForVirtualCluster(respMap *sync.Map, clusterId int, clusterName string) { + connErr := fmt.Errorf("Get virtual cluster '%s' error: connection not setup for isolated clusters", clusterName) + respMap.Store(clusterId, connErr) +} + func (impl *ClusterServiceImpl) ConnectClustersInBatch(clusters []*ClusterBean, clusterExistInDb bool) { var wg sync.WaitGroup - respMap := make(map[int]error) - mutex := &sync.Mutex{} - + respMap := &sync.Map{} for idx, cluster := range clusters { + if cluster.IsVirtualCluster { + impl.updateConnectionStatusForVirtualCluster(respMap, cluster.Id, cluster.ClusterName) + continue + } wg.Add(1) go func(idx int, cluster *ClusterBean) { defer wg.Done() clusterConfig := cluster.GetClusterConfig() _, _, k8sClientSet, err := impl.K8sUtil.GetK8sConfigAndClients(clusterConfig) if err != nil { - mutex.Lock() - respMap[cluster.Id] = err - mutex.Unlock() + respMap.Store(cluster.Id, err) return } @@ -838,7 +875,7 @@ func (impl *ClusterServiceImpl) ConnectClustersInBatch(clusters []*ClusterBean, if !clusterExistInDb { id = idx } - impl.GetAndUpdateConnectionStatusForOneCluster(k8sClientSet, id, respMap, mutex) + impl.GetAndUpdateConnectionStatusForOneCluster(k8sClientSet, id, respMap) }(idx, cluster) } @@ -846,8 +883,19 @@ func (impl *ClusterServiceImpl) ConnectClustersInBatch(clusters []*ClusterBean, impl.HandleErrorInClusterConnections(clusters, respMap, clusterExistInDb) } -func (impl *ClusterServiceImpl) HandleErrorInClusterConnections(clusters []*ClusterBean, respMap map[int]error, clusterExistInDb bool) { - for id, err := range respMap { +func (impl *ClusterServiceImpl) HandleErrorInClusterConnections(clusters []*ClusterBean, respMap *sync.Map, clusterExistInDb bool) { + respMap.Range(func(key, value any) bool { + defer func() { + // defer to handle panic on type assertion + if r := recover(); r != nil { + impl.logger.Errorw("error in handling error in cluster connections", "key", key, "value", value, "err", r) + } + }() + id := key.(int) + var err error + if connectionError, ok := value.(error); ok { + err = connectionError + } errorInConnecting := "" if err != nil { errorInConnecting = err.Error() @@ -867,7 +915,8 @@ func (impl *ClusterServiceImpl) HandleErrorInClusterConnections(clusters []*Clus //id is index of the cluster in clusters array clusters[id].ErrorInConnecting = errorInConnecting } - } + return true + }) } func (impl *ClusterServiceImpl) ValidateKubeconfig(kubeConfig string) (map[string]*ValidateClusterBean, error) { @@ -1037,7 +1086,7 @@ func (impl *ClusterServiceImpl) ValidateKubeconfig(kubeConfig string) (map[strin } -func (impl *ClusterServiceImpl) GetAndUpdateConnectionStatusForOneCluster(k8sClientSet *kubernetes.Clientset, clusterId int, respMap map[int]error, mutex *sync.Mutex) { +func (impl *ClusterServiceImpl) GetAndUpdateConnectionStatusForOneCluster(k8sClientSet *kubernetes.Clientset, clusterId int, respMap *sync.Map) { response, err := impl.K8sUtil.GetLiveZCall(k8s.LiveZ, k8sClientSet) log.Println("received response for cluster livez status", "response", string(response), "err", err, "clusterId", clusterId) @@ -1063,12 +1112,11 @@ func (impl *ClusterServiceImpl) GetAndUpdateConnectionStatusForOneCluster(k8sCli } else if err == nil && string(response) != "ok" { err = fmt.Errorf("Validation failed with response : %s", string(response)) } - mutex.Lock() - respMap[clusterId] = err - mutex.Unlock() + + respMap.Store(clusterId, err) } -func (impl ClusterServiceImpl) ConvertClusterBeanObjectToCluster(bean *ClusterBean) *v1alpha1.Cluster { +func (impl *ClusterServiceImpl) ConvertClusterBeanObjectToCluster(bean *ClusterBean) *v1alpha1.Cluster { configMap := bean.Config serverUrl := bean.ServerUrl bearerToken := "" @@ -1097,7 +1145,7 @@ func (impl ClusterServiceImpl) ConvertClusterBeanObjectToCluster(bean *ClusterBe return cl } -func (impl ClusterServiceImpl) GetClusterConfigByClusterId(clusterId int) (*k8s.ClusterConfig, error) { +func (impl *ClusterServiceImpl) GetClusterConfigByClusterId(clusterId int) (*k8s.ClusterConfig, error) { clusterBean, err := impl.FindById(clusterId) if err != nil { impl.logger.Errorw("error in getting clusterBean by cluster id", "err", err, "clusterId", clusterId) @@ -1108,7 +1156,7 @@ func (impl ClusterServiceImpl) GetClusterConfigByClusterId(clusterId int) (*k8s. return clusterConfig, nil } -func (impl ClusterServiceImpl) IsClusterReachable(clusterId int) (bool, error) { +func (impl *ClusterServiceImpl) IsClusterReachable(clusterId int) (bool, error) { cluster, err := impl.clusterRepository.FindById(clusterId) if err != nil { impl.logger.Errorw("error in finding cluster from clusterId", "envId", clusterId) diff --git a/pkg/cluster/ClusterServiceExtended.go b/pkg/cluster/ClusterServiceExtended.go index 045d5ce5ae..c8047bc162 100644 --- a/pkg/cluster/ClusterServiceExtended.go +++ b/pkg/cluster/ClusterServiceExtended.go @@ -27,9 +27,6 @@ import ( cluster3 "github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/devtron-labs/common-lib/utils/k8s" - repository5 "github.com/devtron-labs/devtron/pkg/auth/user/repository" - "github.com/devtron-labs/devtron/pkg/k8s/informer" - cluster2 "github.com/devtron-labs/devtron/client/argocdServer/cluster" "github.com/devtron-labs/devtron/client/grafana" "github.com/devtron-labs/devtron/internal/constants" @@ -37,7 +34,6 @@ import ( appStoreBean "github.com/devtron-labs/devtron/pkg/appStore/bean" repository2 "github.com/devtron-labs/devtron/pkg/appStore/installedApp/repository" "github.com/devtron-labs/devtron/pkg/cluster/repository" - "go.uber.org/zap" ) // extends ClusterServiceImpl and enhances method of ClusterService with full mode specific errors @@ -50,30 +46,19 @@ type ClusterServiceImplExtended struct { *ClusterServiceImpl } -func NewClusterServiceImplExtended(repository repository.ClusterRepository, environmentRepository repository.EnvironmentRepository, - grafanaClient grafana.GrafanaClient, logger *zap.SugaredLogger, installedAppRepository repository2.InstalledAppRepository, - K8sUtil *k8s.K8sServiceImpl, - clusterServiceCD cluster2.ServiceClient, K8sInformerFactory informer.K8sInformerFactory, - userAuthRepository repository5.UserAuthRepository, - userRepository repository5.UserRepository, roleGroupRepository repository5.RoleGroupRepository, - gitOpsConfigReadService config.GitOpsConfigReadService) *ClusterServiceImplExtended { +func NewClusterServiceImplExtended(environmentRepository repository.EnvironmentRepository, + grafanaClient grafana.GrafanaClient, installedAppRepository repository2.InstalledAppRepository, + clusterServiceCD cluster2.ServiceClient, + gitOpsConfigReadService config.GitOpsConfigReadService, + clusterServiceImpl *ClusterServiceImpl) *ClusterServiceImplExtended { clusterServiceExt := &ClusterServiceImplExtended{ environmentRepository: environmentRepository, grafanaClient: grafanaClient, installedAppRepository: installedAppRepository, clusterServiceCD: clusterServiceCD, gitOpsConfigReadService: gitOpsConfigReadService, - ClusterServiceImpl: &ClusterServiceImpl{ - clusterRepository: repository, - logger: logger, - K8sUtil: K8sUtil, - K8sInformerFactory: K8sInformerFactory, - userAuthRepository: userAuthRepository, - userRepository: userRepository, - roleGroupRepository: roleGroupRepository, - }, + ClusterServiceImpl: clusterServiceImpl, } - go clusterServiceExt.buildInformer() return clusterServiceExt } diff --git a/pkg/cluster/EnvironmentService.go b/pkg/cluster/EnvironmentService.go index 820451c429..77c9b856ab 100644 --- a/pkg/cluster/EnvironmentService.go +++ b/pkg/cluster/EnvironmentService.go @@ -24,6 +24,7 @@ import ( bean2 "github.com/devtron-labs/devtron/pkg/cluster/repository/bean" "strconv" "strings" + "sync" "time" util2 "github.com/devtron-labs/common-lib/utils/k8s" @@ -63,7 +64,7 @@ type EnvironmentService interface { GetByClusterId(id int) ([]*bean2.EnvironmentBean, error) GetCombinedEnvironmentListForDropDown(token string, isActionUserSuperAdmin bool, auth func(email string, object []string) map[string]bool) ([]*bean2.ClusterEnvDto, error) GetCombinedEnvironmentListForDropDownByClusterIds(token string, clusterIds []int, auth func(token string, object string) bool) ([]*bean2.ClusterEnvDto, error) - HandleErrorInClusterConnections(clusters []*ClusterBean, respMap map[int]error, clusterExistInDb bool) + HandleErrorInClusterConnections(clusters []*ClusterBean, respMap *sync.Map, clusterExistInDb bool) GetDetailsById(envId int) (*repository.Environment, error) } @@ -734,7 +735,7 @@ func (impl EnvironmentServiceImpl) Delete(deleteReq *bean2.EnvironmentBean, user return nil } -func (impl EnvironmentServiceImpl) HandleErrorInClusterConnections(clusters []*ClusterBean, respMap map[int]error, clusterExistInDb bool) { +func (impl EnvironmentServiceImpl) HandleErrorInClusterConnections(clusters []*ClusterBean, respMap *sync.Map, clusterExistInDb bool) { impl.clusterService.HandleErrorInClusterConnections(clusters, respMap, clusterExistInDb) } diff --git a/pkg/k8s/capacity/k8sCapacityService.go b/pkg/k8s/capacity/k8sCapacityService.go index 3ed56849ea..7768f810f5 100644 --- a/pkg/k8s/capacity/k8sCapacityService.go +++ b/pkg/k8s/capacity/k8sCapacityService.go @@ -113,7 +113,7 @@ func (impl *K8sCapacityServiceImpl) GetClusterCapacityDetail(ctx context.Context if err != nil { if client.IsClusterUnReachableError(err) { impl.logger.Errorw("k8s cluster unreachable", "err", err) - return nil, &util.ApiError{HttpStatusCode: http.StatusBadRequest, UserMessage: err.Error()} + return nil, &util.ApiError{HttpStatusCode: http.StatusBadRequest, UserMessage: err.Error(), InternalMessage: err.Error()} } impl.logger.Errorw("error in getting node list", "err", err, "clusterId", cluster.Id) return nil, err diff --git a/scripts/devtron-reference-helm-charts/statefulset-chart_5-1-0/templates/servicemonitor.yaml b/scripts/devtron-reference-helm-charts/statefulset-chart_5-1-0/templates/servicemonitor.yaml index 7368288e0c..276a50211e 100644 --- a/scripts/devtron-reference-helm-charts/statefulset-chart_5-1-0/templates/servicemonitor.yaml +++ b/scripts/devtron-reference-helm-charts/statefulset-chart_5-1-0/templates/servicemonitor.yaml @@ -24,8 +24,50 @@ spec: {{- range .Values.ContainerPort }} {{- if .servicemonitor }} {{- if .servicemonitor.enabled}} - {{- if .servicePort }} + {{- if .servicemonitor.targetPort }} + - targetPort: {{ .servicemonitor.targetPort }} + {{- else if .servicePort }} - port: {{ .name }} + {{- end }} + {{- if .servicemonitor.path }} + path: {{ .servicemonitor.path}} + {{- end }} + {{- if .servicemonitor.scheme }} + scheme: {{ .servicemonitor.scheme}} + {{- end }} + {{- if .servicemonitor.interval }} + interval: {{ .servicemonitor.interval}} + {{- end }} + {{- if .servicemonitor.scrapeTimeout }} + scrapeTimeout: {{ .servicemonitor.scrapeTimeout}} + {{- end }} + {{- if .servicemonitor.basicAuth }} + basicAuth: + {{- toYaml .servicemonitor.basicAuth | nindent 8 }} + {{- end }} + {{- if .servicemonitor.insecureTLS }} + tlsConfig: + insecureSkipVerify: true + {{- else if .servicemonitor.tlsConfig }} + tlsConfig: + {{- toYaml .servicemonitor.tlsConfig | nindent 8 }} + {{- end }} + {{- if .servicemonitor.metricRelabelings}} + metricRelabelings: +{{toYaml .servicemonitor.metricRelabelings | indent 8 }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- range .Values.containers }} + {{- range .ports }} + {{- if .servicemonitor }} + {{- if .servicemonitor.enabled}} + {{- if .servicemonitor.targetPort }} + - targetPort: {{ .servicemonitor.targetPort }} + {{- else if .servicePort }} + - port: {{ .name }} + {{- end }} {{- if .servicemonitor.path }} path: {{ .servicemonitor.path}} {{- end }} @@ -42,6 +84,13 @@ spec: basicAuth: {{- toYaml .servicemonitor.basicAuth | nindent 8 }} {{- end }} + {{- if .servicemonitor.insecureTLS }} + tlsConfig: + insecureSkipVerify: true + {{- else if .servicemonitor.tlsConfig }} + tlsConfig: + {{- toYaml .servicemonitor.tlsConfig | nindent 8 }} + {{- end }} {{- if .servicemonitor.metricRelabelings}} metricRelabelings: {{toYaml .servicemonitor.metricRelabelings | indent 8 }} diff --git a/util/GlobalConfig.go b/util/GlobalConfig.go index e980325d29..879c34094e 100644 --- a/util/GlobalConfig.go +++ b/util/GlobalConfig.go @@ -25,6 +25,7 @@ type EnvironmentVariables struct { DevtronSecretConfig *DevtronSecretConfig DeploymentServiceTypeConfig *DeploymentServiceTypeConfig TerminalEnvVariables *TerminalEnvVariables + GlobalClusterConfig *GlobalClusterConfig } type DeploymentServiceTypeConfig struct { @@ -43,6 +44,10 @@ type GlobalEnvVariables struct { ExecuteWireNilChecker bool `env:"EXECUTE_WIRE_NIL_CHECKER" envDefault:"false"` } +type GlobalClusterConfig struct { + ClusterStatusCronTime int `env:"CLUSTER_STATUS_CRON_TIME" envDefault:"15"` +} + type DevtronSecretConfig struct { DevtronSecretName string `env:"DEVTRON_SECRET_NAME" envDefault:"devtron-secret"` DevtronDexSecretNamespace string `env:"DEVTRON_DEX_SECRET_NAMESPACE" envDefault:"devtroncd"` @@ -58,6 +63,7 @@ func GetEnvironmentVariables() (*EnvironmentVariables, error) { DevtronSecretConfig: &DevtronSecretConfig{}, DeploymentServiceTypeConfig: &DeploymentServiceTypeConfig{}, TerminalEnvVariables: &TerminalEnvVariables{}, + GlobalClusterConfig: &GlobalClusterConfig{}, } err := env.Parse(cfg) if err != nil { diff --git a/util/rbac/EnforcerUtilHelm.go b/util/rbac/EnforcerUtilHelm.go index 15861c0d88..c72ce62661 100644 --- a/util/rbac/EnforcerUtilHelm.go +++ b/util/rbac/EnforcerUtilHelm.go @@ -18,6 +18,7 @@ package rbac import ( "fmt" + "github.com/devtron-labs/devtron/api/helm-app/service/bean" "github.com/devtron-labs/devtron/internal/sql/repository/app" repository2 "github.com/devtron-labs/devtron/pkg/appStore/installedApp/repository" "github.com/devtron-labs/devtron/pkg/cluster/repository" @@ -78,8 +79,7 @@ func (impl EnforcerUtilHelmImpl) GetHelmObjectByTeamIdAndClusterId(teamId int, c func (impl EnforcerUtilHelmImpl) GetHelmObjectByClusterIdNamespaceAndAppName(clusterId int, namespace string, appName string) (string, string) { - installedApp, installedAppErr := impl.InstalledAppRepository.GetInstalledApplicationByClusterIdAndNamespaceAndAppName(clusterId, namespace, appName) - + installedApp, installedAppErr := impl.getInstalledApp(clusterId, namespace, appName) if installedAppErr != nil && installedAppErr != pg.ErrNoRows { impl.logger.Errorw("error on fetching data for rbac object from installed app repository", "err", installedAppErr) return "", "" @@ -93,8 +93,7 @@ func (impl EnforcerUtilHelmImpl) GetHelmObjectByClusterIdNamespaceAndAppName(clu if installedApp == nil || installedAppErr == pg.ErrNoRows { // for cli apps which are not yet linked - - app, err := impl.appRepository.FindAppAndProjectByAppName(appName) + app, err := impl.getAppObject(clusterId, namespace, appName) if err != nil && err != pg.ErrNoRows { impl.logger.Errorw("error in fetching app details", "err", err) return "", "" @@ -102,10 +101,10 @@ func (impl EnforcerUtilHelmImpl) GetHelmObjectByClusterIdNamespaceAndAppName(clu if app.TeamId == 0 { // case if project is not assigned to cli app - return fmt.Sprintf("%s/%s__%s/%s", team.UNASSIGNED_PROJECT, cluster.ClusterName, namespace, appName), "" + return fmt.Sprintf("%s/%s__%s/%s", team.UNASSIGNED_PROJECT, cluster.ClusterName, namespace, appName), fmt.Sprintf("%s/%s/%s", team.UNASSIGNED_PROJECT, namespace, appName) } else { // case if project is assigned - return fmt.Sprintf("%s/%s__%s/%s", app.Team.Name, cluster.ClusterName, namespace, appName), "" + return fmt.Sprintf("%s/%s__%s/%s", app.Team.Name, cluster.ClusterName, namespace, appName), fmt.Sprintf("%s/%s/%s", app.Team.Name, namespace, appName) } } @@ -118,7 +117,7 @@ func (impl EnforcerUtilHelmImpl) GetHelmObjectByClusterIdNamespaceAndAppName(clu } else { if installedApp.EnvironmentId == 0 { // for apps in EA mode, initally env can be 0. - return fmt.Sprintf("%s/%s__%s/%s", installedApp.App.Team.Name, cluster.ClusterName, namespace, appName), "" + return fmt.Sprintf("%s/%s__%s/%s", installedApp.App.Team.Name, cluster.ClusterName, namespace, appName), fmt.Sprintf("%s/%s/%s", installedApp.App.Team.Name, namespace, appName) } // for apps which are assigned to a project and have env ID rbacOne := fmt.Sprintf("%s/%s/%s", installedApp.App.Team.Name, installedApp.Environment.EnvironmentIdentifier, appName) @@ -131,6 +130,37 @@ func (impl EnforcerUtilHelmImpl) GetHelmObjectByClusterIdNamespaceAndAppName(clu } +func (impl EnforcerUtilHelmImpl) getAppObject(clusterId int, namespace string, appName string) (*app.App, error) { + appIdentifier := &bean.AppIdentifier{ + ClusterId: clusterId, + Namespace: namespace, + ReleaseName: appName, + } + appNameIdentifier := appIdentifier.GetUniqueAppNameIdentifier() + appObj, err := impl.appRepository.FindAppAndProjectByAppName(appNameIdentifier) + if appObj == nil || err == pg.ErrNoRows { + impl.logger.Warnw("appObj not found, going to find app using display name ", "appIdentifier", appNameIdentifier, "appName", appName) + appObj, err = impl.appRepository.FindAppAndProjectByAppName(appName) + } + return appObj, err +} + +func (impl EnforcerUtilHelmImpl) getInstalledApp(clusterId int, namespace string, appName string) (*repository2.InstalledApps, error) { + appIdentifier := &bean.AppIdentifier{ + ClusterId: clusterId, + Namespace: namespace, + ReleaseName: appName, + } + appNameIdentifier := appIdentifier.GetUniqueAppNameIdentifier() + //installedApp, installedAppErr := impl.InstalledAppRepository.GetInstalledApplicationByClusterIdAndNamespaceAndAppName(clusterId, namespace, appNameIdentifier) + //if installedApp == nil || installedAppErr == pg.ErrNoRows { + // impl.logger.Warnw("installed app not found, going to find app using display name ", "appIdentifier", appNameIdentifier, "appName", appName) + // installedApp, installedAppErr = impl.InstalledAppRepository.GetInstalledApplicationByClusterIdAndNamespaceAndAppName(clusterId, namespace, appName) + //} + return impl.InstalledAppRepository.GetInstalledApplicationByClusterIdAndNamespaceAndAppIdentifier(clusterId, namespace, appNameIdentifier, appName) + //return installedApp, installedAppErr +} + func (impl EnforcerUtilHelmImpl) GetAppRBACNameByInstalledAppId(installedAppVersionId int) (string, string) { InstalledApp, err := impl.InstalledAppRepository.GetInstalledApp(installedAppVersionId) diff --git a/vendor/github.com/bradleyfalzon/ghinstallation/v2/appsTransport.go b/vendor/github.com/bradleyfalzon/ghinstallation/v2/appsTransport.go index 317de76fc1..a67510c1c6 100644 --- a/vendor/github.com/bradleyfalzon/ghinstallation/v2/appsTransport.go +++ b/vendor/github.com/bradleyfalzon/ghinstallation/v2/appsTransport.go @@ -106,6 +106,11 @@ func (t *AppsTransport) RoundTrip(req *http.Request) (*http.Response, error) { return resp, err } +// AppID returns the appID of the transport +func (t *AppsTransport) AppID() int64 { + return t.appID +} + type AppsTransportOption func(*AppsTransport) // WithSigner configures the AppsTransport to use the given Signer for generating JWT tokens. diff --git a/vendor/github.com/bradleyfalzon/ghinstallation/v2/transport.go b/vendor/github.com/bradleyfalzon/ghinstallation/v2/transport.go index 015ebe4b65..84012a00df 100644 --- a/vendor/github.com/bradleyfalzon/ghinstallation/v2/transport.go +++ b/vendor/github.com/bradleyfalzon/ghinstallation/v2/transport.go @@ -191,6 +191,16 @@ func (t *Transport) Expiry() (expiresAt time.Time, refreshAt time.Time, err erro return t.token.ExpiresAt, t.token.getRefreshTime(), nil } +// AppID returns the app ID associated with the transport +func (t *Transport) AppID() int64 { + return t.appID +} + +// InstallationID returns the installation ID associated with the transport +func (t *Transport) InstallationID() int64 { + return t.installationID +} + func (t *Transport) refreshToken(ctx context.Context) error { // Convert InstallationTokenOptions into a ReadWriter to pass as an argument to http.NewRequest. body, err := GetReadWriter(t.InstallationTokenOptions) diff --git a/vendor/github.com/google/go-github/v53/github/actions_runners.go b/vendor/github.com/google/go-github/v53/github/actions_runners.go index 40c6be3a92..3990a5a90f 100644 --- a/vendor/github.com/google/go-github/v53/github/actions_runners.go +++ b/vendor/github.com/google/go-github/v53/github/actions_runners.go @@ -45,6 +45,60 @@ func (s *ActionsService) ListRunnerApplicationDownloads(ctx context.Context, own return rads, resp, nil } +// GenerateJITConfigRequest specifies body parameters to GenerateRepoJITConfig. +type GenerateJITConfigRequest struct { + Name string `json:"name"` + RunnerGroupID int64 `json:"runner_group_id"` + WorkFolder *string `json:"work_folder,omitempty"` + + // Labels represents the names of the custom labels to add to the runner. + // Minimum items: 1. Maximum items: 100. + Labels []string `json:"labels"` +} + +// JITRunnerConfig represents encoded JIT configuration that can be used to bootstrap a self-hosted runner. +type JITRunnerConfig struct { + EncodedJITConfig *string `json:"encoded_jit_config,omitempty"` +} + +// GenerateOrgJITConfig generate a just-in-time configuration for an organization. +// +// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-configuration-for-a-just-in-time-runner-for-an-organization +func (s *ActionsService) GenerateOrgJITConfig(ctx context.Context, owner string, request *GenerateJITConfigRequest) (*JITRunnerConfig, *Response, error) { + u := fmt.Sprintf("orgs/%v/actions/runners/generate-jitconfig", owner) + req, err := s.client.NewRequest("POST", u, request) + if err != nil { + return nil, nil, err + } + + jitConfig := new(JITRunnerConfig) + resp, err := s.client.Do(ctx, req, jitConfig) + if err != nil { + return nil, resp, err + } + + return jitConfig, resp, nil +} + +// GenerateRepoJITConfig generates a just-in-time configuration for a repository. +// +// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-configuration-for-a-just-in-time-runner-for-a-repository +func (s *ActionsService) GenerateRepoJITConfig(ctx context.Context, owner, repo string, request *GenerateJITConfigRequest) (*JITRunnerConfig, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/actions/runners/generate-jitconfig", owner, repo) + req, err := s.client.NewRequest("POST", u, request) + if err != nil { + return nil, nil, err + } + + jitConfig := new(JITRunnerConfig) + resp, err := s.client.Do(ctx, req, jitConfig) + if err != nil { + return nil, resp, err + } + + return jitConfig, resp, nil +} + // RegistrationToken represents a token that can be used to add a self-hosted runner to a repository. type RegistrationToken struct { Token *string `json:"token,omitempty"` diff --git a/vendor/github.com/google/go-github/v53/github/code-scanning.go b/vendor/github.com/google/go-github/v53/github/code-scanning.go index 6717348ed7..e4a6abeba3 100644 --- a/vendor/github.com/google/go-github/v53/github/code-scanning.go +++ b/vendor/github.com/google/go-github/v53/github/code-scanning.go @@ -378,3 +378,76 @@ func (s *CodeScanningService) GetAnalysis(ctx context.Context, owner, repo strin return analysis, resp, nil } + +// DefaultSetupConfiguration represents a code scanning default setup configuration. +type DefaultSetupConfiguration struct { + State *string `json:"state,omitempty"` + Languages []string `json:"languages,omitempty"` + QuerySuite *string `json:"query_suite,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` +} + +// GetDefaultSetupConfiguration gets a code scanning default setup configuration. +// +// You must use an access token with the repo scope to use this +// endpoint with private repos or the public_repo scope for public repos. GitHub Apps must have the repo write +// permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/code-scanning#get-a-code-scanning-default-setup-configuration +func (s *CodeScanningService) GetDefaultSetupConfiguration(ctx context.Context, owner, repo string) (*DefaultSetupConfiguration, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/code-scanning/default-setup", owner, repo) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + cfg := new(DefaultSetupConfiguration) + resp, err := s.client.Do(ctx, req, cfg) + if err != nil { + return nil, resp, err + } + + return cfg, resp, nil +} + +// UpdateDefaultSetupConfigurationOptions specifies parameters to the CodeScanningService.UpdateDefaultSetupConfiguration +// method. +type UpdateDefaultSetupConfigurationOptions struct { + State string `json:"state"` + QuerySuite *string `json:"query_suite,omitempty"` + Languages []string `json:"languages,omitempty"` +} + +// UpdateDefaultSetupConfigurationResponse represents a response from updating a code scanning default setup configuration. +type UpdateDefaultSetupConfigurationResponse struct { + RunID *int64 `json:"run_id,omitempty"` + RunURL *string `json:"run_url,omitempty"` +} + +// UpdateDefaultSetupConfiguration updates a code scanning default setup configuration. +// +// You must use an access token with the repo scope to use this +// endpoint with private repos or the public_repo scope for public repos. GitHub Apps must have the repo write +// permission to use this endpoint. +// +// This method might return an AcceptedError and a status code of 202. This is because this is the status that GitHub +// returns to signify that it has now scheduled the update of the pull request branch in a background task. +// +// GitHub API docs: https://docs.github.com/en/rest/code-scanning#update-a-code-scanning-default-setup-configuration +func (s *CodeScanningService) UpdateDefaultSetupConfiguration(ctx context.Context, owner, repo string, options *UpdateDefaultSetupConfigurationOptions) (*UpdateDefaultSetupConfigurationResponse, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/code-scanning/default-setup", owner, repo) + + req, err := s.client.NewRequest("PATCH", u, options) + if err != nil { + return nil, nil, err + } + + a := new(UpdateDefaultSetupConfigurationResponse) + resp, err := s.client.Do(ctx, req, a) + if err != nil { + return nil, resp, err + } + + return a, resp, nil +} diff --git a/vendor/github.com/google/go-github/v53/github/codespaces.go b/vendor/github.com/google/go-github/v53/github/codespaces.go new file mode 100644 index 0000000000..a260c227de --- /dev/null +++ b/vendor/github.com/google/go-github/v53/github/codespaces.go @@ -0,0 +1,254 @@ +// Copyright 2023 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// CodespacesService handles communication with the Codespaces related +// methods of the GitHub API. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/ +type CodespacesService service + +// Codespace represents a codespace. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces +type Codespace struct { + ID *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + DisplayName *string `json:"display_name,omitempty"` + EnvironmentID *string `json:"environment_id,omitempty"` + Owner *User `json:"owner,omitempty"` + BillableOwner *User `json:"billable_owner,omitempty"` + Repository *Repository `json:"repository,omitempty"` + Machine *CodespacesMachine `json:"machine,omitempty"` + DevcontainerPath *string `json:"devcontainer_path,omitempty"` + Prebuild *bool `json:"prebuild,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + LastUsedAt *Timestamp `json:"last_used_at,omitempty"` + State *string `json:"state,omitempty"` + URL *string `json:"url,omitempty"` + GitStatus *CodespacesGitStatus `json:"git_status,omitempty"` + Location *string `json:"location,omitempty"` + IdleTimeoutMinutes *int `json:"idle_timeout_minutes,omitempty"` + WebURL *string `json:"web_url,omitempty"` + MachinesURL *string `json:"machines_url,omitempty"` + StartURL *string `json:"start_url,omitempty"` + StopURL *string `json:"stop_url,omitempty"` + PullsURL *string `json:"pulls_url,omitempty"` + RecentFolders []string `json:"recent_folders,omitempty"` + RuntimeConstraints *CodespacesRuntimeConstraints `json:"runtime_constraints,omitempty"` + PendingOperation *bool `json:"pending_operation,omitempty"` + PendingOperationDisabledReason *string `json:"pending_operation_disabled_reason,omitempty"` + IdleTimeoutNotice *string `json:"idle_timeout_notice,omitempty"` + RetentionPeriodMinutes *int `json:"retention_period_minutes,omitempty"` + RetentionExpiresAt *Timestamp `json:"retention_expires_at,omitempty"` + LastKnownStopNotice *string `json:"last_known_stop_notice,omitempty"` +} + +// CodespacesGitStatus represents the git status of a codespace. +type CodespacesGitStatus struct { + Ahead *int `json:"ahead,omitempty"` + Behind *int `json:"behind,omitempty"` + HasUnpushedChanges *bool `json:"has_unpushed_changes,omitempty"` + HasUncommittedChanges *bool `json:"has_uncommitted_changes,omitempty"` + Ref *string `json:"ref,omitempty"` +} + +// CodespacesMachine represents the machine type of a codespace. +type CodespacesMachine struct { + Name *string `json:"name,omitempty"` + DisplayName *string `json:"display_name,omitempty"` + OperatingSystem *string `json:"operating_system,omitempty"` + StorageInBytes *int64 `json:"storage_in_bytes,omitempty"` + MemoryInBytes *int64 `json:"memory_in_bytes,omitempty"` + CPUs *int `json:"cpus,omitempty"` + PrebuildAvailability *string `json:"prebuild_availability,omitempty"` +} + +// CodespacesRuntimeConstraints represents the runtime constraints of a codespace. +type CodespacesRuntimeConstraints struct { + AllowedPortPrivacySettings []string `json:"allowed_port_privacy_settings,omitempty"` +} + +// ListCodespaces represents the response from the list codespaces endpoints. +type ListCodespaces struct { + TotalCount *int `json:"total_count,omitempty"` + Codespaces []*Codespace `json:"codespaces"` +} + +// ListInRepo lists codespaces for a user in a repository. +// +// Lists the codespaces associated with a specified repository and the authenticated user. +// You must authenticate using an access token with the codespace scope to use this endpoint. +// GitHub Apps must have read access to the codespaces repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/codespaces?apiVersion=2022-11-28#list-codespaces-in-a-repository-for-the-authenticated-user +func (s *CodespacesService) ListInRepo(ctx context.Context, owner, repo string, opts *ListOptions) (*ListCodespaces, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/codespaces", owner, repo) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var codespaces *ListCodespaces + resp, err := s.client.Do(ctx, req, &codespaces) + if err != nil { + return nil, resp, err + } + + return codespaces, resp, nil +} + +// ListOptions represents the options for listing codespaces for a user. +type ListCodespacesOptions struct { + ListOptions + RepositoryID int64 `url:"repository_id,omitempty"` +} + +// List lists codespaces for an authenticated user. +// +// Lists the authenticated user's codespaces. +// You must authenticate using an access token with the codespace scope to use this endpoint. +// GitHub Apps must have read access to the codespaces repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/codespaces?apiVersion=2022-11-28#list-codespaces-for-the-authenticated-user +func (s *CodespacesService) List(ctx context.Context, opts *ListCodespacesOptions) (*ListCodespaces, *Response, error) { + u := fmt.Sprint("user/codespaces") + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var codespaces *ListCodespaces + resp, err := s.client.Do(ctx, req, &codespaces) + if err != nil { + return nil, resp, err + } + + return codespaces, resp, nil +} + +// CreateCodespaceOptions represents options for the creation of a codespace in a repository. +type CreateCodespaceOptions struct { + Ref *string `json:"ref,omitempty"` + // Geo represents the geographic area for this codespace. + // If not specified, the value is assigned by IP. + // This property replaces location, which is being deprecated. + // Geo can be one of: `EuropeWest`, `SoutheastAsia`, `UsEast`, `UsWest`. + Geo *string `json:"geo,omitempty"` + ClientIP *string `json:"client_ip,omitempty"` + Machine *string `json:"machine,omitempty"` + DevcontainerPath *string `json:"devcontainer_path,omitempty"` + MultiRepoPermissionsOptOut *bool `json:"multi_repo_permissions_opt_out,omitempty"` + WorkingDirectory *string `json:"working_directory,omitempty"` + IdleTimeoutMinutes *int `json:"idle_timeout_minutes,omitempty"` + DisplayName *string `json:"display_name,omitempty"` + // RetentionPeriodMinutes represents the duration in minutes after codespace has gone idle in which it will be deleted. + // Must be integer minutes between 0 and 43200 (30 days). + RetentionPeriodMinutes *int `json:"retention_period_minutes,omitempty"` +} + +// CreateInRepo creates a codespace in a repository. +// +// Creates a codespace owned by the authenticated user in the specified repository. +// You must authenticate using an access token with the codespace scope to use this endpoint. +// GitHub Apps must have write access to the codespaces repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/codespaces?apiVersion=2022-11-28#create-a-codespace-in-a-repository +func (s *CodespacesService) CreateInRepo(ctx context.Context, owner, repo string, request *CreateCodespaceOptions) (*Codespace, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/codespaces", owner, repo) + + req, err := s.client.NewRequest("POST", u, request) + if err != nil { + return nil, nil, err + } + + var codespace *Codespace + resp, err := s.client.Do(ctx, req, &codespace) + if err != nil { + return nil, resp, err + } + + return codespace, resp, nil +} + +// Start starts a codespace. +// +// You must authenticate using an access token with the codespace scope to use this endpoint. +// GitHub Apps must have write access to the codespaces_lifecycle_admin repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/codespaces?apiVersion=2022-11-28#start-a-codespace-for-the-authenticated-user +func (s *CodespacesService) Start(ctx context.Context, codespaceName string) (*Codespace, *Response, error) { + u := fmt.Sprintf("user/codespaces/%v/start", codespaceName) + + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, nil, err + } + + var codespace *Codespace + resp, err := s.client.Do(ctx, req, &codespace) + if err != nil { + return nil, resp, err + } + + return codespace, resp, nil +} + +// Stop stops a codespace. +// +// You must authenticate using an access token with the codespace scope to use this endpoint. +// GitHub Apps must have write access to the codespaces_lifecycle_admin repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/codespaces?apiVersion=2022-11-28#stop-a-codespace-for-the-authenticated-user +func (s *CodespacesService) Stop(ctx context.Context, codespaceName string) (*Codespace, *Response, error) { + u := fmt.Sprintf("user/codespaces/%v/stop", codespaceName) + + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, nil, err + } + + var codespace *Codespace + resp, err := s.client.Do(ctx, req, &codespace) + if err != nil { + return nil, resp, err + } + + return codespace, resp, nil +} + +// Delete deletes a codespace. +// +// You must authenticate using an access token with the codespace scope to use this endpoint. +// GitHub Apps must have write access to the codespaces repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/codespaces?apiVersion=2022-11-28#delete-a-codespace-for-the-authenticated-user +func (s *CodespacesService) Delete(ctx context.Context, codespaceName string) (*Response, error) { + u := fmt.Sprintf("user/codespaces/%v", codespaceName) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/v53/github/codespaces_secrets.go b/vendor/github.com/google/go-github/v53/github/codespaces_secrets.go new file mode 100644 index 0000000000..e11c679c66 --- /dev/null +++ b/vendor/github.com/google/go-github/v53/github/codespaces_secrets.go @@ -0,0 +1,405 @@ +// Copyright 2023 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ListUserSecrets list all secrets available for a users codespace +// +// Lists all secrets available for a user's Codespaces without revealing their encrypted values +// You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must have Codespaces access to use this endpoint +// GitHub Apps must have read access to the codespaces_user_secrets user permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#list-secrets-for-the-authenticated-user +func (s *CodespacesService) ListUserSecrets(ctx context.Context, opts *ListOptions) (*Secrets, *Response, error) { + u, err := addOptions("user/codespaces/secrets", opts) + if err != nil { + return nil, nil, err + } + return s.listSecrets(ctx, u) +} + +// ListOrgSecrets list all secrets available to an org +// +// Lists all Codespaces secrets available at the organization-level without revealing their encrypted values. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/organization-secrets?apiVersion=2022-11-28#list-organization-secrets +func (s *CodespacesService) ListOrgSecrets(ctx context.Context, org string, opts *ListOptions) (*Secrets, *Response, error) { + u := fmt.Sprintf("orgs/%v/codespaces/secrets", org) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + return s.listSecrets(ctx, u) +} + +// ListRepoSecrets list all secrets available to a repo +// +// Lists all secrets available in a repository without revealing their encrypted values. You must authenticate using an access token with the repo scope to use this endpoint. GitHub Apps must have write access to the codespaces_secrets repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/repository-secrets?apiVersion=2022-11-28#list-repository-secrets +func (s *CodespacesService) ListRepoSecrets(ctx context.Context, owner, repo string, opts *ListOptions) (*Secrets, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/codespaces/secrets", owner, repo) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + return s.listSecrets(ctx, u) +} + +func (s *CodespacesService) listSecrets(ctx context.Context, url string) (*Secrets, *Response, error) { + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + var secrets *Secrets + resp, err := s.client.Do(ctx, req, &secrets) + if err != nil { + return nil, resp, err + } + + return secrets, resp, nil +} + +// GetUserPublicKey gets the users public key for encrypting codespace secrets +// +// Gets your public key, which you need to encrypt secrets. You need to encrypt a secret before you can create or update secrets. +// You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must have Codespaces access to use this endpoint. +// GitHub Apps must have read access to the codespaces_user_secrets user permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#get-public-key-for-the-authenticated-user +func (s *CodespacesService) GetUserPublicKey(ctx context.Context) (*PublicKey, *Response, error) { + return s.getPublicKey(ctx, "user/codespaces/secrets/public-key") +} + +// GetOrgPublicKey gets the org public key for encrypting codespace secrets +// +// Gets a public key for an organization, which is required in order to encrypt secrets. You need to encrypt the value of a secret before you can create or update secrets. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/organization-secrets?apiVersion=2022-11-28#get-an-organization-public-key +func (s *CodespacesService) GetOrgPublicKey(ctx context.Context, org string) (*PublicKey, *Response, error) { + return s.getPublicKey(ctx, fmt.Sprintf("orgs/%v/codespaces/secrets/public-key", org)) +} + +// GetRepoPublicKey gets the repo public key for encrypting codespace secrets +// +// Gets your public key, which you need to encrypt secrets. You need to encrypt a secret before you can create or update secrets. Anyone with read access to the repository can use this endpoint. If the repository is private you must use an access token with the repo scope. GitHub Apps must have write access to the codespaces_secrets repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/repository-secrets?apiVersion=2022-11-28#get-a-repository-public-key +func (s *CodespacesService) GetRepoPublicKey(ctx context.Context, owner, repo string) (*PublicKey, *Response, error) { + return s.getPublicKey(ctx, fmt.Sprintf("repos/%v/%v/codespaces/secrets/public-key", owner, repo)) +} + +func (s *CodespacesService) getPublicKey(ctx context.Context, url string) (*PublicKey, *Response, error) { + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + var publicKey *PublicKey + resp, err := s.client.Do(ctx, req, &publicKey) + if err != nil { + return nil, resp, err + } + + return publicKey, resp, nil +} + +// GetUserSecret gets a users codespace secret +// +// Gets a secret available to a user's codespaces without revealing its encrypted value. +// You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must have Codespaces access to use this endpoint. +// GitHub Apps must have read access to the codespaces_user_secrets user permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#get-a-secret-for-the-authenticated-user +func (s *CodespacesService) GetUserSecret(ctx context.Context, name string) (*Secret, *Response, error) { + u := fmt.Sprintf("user/codespaces/secrets/%v", name) + return s.getSecret(ctx, u) +} + +// GetOrgSecret gets an org codespace secret +// +// Gets an organization secret without revealing its encrypted value. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/organization-secrets?apiVersion=2022-11-28#get-an-organization-secret +func (s *CodespacesService) GetOrgSecret(ctx context.Context, org, name string) (*Secret, *Response, error) { + u := fmt.Sprintf("orgs/%v/codespaces/secrets/%v", org, name) + return s.getSecret(ctx, u) +} + +// GetRepoSecret gets a repo codespace secret +// +// Gets a single repository secret without revealing its encrypted value. You must authenticate using an access token with the repo scope to use this endpoint. GitHub Apps must have write access to the codespaces_secrets repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/repository-secrets?apiVersion=2022-11-28#get-a-repository-secret +func (s *CodespacesService) GetRepoSecret(ctx context.Context, owner, repo, name string) (*Secret, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/codespaces/secrets/%v", owner, repo, name) + return s.getSecret(ctx, u) +} + +func (s *CodespacesService) getSecret(ctx context.Context, url string) (*Secret, *Response, error) { + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + var secret *Secret + resp, err := s.client.Do(ctx, req, &secret) + if err != nil { + return nil, resp, err + } + + return secret, resp, nil +} + +// CreateOrUpdateUserSecret creates or updates a users codespace secret +// +// Creates or updates a secret for a user's codespace with an encrypted value. Encrypt your secret using LibSodium. +// You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must also have Codespaces access to use this endpoint. +// GitHub Apps must have write access to the codespaces_user_secrets user permission and codespaces_secrets repository permission on all referenced repositories to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#create-or-update-a-secret-for-the-authenticated-user +func (s *CodespacesService) CreateOrUpdateUserSecret(ctx context.Context, eSecret *EncryptedSecret) (*Response, error) { + u := fmt.Sprintf("user/codespaces/secrets/%v", eSecret.Name) + return s.createOrUpdateSecret(ctx, u, eSecret) +} + +// CreateOrUpdateOrgSecret creates or updates an orgs codespace secret +// +// Creates or updates an organization secret with an encrypted value. Encrypt your secret using LibSodium. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/organization-secrets?apiVersion=2022-11-28#create-or-update-an-organization-secret +func (s *CodespacesService) CreateOrUpdateOrgSecret(ctx context.Context, org string, eSecret *EncryptedSecret) (*Response, error) { + u := fmt.Sprintf("orgs/%v/codespaces/secrets/%v", org, eSecret.Name) + return s.createOrUpdateSecret(ctx, u, eSecret) +} + +// CreateOrUpdateRepoSecret creates or updates a repos codespace secret +// +// Creates or updates a repository secret with an encrypted value. Encrypt your secret using LibSodium. You must authenticate using an access token with the repo scope to use this endpoint. GitHub Apps must have write access to the codespaces_secrets repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/repository-secrets?apiVersion=2022-11-28#create-or-update-a-repository-secret +func (s *CodespacesService) CreateOrUpdateRepoSecret(ctx context.Context, owner, repo string, eSecret *EncryptedSecret) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/codespaces/secrets/%v", owner, repo, eSecret.Name) + return s.createOrUpdateSecret(ctx, u, eSecret) +} + +func (s *CodespacesService) createOrUpdateSecret(ctx context.Context, url string, eSecret *EncryptedSecret) (*Response, error) { + req, err := s.client.NewRequest("PUT", url, eSecret) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// DeleteUserSecret deletes a users codespace secret +// +// Deletes a secret from a user's codespaces using the secret name. Deleting the secret will remove access from all codespaces that were allowed to access the secret. +// You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must have Codespaces access to use this endpoint. +// GitHub Apps must have write access to the codespaces_user_secrets user permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#delete-a-secret-for-the-authenticated-user +func (s *CodespacesService) DeleteUserSecret(ctx context.Context, name string) (*Response, error) { + u := fmt.Sprintf("user/codespaces/secrets/%v", name) + return s.deleteSecret(ctx, u) +} + +// DeleteOrgSecret deletes an orgs codespace secret +// +// Deletes an organization secret using the secret name. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/organization-secrets?apiVersion=2022-11-28#delete-an-organization-secret +func (s *CodespacesService) DeleteOrgSecret(ctx context.Context, org, name string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/codespaces/secrets/%v", org, name) + return s.deleteSecret(ctx, u) +} + +// DeleteRepoSecret deletes a repos codespace secret +// +// Deletes a secret in a repository using the secret name. You must authenticate using an access token with the repo scope to use this endpoint. GitHub Apps must have write access to the codespaces_secrets repository permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/repository-secrets?apiVersion=2022-11-28#delete-a-repository-secret +func (s *CodespacesService) DeleteRepoSecret(ctx context.Context, owner, repo, name string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/codespaces/secrets/%v", owner, repo, name) + return s.deleteSecret(ctx, u) +} + +func (s *CodespacesService) deleteSecret(ctx context.Context, url string) (*Response, error) { + req, err := s.client.NewRequest("DELETE", url, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// ListSelectedReposForUserSecret lists the repositories that have been granted the ability to use a user's codespace secret. +// +// You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must have Codespaces access to use this endpoint. +// GitHub Apps must have read access to the codespaces_user_secrets user permission and write access to the codespaces_secrets repository permission on all referenced repositories to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#list-selected-repositories-for-a-user-secret +func (s *CodespacesService) ListSelectedReposForUserSecret(ctx context.Context, name string, opts *ListOptions) (*SelectedReposList, *Response, error) { + u := fmt.Sprintf("user/codespaces/secrets/%v/repositories", name) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + return s.listSelectedReposForSecret(ctx, u) +} + +// ListSelectedReposForOrgSecret lists the repositories that have been granted the ability to use an organization's codespace secret. +// +// Lists all repositories that have been selected when the visibility for repository access to a secret is set to selected. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/codespaces/organization-secrets?apiVersion=2022-11-28#list-selected-repositories-for-an-organization-secret +func (s *CodespacesService) ListSelectedReposForOrgSecret(ctx context.Context, org, name string, opts *ListOptions) (*SelectedReposList, *Response, error) { + u := fmt.Sprintf("orgs/%v/codespaces/secrets/%v/repositories", org, name) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + return s.listSelectedReposForSecret(ctx, u) +} + +func (s *CodespacesService) listSelectedReposForSecret(ctx context.Context, url string) (*SelectedReposList, *Response, error) { + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + var repositories *SelectedReposList + resp, err := s.client.Do(ctx, req, &repositories) + if err != nil { + return nil, resp, err + } + + return repositories, resp, nil +} + +// SetSelectedReposForUserSecret sets the repositories that have been granted the ability to use a user's codespace secret. +// +// You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must have Codespaces access to use this endpoint. +// GitHub Apps must have write access to the codespaces_user_secrets user permission and write access to the codespaces_secrets repository permission on all referenced repositories to use this endpoint. +// +// Github API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#set-selected-repositories-for-a-user-secret +func (s *CodespacesService) SetSelectedReposForUserSecret(ctx context.Context, name string, ids SelectedRepoIDs) (*Response, error) { + u := fmt.Sprintf("user/codespaces/secrets/%v/repositories", name) + return s.setSelectedRepoForSecret(ctx, u, ids) +} + +// SetSelectedReposForOrgSecret sets the repositories that have been granted the ability to use a user's codespace secret. +// +// Replaces all repositories for an organization secret when the visibility for repository access is set to selected. The visibility is set when you Create or update an organization secret. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// Github API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#set-selected-repositories-for-a-user-secret +func (s *CodespacesService) SetSelectedReposForOrgSecret(ctx context.Context, org, name string, ids SelectedRepoIDs) (*Response, error) { + u := fmt.Sprintf("orgs/%v/codespaces/secrets/%v/repositories", org, name) + return s.setSelectedRepoForSecret(ctx, u, ids) +} + +func (s *CodespacesService) setSelectedRepoForSecret(ctx context.Context, url string, ids SelectedRepoIDs) (*Response, error) { + type repoIDs struct { + SelectedIDs SelectedRepoIDs `json:"selected_repository_ids"` + } + + req, err := s.client.NewRequest("PUT", url, repoIDs{SelectedIDs: ids}) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// AddSelectedRepoToUserSecret adds a repository to the list of repositories that have been granted the ability to use a user's codespace secret. +// +// Adds a repository to the selected repositories for a user's codespace secret. You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must have Codespaces access to use this endpoint. GitHub Apps must have write access to the codespaces_user_secrets user permission and write access to the codespaces_secrets repository permission on the referenced repository to use this endpoint. +// +// Github API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#add-a-selected-repository-to-a-user-secret +func (s *CodespacesService) AddSelectedRepoToUserSecret(ctx context.Context, name string, repo *Repository) (*Response, error) { + u := fmt.Sprintf("user/codespaces/secrets/%v/repositories/%v", name, *repo.ID) + return s.addSelectedRepoToSecret(ctx, u) +} + +// AddSelectedRepoToOrgSecret adds a repository to the list of repositories that have been granted the ability to use an organization's codespace secret. +// +// Adds a repository to an organization secret when the visibility for repository access is set to selected. The visibility is set when you Create or update an organization secret. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// Github API docs: https://docs.github.com/en/rest/codespaces/organization-secrets?apiVersion=2022-11-28#add-selected-repository-to-an-organization-secret +func (s *CodespacesService) AddSelectedRepoToOrgSecret(ctx context.Context, org, name string, repo *Repository) (*Response, error) { + u := fmt.Sprintf("orgs/%v/codespaces/secrets/%v/repositories/%v", org, name, *repo.ID) + return s.addSelectedRepoToSecret(ctx, u) +} + +func (s *CodespacesService) addSelectedRepoToSecret(ctx context.Context, url string) (*Response, error) { + req, err := s.client.NewRequest("PUT", url, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// RemoveSelectedRepoFromUserSecret removes a repository from the list of repositories that have been granted the ability to use a user's codespace secret. +// +// Removes a repository from the selected repositories for a user's codespace secret. You must authenticate using an access token with the codespace or codespace:secrets scope to use this endpoint. User must have Codespaces access to use this endpoint. GitHub Apps must have write access to the codespaces_user_secrets user permission to use this endpoint. +// +// Github API docs: https://docs.github.com/en/rest/codespaces/secrets?apiVersion=2022-11-28#remove-a-selected-repository-from-a-user-secret +func (s *CodespacesService) RemoveSelectedRepoFromUserSecret(ctx context.Context, name string, repo *Repository) (*Response, error) { + u := fmt.Sprintf("user/codespaces/secrets/%v/repositories/%v", name, *repo.ID) + return s.removeSelectedRepoFromSecret(ctx, u) +} + +// RemoveSelectedRepoFromOrgSecret removes a repository from the list of repositories that have been granted the ability to use an organization's codespace secret. +// +// Removes a repository from an organization secret when the visibility for repository access is set to selected. The visibility is set when you Create or update an organization secret. You must authenticate using an access token with the admin:org scope to use this endpoint. +// +// Github API docs: https://docs.github.com/en/rest/codespaces/organization-secrets?apiVersion=2022-11-28#remove-selected-repository-from-an-organization-secret +func (s *CodespacesService) RemoveSelectedRepoFromOrgSecret(ctx context.Context, org, name string, repo *Repository) (*Response, error) { + u := fmt.Sprintf("orgs/%v/codespaces/secrets/%v/repositories/%v", org, name, *repo.ID) + return s.removeSelectedRepoFromSecret(ctx, u) +} + +func (s *CodespacesService) removeSelectedRepoFromSecret(ctx context.Context, url string) (*Response, error) { + req, err := s.client.NewRequest("DELETE", url, nil) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} diff --git a/vendor/github.com/google/go-github/v53/github/event.go b/vendor/github.com/google/go-github/v53/github/event.go index 20907a9932..4ee25603a8 100644 --- a/vendor/github.com/google/go-github/v53/github/event.go +++ b/vendor/github.com/google/go-github/v53/github/event.go @@ -127,6 +127,8 @@ func (e *Event) ParsePayload() (payload interface{}, err error) { payload = &RepositoryVulnerabilityAlertEvent{} case "SecretScanningAlertEvent": payload = &SecretScanningAlertEvent{} + case "SecurityAdvisoryEvent": + payload = &SecurityAdvisoryEvent{} case "StarEvent": payload = &StarEvent{} case "StatusEvent": diff --git a/vendor/github.com/google/go-github/v53/github/github-accessors.go b/vendor/github.com/google/go-github/v53/github/github-accessors.go index a9aaee814a..8acb72b042 100644 --- a/vendor/github.com/google/go-github/v53/github/github-accessors.go +++ b/vendor/github.com/google/go-github/v53/github/github-accessors.go @@ -2222,6 +2222,22 @@ func (b *BranchProtectionRuleEvent) GetSender() *User { return b.Sender } +// GetActorID returns the ActorID field if it's non-nil, zero value otherwise. +func (b *BypassActor) GetActorID() int64 { + if b == nil || b.ActorID == nil { + return 0 + } + return *b.ActorID +} + +// GetActorType returns the ActorType field if it's non-nil, zero value otherwise. +func (b *BypassActor) GetActorType() string { + if b == nil || b.ActorType == nil { + return "" + } + return *b.ActorType +} + // GetApp returns the App field. func (c *CheckRun) GetApp() *App { if c == nil { @@ -2886,6 +2902,342 @@ func (c *CodeSearchResult) GetTotal() int { return *c.Total } +// GetBillableOwner returns the BillableOwner field. +func (c *Codespace) GetBillableOwner() *User { + if c == nil { + return nil + } + return c.BillableOwner +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (c *Codespace) GetCreatedAt() Timestamp { + if c == nil || c.CreatedAt == nil { + return Timestamp{} + } + return *c.CreatedAt +} + +// GetDevcontainerPath returns the DevcontainerPath field if it's non-nil, zero value otherwise. +func (c *Codespace) GetDevcontainerPath() string { + if c == nil || c.DevcontainerPath == nil { + return "" + } + return *c.DevcontainerPath +} + +// GetDisplayName returns the DisplayName field if it's non-nil, zero value otherwise. +func (c *Codespace) GetDisplayName() string { + if c == nil || c.DisplayName == nil { + return "" + } + return *c.DisplayName +} + +// GetEnvironmentID returns the EnvironmentID field if it's non-nil, zero value otherwise. +func (c *Codespace) GetEnvironmentID() string { + if c == nil || c.EnvironmentID == nil { + return "" + } + return *c.EnvironmentID +} + +// GetGitStatus returns the GitStatus field. +func (c *Codespace) GetGitStatus() *CodespacesGitStatus { + if c == nil { + return nil + } + return c.GitStatus +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (c *Codespace) GetID() int64 { + if c == nil || c.ID == nil { + return 0 + } + return *c.ID +} + +// GetIdleTimeoutMinutes returns the IdleTimeoutMinutes field if it's non-nil, zero value otherwise. +func (c *Codespace) GetIdleTimeoutMinutes() int { + if c == nil || c.IdleTimeoutMinutes == nil { + return 0 + } + return *c.IdleTimeoutMinutes +} + +// GetIdleTimeoutNotice returns the IdleTimeoutNotice field if it's non-nil, zero value otherwise. +func (c *Codespace) GetIdleTimeoutNotice() string { + if c == nil || c.IdleTimeoutNotice == nil { + return "" + } + return *c.IdleTimeoutNotice +} + +// GetLastKnownStopNotice returns the LastKnownStopNotice field if it's non-nil, zero value otherwise. +func (c *Codespace) GetLastKnownStopNotice() string { + if c == nil || c.LastKnownStopNotice == nil { + return "" + } + return *c.LastKnownStopNotice +} + +// GetLastUsedAt returns the LastUsedAt field if it's non-nil, zero value otherwise. +func (c *Codespace) GetLastUsedAt() Timestamp { + if c == nil || c.LastUsedAt == nil { + return Timestamp{} + } + return *c.LastUsedAt +} + +// GetLocation returns the Location field if it's non-nil, zero value otherwise. +func (c *Codespace) GetLocation() string { + if c == nil || c.Location == nil { + return "" + } + return *c.Location +} + +// GetMachine returns the Machine field. +func (c *Codespace) GetMachine() *CodespacesMachine { + if c == nil { + return nil + } + return c.Machine +} + +// GetMachinesURL returns the MachinesURL field if it's non-nil, zero value otherwise. +func (c *Codespace) GetMachinesURL() string { + if c == nil || c.MachinesURL == nil { + return "" + } + return *c.MachinesURL +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (c *Codespace) GetName() string { + if c == nil || c.Name == nil { + return "" + } + return *c.Name +} + +// GetOwner returns the Owner field. +func (c *Codespace) GetOwner() *User { + if c == nil { + return nil + } + return c.Owner +} + +// GetPendingOperation returns the PendingOperation field if it's non-nil, zero value otherwise. +func (c *Codespace) GetPendingOperation() bool { + if c == nil || c.PendingOperation == nil { + return false + } + return *c.PendingOperation +} + +// GetPendingOperationDisabledReason returns the PendingOperationDisabledReason field if it's non-nil, zero value otherwise. +func (c *Codespace) GetPendingOperationDisabledReason() string { + if c == nil || c.PendingOperationDisabledReason == nil { + return "" + } + return *c.PendingOperationDisabledReason +} + +// GetPrebuild returns the Prebuild field if it's non-nil, zero value otherwise. +func (c *Codespace) GetPrebuild() bool { + if c == nil || c.Prebuild == nil { + return false + } + return *c.Prebuild +} + +// GetPullsURL returns the PullsURL field if it's non-nil, zero value otherwise. +func (c *Codespace) GetPullsURL() string { + if c == nil || c.PullsURL == nil { + return "" + } + return *c.PullsURL +} + +// GetRepository returns the Repository field. +func (c *Codespace) GetRepository() *Repository { + if c == nil { + return nil + } + return c.Repository +} + +// GetRetentionExpiresAt returns the RetentionExpiresAt field if it's non-nil, zero value otherwise. +func (c *Codespace) GetRetentionExpiresAt() Timestamp { + if c == nil || c.RetentionExpiresAt == nil { + return Timestamp{} + } + return *c.RetentionExpiresAt +} + +// GetRetentionPeriodMinutes returns the RetentionPeriodMinutes field if it's non-nil, zero value otherwise. +func (c *Codespace) GetRetentionPeriodMinutes() int { + if c == nil || c.RetentionPeriodMinutes == nil { + return 0 + } + return *c.RetentionPeriodMinutes +} + +// GetRuntimeConstraints returns the RuntimeConstraints field. +func (c *Codespace) GetRuntimeConstraints() *CodespacesRuntimeConstraints { + if c == nil { + return nil + } + return c.RuntimeConstraints +} + +// GetStartURL returns the StartURL field if it's non-nil, zero value otherwise. +func (c *Codespace) GetStartURL() string { + if c == nil || c.StartURL == nil { + return "" + } + return *c.StartURL +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (c *Codespace) GetState() string { + if c == nil || c.State == nil { + return "" + } + return *c.State +} + +// GetStopURL returns the StopURL field if it's non-nil, zero value otherwise. +func (c *Codespace) GetStopURL() string { + if c == nil || c.StopURL == nil { + return "" + } + return *c.StopURL +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (c *Codespace) GetUpdatedAt() Timestamp { + if c == nil || c.UpdatedAt == nil { + return Timestamp{} + } + return *c.UpdatedAt +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (c *Codespace) GetURL() string { + if c == nil || c.URL == nil { + return "" + } + return *c.URL +} + +// GetWebURL returns the WebURL field if it's non-nil, zero value otherwise. +func (c *Codespace) GetWebURL() string { + if c == nil || c.WebURL == nil { + return "" + } + return *c.WebURL +} + +// GetAhead returns the Ahead field if it's non-nil, zero value otherwise. +func (c *CodespacesGitStatus) GetAhead() int { + if c == nil || c.Ahead == nil { + return 0 + } + return *c.Ahead +} + +// GetBehind returns the Behind field if it's non-nil, zero value otherwise. +func (c *CodespacesGitStatus) GetBehind() int { + if c == nil || c.Behind == nil { + return 0 + } + return *c.Behind +} + +// GetHasUncommittedChanges returns the HasUncommittedChanges field if it's non-nil, zero value otherwise. +func (c *CodespacesGitStatus) GetHasUncommittedChanges() bool { + if c == nil || c.HasUncommittedChanges == nil { + return false + } + return *c.HasUncommittedChanges +} + +// GetHasUnpushedChanges returns the HasUnpushedChanges field if it's non-nil, zero value otherwise. +func (c *CodespacesGitStatus) GetHasUnpushedChanges() bool { + if c == nil || c.HasUnpushedChanges == nil { + return false + } + return *c.HasUnpushedChanges +} + +// GetRef returns the Ref field if it's non-nil, zero value otherwise. +func (c *CodespacesGitStatus) GetRef() string { + if c == nil || c.Ref == nil { + return "" + } + return *c.Ref +} + +// GetCPUs returns the CPUs field if it's non-nil, zero value otherwise. +func (c *CodespacesMachine) GetCPUs() int { + if c == nil || c.CPUs == nil { + return 0 + } + return *c.CPUs +} + +// GetDisplayName returns the DisplayName field if it's non-nil, zero value otherwise. +func (c *CodespacesMachine) GetDisplayName() string { + if c == nil || c.DisplayName == nil { + return "" + } + return *c.DisplayName +} + +// GetMemoryInBytes returns the MemoryInBytes field if it's non-nil, zero value otherwise. +func (c *CodespacesMachine) GetMemoryInBytes() int64 { + if c == nil || c.MemoryInBytes == nil { + return 0 + } + return *c.MemoryInBytes +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (c *CodespacesMachine) GetName() string { + if c == nil || c.Name == nil { + return "" + } + return *c.Name +} + +// GetOperatingSystem returns the OperatingSystem field if it's non-nil, zero value otherwise. +func (c *CodespacesMachine) GetOperatingSystem() string { + if c == nil || c.OperatingSystem == nil { + return "" + } + return *c.OperatingSystem +} + +// GetPrebuildAvailability returns the PrebuildAvailability field if it's non-nil, zero value otherwise. +func (c *CodespacesMachine) GetPrebuildAvailability() string { + if c == nil || c.PrebuildAvailability == nil { + return "" + } + return *c.PrebuildAvailability +} + +// GetStorageInBytes returns the StorageInBytes field if it's non-nil, zero value otherwise. +func (c *CodespacesMachine) GetStorageInBytes() int64 { + if c == nil || c.StorageInBytes == nil { + return 0 + } + return *c.StorageInBytes +} + // GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. func (c *CollaboratorInvitation) GetCreatedAt() Timestamp { if c == nil || c.CreatedAt == nil { @@ -4006,6 +4358,86 @@ func (c *CreateCheckSuiteOptions) GetHeadBranch() string { return *c.HeadBranch } +// GetClientIP returns the ClientIP field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetClientIP() string { + if c == nil || c.ClientIP == nil { + return "" + } + return *c.ClientIP +} + +// GetDevcontainerPath returns the DevcontainerPath field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetDevcontainerPath() string { + if c == nil || c.DevcontainerPath == nil { + return "" + } + return *c.DevcontainerPath +} + +// GetDisplayName returns the DisplayName field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetDisplayName() string { + if c == nil || c.DisplayName == nil { + return "" + } + return *c.DisplayName +} + +// GetGeo returns the Geo field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetGeo() string { + if c == nil || c.Geo == nil { + return "" + } + return *c.Geo +} + +// GetIdleTimeoutMinutes returns the IdleTimeoutMinutes field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetIdleTimeoutMinutes() int { + if c == nil || c.IdleTimeoutMinutes == nil { + return 0 + } + return *c.IdleTimeoutMinutes +} + +// GetMachine returns the Machine field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetMachine() string { + if c == nil || c.Machine == nil { + return "" + } + return *c.Machine +} + +// GetMultiRepoPermissionsOptOut returns the MultiRepoPermissionsOptOut field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetMultiRepoPermissionsOptOut() bool { + if c == nil || c.MultiRepoPermissionsOptOut == nil { + return false + } + return *c.MultiRepoPermissionsOptOut +} + +// GetRef returns the Ref field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetRef() string { + if c == nil || c.Ref == nil { + return "" + } + return *c.Ref +} + +// GetRetentionPeriodMinutes returns the RetentionPeriodMinutes field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetRetentionPeriodMinutes() int { + if c == nil || c.RetentionPeriodMinutes == nil { + return 0 + } + return *c.RetentionPeriodMinutes +} + +// GetWorkingDirectory returns the WorkingDirectory field if it's non-nil, zero value otherwise. +func (c *CreateCodespaceOptions) GetWorkingDirectory() string { + if c == nil || c.WorkingDirectory == nil { + return "" + } + return *c.WorkingDirectory +} + // GetDescription returns the Description field if it's non-nil, zero value otherwise. func (c *CreateEvent) GetDescription() string { if c == nil || c.Description == nil { @@ -4262,6 +4694,30 @@ func (c *CustomRepoRoles) GetName() string { return *c.Name } +// GetQuerySuite returns the QuerySuite field if it's non-nil, zero value otherwise. +func (d *DefaultSetupConfiguration) GetQuerySuite() string { + if d == nil || d.QuerySuite == nil { + return "" + } + return *d.QuerySuite +} + +// GetState returns the State field if it's non-nil, zero value otherwise. +func (d *DefaultSetupConfiguration) GetState() string { + if d == nil || d.State == nil { + return "" + } + return *d.State +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (d *DefaultSetupConfiguration) GetUpdatedAt() Timestamp { + if d == nil || d.UpdatedAt == nil { + return Timestamp{} + } + return *d.UpdatedAt +} + // GetInstallation returns the Installation field. func (d *DeleteEvent) GetInstallation() *Installation { if d == nil { @@ -6310,6 +6766,14 @@ func (f *ForkEvent) GetSender() *User { return f.Sender } +// GetWorkFolder returns the WorkFolder field if it's non-nil, zero value otherwise. +func (g *GenerateJITConfigRequest) GetWorkFolder() string { + if g == nil || g.WorkFolder == nil { + return "" + } + return *g.WorkFolder +} + // GetPreviousTagName returns the PreviousTagName field if it's non-nil, zero value otherwise. func (g *GenerateNotesOptions) GetPreviousTagName() string { if g == nil || g.PreviousTagName == nil { @@ -8934,6 +9398,14 @@ func (i *IssueStats) GetTotalIssues() int { return *i.TotalIssues } +// GetEncodedJITConfig returns the EncodedJITConfig field if it's non-nil, zero value otherwise. +func (j *JITRunnerConfig) GetEncodedJITConfig() string { + if j == nil || j.EncodedJITConfig == nil { + return "" + } + return *j.EncodedJITConfig +} + // GetTotalCount returns the TotalCount field if it's non-nil, zero value otherwise. func (j *Jobs) GetTotalCount() int { if j == nil || j.TotalCount == nil { @@ -9454,6 +9926,14 @@ func (l *ListCheckSuiteResults) GetTotal() int { return *l.Total } +// GetTotalCount returns the TotalCount field if it's non-nil, zero value otherwise. +func (l *ListCodespaces) GetTotalCount() int { + if l == nil || l.TotalCount == nil { + return 0 + } + return *l.TotalCount +} + // GetAffiliation returns the Affiliation field if it's non-nil, zero value otherwise. func (l *ListCollaboratorOptions) GetAffiliation() string { if l == nil || l.Affiliation == nil { @@ -17918,6 +18398,14 @@ func (r *RepositoryRelease) GetZipballURL() string { return *r.ZipballURL } +// GetParameters returns the Parameters field if it's non-nil, zero value otherwise. +func (r *RepositoryRule) GetParameters() json.RawMessage { + if r == nil || r.Parameters == nil { + return json.RawMessage{} + } + return *r.Parameters +} + // GetCommit returns the Commit field. func (r *RepositoryTag) GetCommit() *Commit { if r == nil { @@ -18366,6 +18854,118 @@ func (r *Rule) GetSeverity() string { return *r.Severity } +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (r *RulePatternParameters) GetName() string { + if r == nil || r.Name == nil { + return "" + } + return *r.Name +} + +// GetNegate returns the Negate field if it's non-nil, zero value otherwise. +func (r *RulePatternParameters) GetNegate() bool { + if r == nil || r.Negate == nil { + return false + } + return *r.Negate +} + +// GetIntegrationID returns the IntegrationID field if it's non-nil, zero value otherwise. +func (r *RuleRequiredStatusChecks) GetIntegrationID() int64 { + if r == nil || r.IntegrationID == nil { + return 0 + } + return *r.IntegrationID +} + +// GetBypassMode returns the BypassMode field if it's non-nil, zero value otherwise. +func (r *Ruleset) GetBypassMode() string { + if r == nil || r.BypassMode == nil { + return "" + } + return *r.BypassMode +} + +// GetConditions returns the Conditions field. +func (r *Ruleset) GetConditions() *RulesetConditions { + if r == nil { + return nil + } + return r.Conditions +} + +// GetLinks returns the Links field. +func (r *Ruleset) GetLinks() *RulesetLinks { + if r == nil { + return nil + } + return r.Links +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (r *Ruleset) GetNodeID() string { + if r == nil || r.NodeID == nil { + return "" + } + return *r.NodeID +} + +// GetSourceType returns the SourceType field if it's non-nil, zero value otherwise. +func (r *Ruleset) GetSourceType() string { + if r == nil || r.SourceType == nil { + return "" + } + return *r.SourceType +} + +// GetTarget returns the Target field if it's non-nil, zero value otherwise. +func (r *Ruleset) GetTarget() string { + if r == nil || r.Target == nil { + return "" + } + return *r.Target +} + +// GetRefName returns the RefName field. +func (r *RulesetConditions) GetRefName() *RulesetRefConditionParameters { + if r == nil { + return nil + } + return r.RefName +} + +// GetRepositoryName returns the RepositoryName field. +func (r *RulesetConditions) GetRepositoryName() *RulesetRepositoryConditionParameters { + if r == nil { + return nil + } + return r.RepositoryName +} + +// GetHRef returns the HRef field if it's non-nil, zero value otherwise. +func (r *RulesetLink) GetHRef() string { + if r == nil || r.HRef == nil { + return "" + } + return *r.HRef +} + +// GetSelf returns the Self field. +func (r *RulesetLinks) GetSelf() *RulesetLink { + if r == nil { + return nil + } + return r.Self +} + +// GetProtected returns the Protected field if it's non-nil, zero value otherwise. +func (r *RulesetRepositoryConditionParameters) GetProtected() bool { + if r == nil || r.Protected == nil { + return false + } + return *r.Protected +} + // GetBusy returns the Busy field if it's non-nil, zero value otherwise. func (r *Runner) GetBusy() bool { if r == nil || r.Busy == nil { @@ -20998,6 +21598,30 @@ func (u *UpdateCheckRunOptions) GetStatus() string { return *u.Status } +// GetQuerySuite returns the QuerySuite field if it's non-nil, zero value otherwise. +func (u *UpdateDefaultSetupConfigurationOptions) GetQuerySuite() string { + if u == nil || u.QuerySuite == nil { + return "" + } + return *u.QuerySuite +} + +// GetRunID returns the RunID field if it's non-nil, zero value otherwise. +func (u *UpdateDefaultSetupConfigurationResponse) GetRunID() int64 { + if u == nil || u.RunID == nil { + return 0 + } + return *u.RunID +} + +// GetRunURL returns the RunURL field if it's non-nil, zero value otherwise. +func (u *UpdateDefaultSetupConfigurationResponse) GetRunURL() string { + if u == nil || u.RunURL == nil { + return "" + } + return *u.RunURL +} + // GetAllowsPublicRepositories returns the AllowsPublicRepositories field if it's non-nil, zero value otherwise. func (u *UpdateRunnerGroupRequest) GetAllowsPublicRepositories() bool { if u == nil || u.AllowsPublicRepositories == nil { diff --git a/vendor/github.com/google/go-github/v53/github/github.go b/vendor/github.com/google/go-github/v53/github/github.go index 7d8aef5302..34a27282f0 100644 --- a/vendor/github.com/google/go-github/v53/github/github.go +++ b/vendor/github.com/google/go-github/v53/github/github.go @@ -28,7 +28,7 @@ import ( ) const ( - Version = "v53.0.0" + Version = "v53.2.0" defaultAPIVersion = "2022-11-28" defaultBaseURL = "https://api.github.com/" @@ -187,6 +187,7 @@ type Client struct { Billing *BillingService Checks *ChecksService CodeScanning *CodeScanningService + Codespaces *CodespacesService Dependabot *DependabotService Enterprise *EnterpriseService Gists *GistsService @@ -325,6 +326,7 @@ func NewClient(httpClient *http.Client) *Client { c.Billing = (*BillingService)(&c.common) c.Checks = (*ChecksService)(&c.common) c.CodeScanning = (*CodeScanningService)(&c.common) + c.Codespaces = (*CodespacesService)(&c.common) c.Dependabot = (*DependabotService)(&c.common) c.Enterprise = (*EnterpriseService)(&c.common) c.Gists = (*GistsService)(&c.common) diff --git a/vendor/github.com/google/go-github/v53/github/messages.go b/vendor/github.com/google/go-github/v53/github/messages.go index 8547b8810f..bb5ae3f389 100644 --- a/vendor/github.com/google/go-github/v53/github/messages.go +++ b/vendor/github.com/google/go-github/v53/github/messages.go @@ -93,6 +93,7 @@ var ( "repository_vulnerability_alert": "RepositoryVulnerabilityAlertEvent", "release": "ReleaseEvent", "secret_scanning_alert": "SecretScanningAlertEvent", + "security_advisory": "SecurityAdvisoryEvent", "star": "StarEvent", "status": "StatusEvent", "team": "TeamEvent", diff --git a/vendor/github.com/google/go-github/v53/github/orgs_rules.go b/vendor/github.com/google/go-github/v53/github/orgs_rules.go new file mode 100644 index 0000000000..a3905af8fb --- /dev/null +++ b/vendor/github.com/google/go-github/v53/github/orgs_rules.go @@ -0,0 +1,105 @@ +// Copyright 2023 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// GetAllOrganizationRulesets gets all the rulesets for the specified organization. +// +// GitHub API docs: https://docs.github.com/en/rest/orgs/rules#get-all-organization-repository-rulesets +func (s *OrganizationsService) GetAllOrganizationRulesets(ctx context.Context, org string) ([]*Ruleset, *Response, error) { + u := fmt.Sprintf("orgs/%v/rulesets", org) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var rulesets []*Ruleset + resp, err := s.client.Do(ctx, req, &rulesets) + if err != nil { + return nil, resp, err + } + + return rulesets, resp, nil +} + +// CreateOrganizationRuleset creates a ruleset for the specified organization. +// +// GitHub API docs: https://docs.github.com/en/rest/orgs/rules#create-an-organization-repository-ruleset +func (s *OrganizationsService) CreateOrganizationRuleset(ctx context.Context, org string, rs *Ruleset) (*Ruleset, *Response, error) { + u := fmt.Sprintf("orgs/%v/rulesets", org) + + req, err := s.client.NewRequest("POST", u, rs) + if err != nil { + return nil, nil, err + } + + var ruleset *Ruleset + resp, err := s.client.Do(ctx, req, &ruleset) + if err != nil { + return nil, resp, err + } + + return ruleset, resp, nil +} + +// GetOrganizationRuleset gets a ruleset from the specified organization. +// +// GitHub API docs: https://docs.github.com/en/rest/orgs/rules#get-an-organization-repository-ruleset +func (s *OrganizationsService) GetOrganizationRuleset(ctx context.Context, org string, rulesetID int64) (*Ruleset, *Response, error) { + u := fmt.Sprintf("orgs/%v/rulesets/%v", org, rulesetID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var ruleset *Ruleset + resp, err := s.client.Do(ctx, req, &ruleset) + if err != nil { + return nil, resp, err + } + + return ruleset, resp, nil +} + +// UpdateOrganizationRuleset updates a ruleset from the specified organization. +// +// GitHub API docs: https://docs.github.com/en/rest/orgs/rules#update-an-organization-repository-ruleset +func (s *OrganizationsService) UpdateOrganizationRuleset(ctx context.Context, org string, rulesetID int64, rs *Ruleset) (*Ruleset, *Response, error) { + u := fmt.Sprintf("orgs/%v/rulesets/%v", org, rulesetID) + + req, err := s.client.NewRequest("PUT", u, rs) + if err != nil { + return nil, nil, err + } + + var ruleset *Ruleset + resp, err := s.client.Do(ctx, req, &ruleset) + if err != nil { + return nil, resp, err + } + + return ruleset, resp, nil +} + +// DeleteOrganizationRuleset deletes a ruleset from the specified organization. +// +// GitHub API docs: https://docs.github.com/en/rest/orgs/rules#delete-an-organization-repository-ruleset +func (s *OrganizationsService) DeleteOrganizationRuleset(ctx context.Context, org string, rulesetID int64) (*Response, error) { + u := fmt.Sprintf("orgs/%v/rulesets/%v", org, rulesetID) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/google/go-github/v53/github/repos_contents.go b/vendor/github.com/google/go-github/v53/github/repos_contents.go index be58fd52f6..874a327728 100644 --- a/vendor/github.com/google/go-github/v53/github/repos_contents.go +++ b/vendor/github.com/google/go-github/v53/github/repos_contents.go @@ -192,8 +192,15 @@ func (s *RepositoriesService) DownloadContentsWithMeta(ctx context.Context, owne // as possible, both result types will be returned but only one will contain a // value and the other will be nil. // +// Due to an auth vulnerability issue in the GitHub v3 API, ".." is not allowed +// to appear anywhere in the "path" or this method will return an error. +// // GitHub API docs: https://docs.github.com/en/rest/repos/contents#get-repository-content func (s *RepositoriesService) GetContents(ctx context.Context, owner, repo, path string, opts *RepositoryContentGetOptions) (fileContent *RepositoryContent, directoryContent []*RepositoryContent, resp *Response, err error) { + if strings.Contains(path, "..") { + return nil, nil, nil, errors.New("path must not contain '..' due to auth vulnerability issue") + } + escapedPath := (&url.URL{Path: strings.TrimSuffix(path, "/")}).String() u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, escapedPath) u, err = addOptions(u, opts) diff --git a/vendor/github.com/google/go-github/v53/github/repos_rules.go b/vendor/github.com/google/go-github/v53/github/repos_rules.go new file mode 100644 index 0000000000..9299d3e7f3 --- /dev/null +++ b/vendor/github.com/google/go-github/v53/github/repos_rules.go @@ -0,0 +1,447 @@ +// Copyright 2023 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" +) + +// BypassActor represents the bypass actors from a ruleset. +type BypassActor struct { + ActorID *int64 `json:"actor_id,omitempty"` + // Possible values for ActorType are: Team, Integration + ActorType *string `json:"actor_type,omitempty"` +} + +// RulesetLink represents a single link object from GitHub ruleset request _links. +type RulesetLink struct { + HRef *string `json:"href,omitempty"` +} + +// RulesetLinks represents the "_links" object in a Ruleset. +type RulesetLinks struct { + Self *RulesetLink `json:"self,omitempty"` +} + +// RulesetRefConditionParameters represents the conditions object for ref_names. +type RulesetRefConditionParameters struct { + Include []string `json:"include"` + Exclude []string `json:"exclude"` +} + +// RulesetRepositoryConditionParameters represents the conditions object for repository_names. +type RulesetRepositoryConditionParameters struct { + Include []string `json:"include,omitempty"` + Exclude []string `json:"exclude,omitempty"` + Protected *bool `json:"protected,omitempty"` +} + +// RulesetCondition represents the conditions object in a ruleset. +type RulesetConditions struct { + RefName *RulesetRefConditionParameters `json:"ref_name,omitempty"` + RepositoryName *RulesetRepositoryConditionParameters `json:"repository_name,omitempty"` +} + +// RulePatternParameters represents the rule pattern parameters. +type RulePatternParameters struct { + Name *string `json:"name,omitempty"` + // If Negate is true, the rule will fail if the pattern matches. + Negate *bool `json:"negate,omitempty"` + // Possible values for Operator are: starts_with, ends_with, contains, regex + Operator string `json:"operator"` + Pattern string `json:"pattern"` +} + +// UpdateAllowsFetchAndMergeRuleParameters represents the update rule parameters. +type UpdateAllowsFetchAndMergeRuleParameters struct { + UpdateAllowsFetchAndMerge bool `json:"update_allows_fetch_and_merge"` +} + +// RequiredDeploymentEnvironmentsRuleParameters represents the required_deployments rule parameters. +type RequiredDeploymentEnvironmentsRuleParameters struct { + RequiredDeploymentEnvironments []string `json:"required_deployment_environments"` +} + +// PullRequestRuleParameters represents the pull_request rule parameters. +type PullRequestRuleParameters struct { + DismissStaleReviewsOnPush bool `json:"dismiss_stale_reviews_on_push"` + RequireCodeOwnerReview bool `json:"require_code_owner_review"` + RequireLastPushApproval bool `json:"require_last_push_approval"` + RequiredApprovingReviewCount int `json:"required_approving_review_count"` + RequiredReviewThreadResolution bool `json:"required_review_thread_resolution"` +} + +// RuleRequiredStatusChecks represents the RequiredStatusChecks for the RequiredStatusChecksRuleParameters object. +type RuleRequiredStatusChecks struct { + Context string `json:"context"` + IntegrationID *int64 `json:"integration_id,omitempty"` +} + +// RequiredStatusChecksRuleParameters represents the required_status_checks rule parameters. +type RequiredStatusChecksRuleParameters struct { + RequiredStatusChecks []RuleRequiredStatusChecks `json:"required_status_checks"` + StrictRequiredStatusChecksPolicy bool `json:"strict_required_status_checks_policy"` +} + +// RepositoryRule represents a GitHub Rule. +type RepositoryRule struct { + Type string `json:"type"` + Parameters *json.RawMessage `json:"parameters,omitempty"` +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// This helps us handle the fact that RepositoryRule parameter field can be of numerous types. +func (r *RepositoryRule) UnmarshalJSON(data []byte) error { + type rule RepositoryRule + var RepositoryRule rule + if err := json.Unmarshal(data, &RepositoryRule); err != nil { + return err + } + + r.Type = RepositoryRule.Type + + switch RepositoryRule.Type { + case "creation", "deletion", "required_linear_history", "required_signatures", "non_fast_forward": + r.Parameters = nil + case "update": + params := UpdateAllowsFetchAndMergeRuleParameters{} + if err := json.Unmarshal(*RepositoryRule.Parameters, ¶ms); err != nil { + return err + } + + bytes, _ := json.Marshal(params) + rawParams := json.RawMessage(bytes) + + r.Parameters = &rawParams + case "required_deployments": + params := RequiredDeploymentEnvironmentsRuleParameters{} + if err := json.Unmarshal(*RepositoryRule.Parameters, ¶ms); err != nil { + return err + } + + bytes, _ := json.Marshal(params) + rawParams := json.RawMessage(bytes) + + r.Parameters = &rawParams + case "commit_message_pattern", "commit_author_email_pattern", "committer_email_pattern", "branch_name_pattern", "tag_name_pattern": + params := RulePatternParameters{} + if err := json.Unmarshal(*RepositoryRule.Parameters, ¶ms); err != nil { + return err + } + + bytes, _ := json.Marshal(params) + rawParams := json.RawMessage(bytes) + + r.Parameters = &rawParams + case "pull_request": + params := PullRequestRuleParameters{} + if err := json.Unmarshal(*RepositoryRule.Parameters, ¶ms); err != nil { + return err + } + + bytes, _ := json.Marshal(params) + rawParams := json.RawMessage(bytes) + + r.Parameters = &rawParams + case "required_status_checks": + params := RequiredStatusChecksRuleParameters{} + if err := json.Unmarshal(*RepositoryRule.Parameters, ¶ms); err != nil { + return err + } + + bytes, _ := json.Marshal(params) + rawParams := json.RawMessage(bytes) + + r.Parameters = &rawParams + default: + r.Type = "" + r.Parameters = nil + return fmt.Errorf("RepositoryRule.Type %T is not yet implemented, unable to unmarshal", RepositoryRule.Type) + } + + return nil +} + +// NewCreationRule creates a rule to only allow users with bypass permission to create matching refs. +func NewCreationRule() (rule *RepositoryRule) { + return &RepositoryRule{ + Type: "creation", + } +} + +// NewUpdateRule creates a rule to only allow users with bypass permission to update matching refs. +func NewUpdateRule(params *UpdateAllowsFetchAndMergeRuleParameters) (rule *RepositoryRule) { + bytes, _ := json.Marshal(params) + + rawParams := json.RawMessage(bytes) + + return &RepositoryRule{ + Type: "update", + Parameters: &rawParams, + } +} + +// NewDeletionRule creates a rule to only allow users with bypass permissions to delete matching refs. +func NewDeletionRule() (rule *RepositoryRule) { + return &RepositoryRule{ + Type: "deletion", + } +} + +// NewRequiredLinearHistoryRule creates a rule to prevent merge commits from being pushed to matching branches. +func NewRequiredLinearHistoryRule() (rule *RepositoryRule) { + return &RepositoryRule{ + Type: "required_linear_history", + } +} + +// NewRequiredDeploymentsRule creates a rule to require environments to be successfully deployed before they can be merged into the matching branches. +func NewRequiredDeploymentsRule(params *RequiredDeploymentEnvironmentsRuleParameters) (rule *RepositoryRule) { + bytes, _ := json.Marshal(params) + + rawParams := json.RawMessage(bytes) + + return &RepositoryRule{ + Type: "required_deployments", + Parameters: &rawParams, + } +} + +// NewRequiredSignaturesRule creates a rule a to require commits pushed to matching branches to have verified signatures. +func NewRequiredSignaturesRule() (rule *RepositoryRule) { + return &RepositoryRule{ + Type: "required_signatures", + } +} + +// NewPullRequestRule creates a rule to require all commits be made to a non-target branch and submitted via a pull request before they can be merged. +func NewPullRequestRule(params *PullRequestRuleParameters) (rule *RepositoryRule) { + bytes, _ := json.Marshal(params) + + rawParams := json.RawMessage(bytes) + + return &RepositoryRule{ + Type: "pull_request", + Parameters: &rawParams, + } +} + +// NewRequiredStatusChecksRule creates a rule to require which status checks must pass before branches can be merged into a branch rule. +func NewRequiredStatusChecksRule(params *RequiredStatusChecksRuleParameters) (rule *RepositoryRule) { + bytes, _ := json.Marshal(params) + + rawParams := json.RawMessage(bytes) + + return &RepositoryRule{ + Type: "required_status_checks", + Parameters: &rawParams, + } +} + +// NewNonFastForwardRule creates a rule as part to prevent users with push access from force pushing to matching branches. +func NewNonFastForwardRule() (rule *RepositoryRule) { + return &RepositoryRule{ + Type: "non_fast_forward", + } +} + +// NewCommitMessagePatternRule creates a rule to restrict commit message patterns being pushed to matching branches. +func NewCommitMessagePatternRule(params *RulePatternParameters) (rule *RepositoryRule) { + bytes, _ := json.Marshal(params) + + rawParams := json.RawMessage(bytes) + + return &RepositoryRule{ + Type: "commit_message_pattern", + Parameters: &rawParams, + } +} + +// NewCommitAuthorEmailPatternRule creates a rule to restrict commits with author email patterns being merged into matching branches. +func NewCommitAuthorEmailPatternRule(params *RulePatternParameters) (rule *RepositoryRule) { + bytes, _ := json.Marshal(params) + + rawParams := json.RawMessage(bytes) + + return &RepositoryRule{ + Type: "commit_author_email_pattern", + Parameters: &rawParams, + } +} + +// NewCommitterEmailPatternRule creates a rule to restrict commits with committer email patterns being merged into matching branches. +func NewCommitterEmailPatternRule(params *RulePatternParameters) (rule *RepositoryRule) { + bytes, _ := json.Marshal(params) + + rawParams := json.RawMessage(bytes) + + return &RepositoryRule{ + Type: "committer_email_pattern", + Parameters: &rawParams, + } +} + +// NewBranchNamePatternRule creates a rule to restrict branch patterns from being merged into matching branches. +func NewBranchNamePatternRule(params *RulePatternParameters) (rule *RepositoryRule) { + bytes, _ := json.Marshal(params) + + rawParams := json.RawMessage(bytes) + + return &RepositoryRule{ + Type: "branch_name_pattern", + Parameters: &rawParams, + } +} + +// NewTagNamePatternRule creates a rule to restrict tag patterns contained in non-target branches from being merged into matching branches. +func NewTagNamePatternRule(params *RulePatternParameters) (rule *RepositoryRule) { + bytes, _ := json.Marshal(params) + + rawParams := json.RawMessage(bytes) + + return &RepositoryRule{ + Type: "tag_name_pattern", + Parameters: &rawParams, + } +} + +// Ruleset represents a GitHub ruleset object. +type Ruleset struct { + ID int64 `json:"id"` + Name string `json:"name"` + // Possible values for Target are branch, tag + Target *string `json:"target,omitempty"` + // Possible values for SourceType are: Repository, Organization + SourceType *string `json:"source_type,omitempty"` + Source string `json:"source"` + // Possible values for Enforcement are: disabled, active, evaluate + Enforcement string `json:"enforcement"` + // Possible values for BypassMode are: none, repository, organization + BypassMode *string `json:"bypass_mode,omitempty"` + BypassActors []*BypassActor `json:"bypass_actors,omitempty"` + NodeID *string `json:"node_id,omitempty"` + Links *RulesetLinks `json:"_links,omitempty"` + Conditions *RulesetConditions `json:"conditions,omitempty"` + Rules []*RepositoryRule `json:"rules,omitempty"` +} + +// GetRulesForBranch gets all the rules that apply to the specified branch. +// +// GitHub API docs: https://docs.github.com/en/rest/repos/rules#get-rules-for-a-branch +func (s *RepositoriesService) GetRulesForBranch(ctx context.Context, owner, repo, branch string) ([]*RepositoryRule, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/rules/branches/%v", owner, repo, branch) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var rules []*RepositoryRule + resp, err := s.client.Do(ctx, req, &rules) + if err != nil { + return nil, resp, err + } + + return rules, resp, nil +} + +// GetAllRulesets gets all the rules that apply to the specified repository. +// If includesParents is true, rulesets configured at the organization level that apply to the repository will be returned. +// +// GitHub API docs: https://docs.github.com/en/rest/repos/rules#get-all-repository-rulesets +func (s *RepositoriesService) GetAllRulesets(ctx context.Context, owner, repo string, includesParents bool) ([]*Ruleset, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/rulesets?includes_parents=%v", owner, repo, includesParents) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var ruleset []*Ruleset + resp, err := s.client.Do(ctx, req, &ruleset) + if err != nil { + return nil, resp, err + } + + return ruleset, resp, nil +} + +// CreateRuleset creates a ruleset for the specified repository. +// +// GitHub API docs: https://docs.github.com/en/rest/repos/rules#create-a-repository-ruleset +func (s *RepositoriesService) CreateRuleset(ctx context.Context, owner, repo string, rs *Ruleset) (*Ruleset, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/rulesets", owner, repo) + + req, err := s.client.NewRequest("POST", u, rs) + if err != nil { + return nil, nil, err + } + + var ruleset *Ruleset + resp, err := s.client.Do(ctx, req, &ruleset) + if err != nil { + return nil, resp, err + } + + return ruleset, resp, nil +} + +// GetRuleset gets a ruleset for the specified repository. +// If includesParents is true, rulesets configured at the organization level that apply to the repository will be returned. +// +// GitHub API docs: https://docs.github.com/en/rest/repos/rules#get-a-repository-ruleset +func (s *RepositoriesService) GetRuleset(ctx context.Context, owner, repo string, rulesetID int64, includesParents bool) (*Ruleset, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/rulesets/%v?includes_parents=%v", owner, repo, rulesetID, includesParents) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var ruleset *Ruleset + resp, err := s.client.Do(ctx, req, &ruleset) + if err != nil { + return nil, resp, err + } + + return ruleset, resp, nil +} + +// UpdateRuleset updates a ruleset for the specified repository. +// +// GitHub API docs: https://docs.github.com/en/rest/repos/rules#update-a-repository-ruleset +func (s *RepositoriesService) UpdateRuleset(ctx context.Context, owner, repo string, rulesetID int64, rs *Ruleset) (*Ruleset, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/rulesets/%v", owner, repo, rulesetID) + + req, err := s.client.NewRequest("PUT", u, rs) + if err != nil { + return nil, nil, err + } + + var ruleset *Ruleset + resp, err := s.client.Do(ctx, req, &ruleset) + if err != nil { + return nil, resp, err + } + + return ruleset, resp, nil +} + +// DeleteRuleset deletes a ruleset for the specified repository. +// +// GitHub API docs: https://docs.github.com/en/rest/repos/rules#delete-a-repository-ruleset +func (s *RepositoriesService) DeleteRuleset(ctx context.Context, owner, repo string, rulesetID int64) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/rulesets/%v", owner, repo, rulesetID) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/tidwall/gjson/README.md b/vendor/github.com/tidwall/gjson/README.md index 00b4c9621b..c8db11f147 100644 --- a/vendor/github.com/tidwall/gjson/README.md +++ b/vendor/github.com/tidwall/gjson/README.md @@ -176,7 +176,7 @@ The `result.Int()` and `result.Uint()` calls are capable of reading all 64 bits, ```go result.Int() int64 // -9223372036854775808 to 9223372036854775807 -result.Uint() int64 // 0 to 18446744073709551615 +result.Uint() uint64 // 0 to 18446744073709551615 ``` ## Modifiers and path chaining diff --git a/vendor/github.com/tidwall/gjson/gjson.go b/vendor/github.com/tidwall/gjson/gjson.go index 330218d4ae..53cbd2363f 100644 --- a/vendor/github.com/tidwall/gjson/gjson.go +++ b/vendor/github.com/tidwall/gjson/gjson.go @@ -1009,8 +1009,8 @@ func parseObjectPath(path string) (r objectPathResult) { r.piped = true } else { r.path = path[i+1:] + r.more = true } - r.more = true return } else if path[i] == '|' { r.part = string(epart) diff --git a/vendor/modules.txt b/vendor/modules.txt index 1a331730d0..5b54abcf6e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -249,7 +249,7 @@ github.com/bmatcuk/doublestar/v4 # github.com/bombsimon/logrusr/v2 v2.0.1 ## explicit; go 1.13 github.com/bombsimon/logrusr/v2 -# github.com/bradleyfalzon/ghinstallation/v2 v2.5.0 +# github.com/bradleyfalzon/ghinstallation/v2 v2.6.0 ## explicit; go 1.13 github.com/bradleyfalzon/ghinstallation/v2 # github.com/caarlos0/env v3.5.0+incompatible @@ -612,7 +612,7 @@ github.com/google/go-cmp/cmp/internal/value # github.com/google/go-github v17.0.0+incompatible ## explicit github.com/google/go-github/github -# github.com/google/go-github/v53 v53.0.0 +# github.com/google/go-github/v53 v53.2.0 ## explicit; go 1.17 github.com/google/go-github/v53/github # github.com/google/go-querystring v1.1.0 @@ -958,7 +958,7 @@ github.com/syndtr/goleveldb/leveldb/opt github.com/syndtr/goleveldb/leveldb/storage github.com/syndtr/goleveldb/leveldb/table github.com/syndtr/goleveldb/leveldb/util -# github.com/tidwall/gjson v1.14.3 +# github.com/tidwall/gjson v1.14.4 ## explicit; go 1.12 github.com/tidwall/gjson # github.com/tidwall/match v1.1.1 @@ -2040,7 +2040,7 @@ k8s.io/utils/trace # mellium.im/sasl v0.3.1 ## explicit; go 1.18 mellium.im/sasl -# oras.land/oras-go/v2 v2.2.0 +# oras.land/oras-go/v2 v2.3.0 ## explicit; go 1.19 oras.land/oras-go/v2/content oras.land/oras-go/v2/errdef diff --git a/vendor/oras.land/oras-go/v2/content/graph.go b/vendor/oras.land/oras-go/v2/content/graph.go index fa2f9efe78..9ae837285e 100644 --- a/vendor/oras.land/oras-go/v2/content/graph.go +++ b/vendor/oras.land/oras-go/v2/content/graph.go @@ -75,18 +75,33 @@ func Successors(ctx context.Context, fetcher Fetcher, node ocispec.Descriptor) ( } nodes = append(nodes, manifest.Config) return append(nodes, manifest.Layers...), nil - case docker.MediaTypeManifestList, ocispec.MediaTypeImageIndex: + case docker.MediaTypeManifestList: content, err := FetchAll(ctx, fetcher, node) if err != nil { return nil, err } - // docker manifest list and oci index are equivalent for successors. + // OCI manifest index schema can be used to marshal docker manifest list var index ocispec.Index if err := json.Unmarshal(content, &index); err != nil { return nil, err } return index.Manifests, nil + case ocispec.MediaTypeImageIndex: + content, err := FetchAll(ctx, fetcher, node) + if err != nil { + return nil, err + } + + var index ocispec.Index + if err := json.Unmarshal(content, &index); err != nil { + return nil, err + } + var nodes []ocispec.Descriptor + if index.Subject != nil { + nodes = append(nodes, *index.Subject) + } + return append(nodes, index.Manifests...), nil case spec.MediaTypeArtifactManifest: content, err := FetchAll(ctx, fetcher, node) if err != nil { diff --git a/vendor/oras.land/oras-go/v2/content/reader.go b/vendor/oras.land/oras-go/v2/content/reader.go index 11d27b236e..e575378e31 100644 --- a/vendor/oras.land/oras-go/v2/content/reader.go +++ b/vendor/oras.land/oras-go/v2/content/reader.go @@ -70,7 +70,7 @@ func (vr *VerifyReader) Read(p []byte) (n int, err error) { return } -// Verify verifies the read content against the size and the digest. +// Verify checks for remaining unread content and verifies the read content against the digest func (vr *VerifyReader) Verify() error { if vr.verified { return nil @@ -120,7 +120,10 @@ func ReadAll(r io.Reader, desc ocispec.Descriptor) ([]byte, error) { buf := make([]byte, desc.Size) vr := NewVerifyReader(r, desc) - if _, err := io.ReadFull(vr, buf); err != nil { + if n, err := io.ReadFull(vr, buf); err != nil { + if errors.Is(err, io.ErrUnexpectedEOF) { + return nil, fmt.Errorf("read failed: expected content size of %d, got %d, for digest %s: %w", desc.Size, n, desc.Digest.String(), err) + } return nil, fmt.Errorf("read failed: %w", err) } if err := vr.Verify(); err != nil { diff --git a/vendor/oras.land/oras-go/v2/content/storage.go b/vendor/oras.land/oras-go/v2/content/storage.go index 971142cbf9..47c95d8769 100644 --- a/vendor/oras.land/oras-go/v2/content/storage.go +++ b/vendor/oras.land/oras-go/v2/content/storage.go @@ -31,7 +31,7 @@ type Fetcher interface { // Pusher pushes content. type Pusher interface { // Push pushes the content, matching the expected descriptor. - // Reader is perferred to Writer so that the suitable buffer size can be + // Reader is preferred to Writer so that the suitable buffer size can be // chosen by the underlying implementation. Furthermore, the implementation // can also do reflection on the Reader for more advanced I/O optimization. Push(ctx context.Context, expected ocispec.Descriptor, content io.Reader) error diff --git a/vendor/oras.land/oras-go/v2/errdef/errors.go b/vendor/oras.land/oras-go/v2/errdef/errors.go index 030360edb7..7adb44b173 100644 --- a/vendor/oras.land/oras-go/v2/errdef/errors.go +++ b/vendor/oras.land/oras-go/v2/errdef/errors.go @@ -22,6 +22,7 @@ var ( ErrAlreadyExists = errors.New("already exists") ErrInvalidDigest = errors.New("invalid digest") ErrInvalidReference = errors.New("invalid reference") + ErrInvalidMediaType = errors.New("invalid media type") ErrMissingReference = errors.New("missing reference") ErrNotFound = errors.New("not found") ErrSizeExceedsLimit = errors.New("size exceeds limit") diff --git a/vendor/oras.land/oras-go/v2/internal/spec/artifact.go b/vendor/oras.land/oras-go/v2/internal/spec/artifact.go index 8aa8e79ecc..7f801fd9ca 100644 --- a/vendor/oras.land/oras-go/v2/internal/spec/artifact.go +++ b/vendor/oras.land/oras-go/v2/internal/spec/artifact.go @@ -17,8 +17,16 @@ package spec import ocispec "github.com/opencontainers/image-spec/specs-go/v1" -// AnnotationReferrersFiltersApplied is the annotation key for the comma separated list of filters applied by the registry in the referrers listing. -const AnnotationReferrersFiltersApplied = "org.opencontainers.referrers.filtersApplied" +const ( + // AnnotationArtifactCreated is the annotation key for the date and time on which the artifact was built, conforming to RFC 3339. + AnnotationArtifactCreated = "org.opencontainers.artifact.created" + + // AnnotationArtifactDescription is the annotation key for the human readable description for the artifact. + AnnotationArtifactDescription = "org.opencontainers.artifact.description" + + // AnnotationReferrersFiltersApplied is the annotation key for the comma separated list of filters applied by the registry in the referrers listing. + AnnotationReferrersFiltersApplied = "org.opencontainers.referrers.filtersApplied" +) // MediaTypeArtifactManifest specifies the media type for a content descriptor. const MediaTypeArtifactManifest = "application/vnd.oci.artifact.manifest.v1+json" diff --git a/vendor/oras.land/oras-go/v2/registry/reference.go b/vendor/oras.land/oras-go/v2/registry/reference.go index cea579a141..7661a162bd 100644 --- a/vendor/oras.land/oras-go/v2/registry/reference.go +++ b/vendor/oras.land/oras-go/v2/registry/reference.go @@ -31,14 +31,16 @@ var ( // repository name set under OCI distribution spec is a subset of the docker // spec. For maximum compatability, the docker spec is verified client-side. // Further checks are left to the server-side. + // // References: - // - https://github.com/distribution/distribution/blob/v2.7.1/reference/regexp.go#L53 - // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#pulling-manifests + // - https://github.com/distribution/distribution/blob/v2.7.1/reference/regexp.go#L53 + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pulling-manifests repositoryRegexp = regexp.MustCompile(`^[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9]+)*(?:/[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9]+)*)*$`) // tagRegexp checks the tag name. // The docker and OCI spec have the same regular expression. - // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#pulling-manifests + // + // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pulling-manifests tagRegexp = regexp.MustCompile(`^[\w][\w.-]{0,127}$`) ) diff --git a/vendor/oras.land/oras-go/v2/registry/remote/errcode/errors.go b/vendor/oras.land/oras-go/v2/registry/remote/errcode/errors.go index cf0018a018..fb192aa8a3 100644 --- a/vendor/oras.land/oras-go/v2/registry/remote/errcode/errors.go +++ b/vendor/oras.land/oras-go/v2/registry/remote/errcode/errors.go @@ -24,7 +24,7 @@ import ( ) // References: -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#error-codes +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#error-codes // - https://docs.docker.com/registry/spec/api/#errors-2 const ( ErrorCodeBlobUnknown = "BLOB_UNKNOWN" @@ -45,7 +45,7 @@ const ( // Error represents a response inner error returned by the remote // registry. // References: -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#error-codes +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#error-codes // - https://docs.docker.com/registry/spec/api/#errors-2 type Error struct { Code string `json:"code"` @@ -73,7 +73,7 @@ func (e Error) Error() string { // Errors represents a list of response inner errors returned by the remote // server. // References: -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#error-codes +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#error-codes // - https://docs.docker.com/registry/spec/api/#errors-2 type Errors []Error diff --git a/vendor/oras.land/oras-go/v2/registry/remote/referrers.go b/vendor/oras.land/oras-go/v2/registry/remote/referrers.go index a3ed08ca5b..191db9d179 100644 --- a/vendor/oras.land/oras-go/v2/registry/remote/referrers.go +++ b/vendor/oras.land/oras-go/v2/registry/remote/referrers.go @@ -22,7 +22,6 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2/content" "oras.land/oras-go/v2/internal/descriptor" - "oras.land/oras-go/v2/internal/spec" ) // zeroDigest represents a digest that consists of zeros. zeroDigest is used @@ -103,17 +102,15 @@ func (e *ReferrersError) IsReferrersIndexDelete() bool { // buildReferrersTag builds the referrers tag for the given manifest descriptor. // Format: - -// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#unavailable-referrers-api +// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#unavailable-referrers-api func buildReferrersTag(desc ocispec.Descriptor) string { alg := desc.Digest.Algorithm().String() encoded := desc.Digest.Encoded() return alg + "-" + encoded } -// isReferrersFilterApplied checks annotations to see if requested is in the -// applied filter list. -func isReferrersFilterApplied(annotations map[string]string, requested string) bool { - applied := annotations[spec.AnnotationReferrersFiltersApplied] +// isReferrersFilterApplied checks if requsted is in the applied filter list. +func isReferrersFilterApplied(applied, requested string) bool { if applied == "" || requested == "" { return false } diff --git a/vendor/oras.land/oras-go/v2/registry/remote/registry.go b/vendor/oras.land/oras-go/v2/registry/remote/registry.go index c8c414f17f..8ae538d964 100644 --- a/vendor/oras.land/oras-go/v2/registry/remote/registry.go +++ b/vendor/oras.land/oras-go/v2/registry/remote/registry.go @@ -73,13 +73,28 @@ func (r *Registry) client() Client { return r.Client } +// do sends an HTTP request and returns an HTTP response using the HTTP client +// returned by r.client(). +func (r *Registry) do(req *http.Request) (*http.Response, error) { + if r.HandleWarning == nil { + return r.client().Do(req) + } + + resp, err := r.client().Do(req) + if err != nil { + return nil, err + } + handleWarningHeaders(resp.Header.Values(headerWarning), r.HandleWarning) + return resp, nil +} + // Ping checks whether or not the registry implement Docker Registry API V2 or // OCI Distribution Specification. // Ping can be used to check authentication when an auth client is configured. // // References: // - https://docs.docker.com/registry/spec/api/#base -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#api +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#api func (r *Registry) Ping(ctx context.Context) error { url := buildRegistryBaseURL(r.PlainHTTP, r.Reference) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) @@ -87,7 +102,7 @@ func (r *Registry) Ping(ctx context.Context) error { return err } - resp, err := r.client().Do(req) + resp, err := r.do(req) if err != nil { return err } @@ -142,7 +157,7 @@ func (r *Registry) repositories(ctx context.Context, last string, fn func(repos } req.URL.RawQuery = q.Encode() } - resp, err := r.client().Do(req) + resp, err := r.do(req) if err != nil { return "", err } diff --git a/vendor/oras.land/oras-go/v2/registry/remote/repository.go b/vendor/oras.land/oras-go/v2/registry/remote/repository.go index 32ac347d63..0f8c6acd21 100644 --- a/vendor/oras.land/oras-go/v2/registry/remote/repository.go +++ b/vendor/oras.land/oras-go/v2/registry/remote/repository.go @@ -47,11 +47,37 @@ import ( "oras.land/oras-go/v2/registry/remote/internal/errutil" ) -// dockerContentDigestHeader - The Docker-Content-Digest header, if present -// on the response, returns the canonical digest of the uploaded blob. -// See https://docs.docker.com/registry/spec/api/#digest-header -// See https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#pull -const dockerContentDigestHeader = "Docker-Content-Digest" +const ( + // headerDockerContentDigest is the "Docker-Content-Digest" header. + // If present on the response, it contains the canonical digest of the + // uploaded blob. + // + // References: + // - https://docs.docker.com/registry/spec/api/#digest-header + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pull + headerDockerContentDigest = "Docker-Content-Digest" + + // headerOCIFiltersApplied is the "OCI-Filters-Applied" header. + // If present on the response, it contains a comma-separated list of the + // applied filters. + // + // Reference: + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers + headerOCIFiltersApplied = "OCI-Filters-Applied" + + // headerOCISubject is the "OCI-Subject" header. + // If present on the response, it contains the digest of the subject, + // indicating that Referrers API is supported by the registry. + headerOCISubject = "OCI-Subject" +) + +// filterTypeArtifactType is the "artifactType" filter applied on the list of +// referrers. +// +// References: +// - Latest spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers +// - Compatible spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#listing-referrers +const filterTypeArtifactType = "artifactType" // Client is an interface for a HTTP client. type Client interface { @@ -93,7 +119,7 @@ type Repository struct { // ReferrerListPageSize specifies the page size when invoking the Referrers // API. // If zero, the page size is determined by the remote registry. - // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#listing-referrers + // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers ReferrerListPageSize int // MaxMetadataBytes specifies a limit on how many response bytes are allowed @@ -102,6 +128,25 @@ type Repository struct { // If less than or equal to zero, a default (currently 4MiB) is used. MaxMetadataBytes int64 + // SkipReferrersGC specifies whether to delete the dangling referrers + // index when referrers tag schema is utilized. + // - If false, the old referrers index will be deleted after the new one + // is successfully uploaded. + // - If true, the old referrers index is kept. + // By default, it is disabled (set to false). See also: + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#referrers-tag-schema + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pushing-manifests-with-subject + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#deleting-manifests + SkipReferrersGC bool + + // HandleWarning handles the warning returned by the remote server. + // Callers SHOULD deduplicate warnings from multiple associated responses. + // + // References: + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#warnings + // - https://www.rfc-editor.org/rfc/rfc7234#section-5.5 + HandleWarning func(warning Warning) + // NOTE: Must keep fields in sync with newRepositoryWithOptions function. // referrersState represents that if the repository supports Referrers API. @@ -145,6 +190,7 @@ func newRepositoryWithOptions(ref registry.Reference, opts *RepositoryOptions) ( Client: opts.Client, Reference: ref, PlainHTTP: opts.PlainHTTP, + SkipReferrersGC: opts.SkipReferrersGC, ManifestMediaTypes: slices.Clone(opts.ManifestMediaTypes), TagListPageSize: opts.TagListPageSize, ReferrerListPageSize: opts.ReferrerListPageSize, @@ -159,9 +205,9 @@ func newRepositoryWithOptions(ref registry.Reference, opts *RepositoryOptions) ( // SetReferrersCapability returns ErrReferrersCapabilityAlreadySet if the // Referrers API capability has been already set. // - When the capability is set to true, the Referrers() function will always -// request the Referrers API. Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#listing-referrers +// request the Referrers API. Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers // - When the capability is set to false, the Referrers() function will always -// request the Referrers Tag. Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#referrers-tag-schema +// request the Referrers Tag. Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#referrers-tag-schema // - When the capability is not set, the Referrers() function will automatically // determine which API to use. func (r *Repository) SetReferrersCapability(capable bool) error { @@ -196,6 +242,21 @@ func (r *Repository) client() Client { return r.Client } +// do sends an HTTP request and returns an HTTP response using the HTTP client +// returned by r.client(). +func (r *Repository) do(req *http.Request) (*http.Response, error) { + if r.HandleWarning == nil { + return r.client().Do(req) + } + + resp, err := r.client().Do(req) + if err != nil { + return nil, err + } + handleWarningHeaders(resp.Header.Values(headerWarning), r.HandleWarning) + return resp, nil +} + // blobStore detects the blob store for the given descriptor. func (r *Repository) blobStore(desc ocispec.Descriptor) registry.BlobStore { if isManifest(r.ManifestMediaTypes, desc) { @@ -320,7 +381,7 @@ func (r *Repository) ParseReference(reference string) (registry.Reference, error // of the Tags list. // // References: -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#content-discovery +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#content-discovery // - https://docs.docker.com/registry/spec/api/#tags func (r *Repository) Tags(ctx context.Context, last string, fn func(tags []string) error) error { ctx = registryutil.WithScopeHint(ctx, r.Reference, auth.ActionPull) @@ -353,7 +414,7 @@ func (r *Repository) tags(ctx context.Context, last string, fn func(tags []strin } req.URL.RawQuery = q.Encode() } - resp, err := r.client().Do(req) + resp, err := r.do(req) if err != nil { return "", err } @@ -379,7 +440,7 @@ func (r *Repository) tags(ctx context.Context, last string, fn func(tags []strin // Predecessors returns the descriptors of image or artifact manifests directly // referencing the given manifest descriptor. // Predecessors internally leverages Referrers. -// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#listing-referrers +// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers func (r *Repository) Predecessors(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { var res []ocispec.Descriptor if err := r.Referrers(ctx, desc, "", func(referrers []ocispec.Descriptor) error { @@ -398,7 +459,7 @@ func (r *Repository) Predecessors(ctx context.Context, desc ocispec.Descriptor) // If artifactType is not empty, only referrers of the same artifact type are // fed to fn. // -// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#listing-referrers +// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers func (r *Repository) Referrers(ctx context.Context, desc ocispec.Descriptor, artifactType string, fn func(referrers []ocispec.Descriptor) error) error { state := r.loadReferrersState() if state == referrersStateUnsupported { @@ -470,7 +531,7 @@ func (r *Repository) referrersPageByAPI(ctx context.Context, artifactType string req.URL.RawQuery = q.Encode() } - resp, err := r.client().Do(req) + resp, err := r.do(req) if err != nil { return "", err } @@ -485,10 +546,19 @@ func (r *Repository) referrersPageByAPI(ctx context.Context, artifactType string if err := json.NewDecoder(lr).Decode(&index); err != nil { return "", fmt.Errorf("%s %q: failed to decode response: %w", resp.Request.Method, resp.Request.URL, err) } + referrers := index.Manifests - if artifactType != "" && !isReferrersFilterApplied(index.Annotations, "artifactType") { - // perform client side filtering if the filter is not applied on the server side - referrers = filterReferrers(referrers, artifactType) + if artifactType != "" { + // check both filters header and filters annotations for compatibility + // latest spec for filters header: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers + // older spec for filters annotations: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#listing-referrers + filtersHeader := resp.Header.Get(headerOCIFiltersApplied) + filtersAnnotation := index.Annotations[spec.AnnotationReferrersFiltersApplied] + if !isReferrersFilterApplied(filtersHeader, filterTypeArtifactType) && + !isReferrersFilterApplied(filtersAnnotation, filterTypeArtifactType) { + // perform client side filtering if the filter is not applied on the server side + referrers = filterReferrers(referrers, artifactType) + } } if len(referrers) > 0 { if err := fn(referrers); err != nil { @@ -502,7 +572,7 @@ func (r *Repository) referrersPageByAPI(ctx context.Context, artifactType string // referencing the given manifest descriptor by requesting referrers tag. // fn is called for the referrers result. If artifactType is not empty, // only referrers of the same artifact type are fed to fn. -// reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#backwards-compatibility +// reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#backwards-compatibility func (r *Repository) referrersByTagSchema(ctx context.Context, desc ocispec.Descriptor, artifactType string, fn func(referrers []ocispec.Descriptor) error) error { referrersTag := buildReferrersTag(desc) _, referrers, err := r.referrersFromIndex(ctx, referrersTag) @@ -572,7 +642,7 @@ func (r *Repository) pingReferrers(ctx context.Context) (bool, error) { if err != nil { return false, err } - resp, err := r.client().Do(req) + resp, err := r.do(req) if err != nil { return false, err } @@ -610,7 +680,7 @@ func (r *Repository) delete(ctx context.Context, target ocispec.Descriptor, isMa return err } - resp, err := r.client().Do(req) + resp, err := r.do(req) if err != nil { return err } @@ -642,7 +712,7 @@ func (s *blobStore) Fetch(ctx context.Context, target ocispec.Descriptor) (rc io return nil, err } - resp, err := s.repo.client().Do(req) + resp, err := s.repo.do(req) if err != nil { return nil, err } @@ -689,7 +759,7 @@ func (s *blobStore) Mount(ctx context.Context, desc ocispec.Descriptor, fromRepo if err != nil { return err } - resp, err := s.repo.client().Do(req) + resp, err := s.repo.do(req) if err != nil { return err } @@ -715,7 +785,7 @@ func (s *blobStore) Mount(ctx context.Context, desc ocispec.Descriptor, fromRepo // push it. If the caller has provided a getContent function, we // can use that, otherwise pull the content from the source repository. // - // [spec]: https://github.com/opencontainers/distribution-spec/blob/main/spec.md#mounting-a-blob-from-another-repository + // [spec]: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#mounting-a-blob-from-another-repository var r io.ReadCloser if getContent != nil { @@ -746,10 +816,11 @@ func (s *blobStore) sibling(otherRepoName string) *blobStore { // Push is done by conventional 2-step monolithic upload instead of a single // `POST` request for better overall performance. It also allows early fail on // authentication errors. +// // References: -// - https://docs.docker.com/registry/spec/api/#pushing-an-image -// - https://docs.docker.com/registry/spec/api/#initiate-blob-upload -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#pushing-a-blob-monolithically +// - https://docs.docker.com/registry/spec/api/#pushing-an-image +// - https://docs.docker.com/registry/spec/api/#initiate-blob-upload +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pushing-a-blob-monolithically func (s *blobStore) Push(ctx context.Context, expected ocispec.Descriptor, content io.Reader) error { // start an upload // pushing usually requires both pull and push actions. @@ -761,7 +832,7 @@ func (s *blobStore) Push(ctx context.Context, expected ocispec.Descriptor, conte return err } - resp, err := s.repo.client().Do(req) + resp, err := s.repo.do(req) if err != nil { return err } @@ -816,7 +887,7 @@ func (s *blobStore) completePushAfterInitialPost(ctx context.Context, req *http. if auth := resp.Request.Header.Get("Authorization"); auth != "" { req.Header.Set("Authorization", auth) } - resp, err = s.repo.client().Do(req) + resp, err = s.repo.do(req) if err != nil { return err } @@ -862,7 +933,7 @@ func (s *blobStore) Resolve(ctx context.Context, reference string) (ocispec.Desc return ocispec.Descriptor{}, err } - resp, err := s.repo.client().Do(req) + resp, err := s.repo.do(req) if err != nil { return ocispec.Descriptor{}, err } @@ -897,7 +968,7 @@ func (s *blobStore) FetchReference(ctx context.Context, reference string) (desc return ocispec.Descriptor{}, nil, err } - resp, err := s.repo.client().Do(req) + resp, err := s.repo.do(req) if err != nil { return ocispec.Descriptor{}, nil, err } @@ -973,7 +1044,7 @@ func (s *manifestStore) Fetch(ctx context.Context, target ocispec.Descriptor) (r } req.Header.Set("Accept", target.MediaType) - resp, err := s.repo.client().Do(req) + resp, err := s.repo.do(req) if err != nil { return nil, err } @@ -1032,7 +1103,8 @@ func (s *manifestStore) Delete(ctx context.Context, target ocispec.Descriptor) e // deleteWithIndexing removes the manifest content identified by the descriptor, // and indexes referrers for the manifest when needed. func (s *manifestStore) deleteWithIndexing(ctx context.Context, target ocispec.Descriptor) error { - if target.MediaType == spec.MediaTypeArtifactManifest || target.MediaType == ocispec.MediaTypeImageManifest { + switch target.MediaType { + case spec.MediaTypeArtifactManifest, ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex: if state := s.repo.loadReferrersState(); state == referrersStateSupported { // referrers API is available, no client-side indexing needed return s.repo.delete(ctx, target, true) @@ -1053,9 +1125,12 @@ func (s *manifestStore) deleteWithIndexing(ctx context.Context, target ocispec.D return s.repo.delete(ctx, target, true) } -// indexReferrersForDelete indexes referrers for image or artifact manifest with -// the subject field on manifest delete. -// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#deleting-manifests +// indexReferrersForDelete indexes referrers for manifests with a subject field +// on manifest delete. +// +// References: +// - Latest spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#deleting-manifests +// - Compatible spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#deleting-manifests func (s *manifestStore) indexReferrersForDelete(ctx context.Context, desc ocispec.Descriptor, manifestJSON []byte) error { var manifest struct { Subject *ocispec.Descriptor `json:"subject"` @@ -1095,7 +1170,7 @@ func (s *manifestStore) Resolve(ctx context.Context, reference string) (ocispec. } req.Header.Set("Accept", manifestAcceptHeader(s.repo.ManifestMediaTypes)) - resp, err := s.repo.client().Do(req) + resp, err := s.repo.do(req) if err != nil { return ocispec.Descriptor{}, err } @@ -1127,7 +1202,7 @@ func (s *manifestStore) FetchReference(ctx context.Context, reference string) (d } req.Header.Set("Accept", manifestAcceptHeader(s.repo.ManifestMediaTypes)) - resp, err := s.repo.client().Do(req) + resp, err := s.repo.do(req) if err != nil { return ocispec.Descriptor{}, nil, err } @@ -1225,7 +1300,7 @@ func (s *manifestStore) push(ctx context.Context, expected ocispec.Descriptor, c return err } } - resp, err := client.Do(req) + resp, err := s.repo.do(req) if err != nil { return err } @@ -1234,14 +1309,26 @@ func (s *manifestStore) push(ctx context.Context, expected ocispec.Descriptor, c if resp.StatusCode != http.StatusCreated { return errutil.ParseErrorResponse(resp) } + s.checkOCISubjectHeader(resp) return verifyContentDigest(resp, expected.Digest) } +// checkOCISubjectHeader checks the "OCI-Subject" header in the response and +// sets referrers capability accordingly. +// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pushing-manifests-with-subject +func (s *manifestStore) checkOCISubjectHeader(resp *http.Response) { + // Referrers capability is not set to false when the subject header is not + // present, as the server may still conform to an older version of the spec + if subjectHeader := resp.Header.Get(headerOCISubject); subjectHeader != "" { + s.repo.SetReferrersCapability(true) + } +} + // pushWithIndexing pushes the manifest content matching the expected descriptor, // and indexes referrers for the manifest when needed. func (s *manifestStore) pushWithIndexing(ctx context.Context, expected ocispec.Descriptor, r io.Reader, reference string) error { switch expected.MediaType { - case spec.MediaTypeArtifactManifest, ocispec.MediaTypeImageManifest: + case spec.MediaTypeArtifactManifest, ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex: if state := s.repo.loadReferrersState(); state == referrersStateSupported { // referrers API is available, no client-side indexing needed return s.push(ctx, expected, r, reference) @@ -1257,15 +1344,22 @@ func (s *manifestStore) pushWithIndexing(ctx context.Context, expected ocispec.D if err := s.push(ctx, expected, bytes.NewReader(manifestJSON), reference); err != nil { return err } + // check referrers API availability again after push + if state := s.repo.loadReferrersState(); state == referrersStateSupported { + return nil + } return s.indexReferrersForPush(ctx, expected, manifestJSON) default: return s.push(ctx, expected, r, reference) } } -// indexReferrersForPush indexes referrers for image or artifact manifest with -// the subject field on manifest push. -// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#pushing-manifests-with-subject +// indexReferrersForPush indexes referrers for manifests with a subject field +// on manifest push. +// +// References: +// - Latest spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pushing-manifests-with-subject +// - Compatible spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#pushing-manifests-with-subject func (s *manifestStore) indexReferrersForPush(ctx context.Context, desc ocispec.Descriptor, manifestJSON []byte) error { var subject ocispec.Descriptor switch desc.MediaType { @@ -1291,7 +1385,22 @@ func (s *manifestStore) indexReferrersForPush(ctx context.Context, desc ocispec. return nil } subject = *manifest.Subject - desc.ArtifactType = manifest.Config.MediaType + desc.ArtifactType = manifest.ArtifactType + if desc.ArtifactType == "" { + desc.ArtifactType = manifest.Config.MediaType + } + desc.Annotations = manifest.Annotations + case ocispec.MediaTypeImageIndex: + var manifest ocispec.Index + if err := json.Unmarshal(manifestJSON, &manifest); err != nil { + return fmt.Errorf("failed to decode manifest: %s: %s: %w", desc.Digest, desc.MediaType, err) + } + if manifest.Subject == nil { + // no subject, no indexing needed + return nil + } + subject = *manifest.Subject + desc.ArtifactType = manifest.ArtifactType desc.Annotations = manifest.Annotations default: return nil @@ -1311,12 +1420,12 @@ func (s *manifestStore) indexReferrersForPush(ctx context.Context, desc ocispec. // updateReferrersIndex updates the referrers index for desc referencing subject // on manifest push and manifest delete. // References: -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#pushing-manifests-with-subject -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#deleting-manifests +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pushing-manifests-with-subject +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#deleting-manifests func (s *manifestStore) updateReferrersIndex(ctx context.Context, subject ocispec.Descriptor, change referrerChange) (err error) { referrersTag := buildReferrersTag(subject) - var skipDelete bool + skipDelete := s.repo.SkipReferrersGC var oldIndexDesc ocispec.Descriptor var referrers []ocispec.Descriptor prepare := func() error { @@ -1408,13 +1517,13 @@ func (s *manifestStore) generateDescriptor(resp *http.Response, ref registry.Ref // 4. Validate Server Digest (if present) var serverHeaderDigest digest.Digest - if serverHeaderDigestStr := resp.Header.Get(dockerContentDigestHeader); serverHeaderDigestStr != "" { + if serverHeaderDigestStr := resp.Header.Get(headerDockerContentDigest); serverHeaderDigestStr != "" { if serverHeaderDigest, err = digest.Parse(serverHeaderDigestStr); err != nil { return ocispec.Descriptor{}, fmt.Errorf( "%s %q: invalid response header value: `%s: %s`; %w", resp.Request.Method, resp.Request.URL, - dockerContentDigestHeader, + headerDockerContentDigest, serverHeaderDigestStr, err, ) @@ -1431,7 +1540,7 @@ func (s *manifestStore) generateDescriptor(resp *http.Response, ref registry.Ref // immediate fail return ocispec.Descriptor{}, fmt.Errorf( "HTTP %s request missing required header %q", - httpMethod, dockerContentDigestHeader, + httpMethod, headerDockerContentDigest, ) } // Otherwise, just trust the client-supplied digest @@ -1453,7 +1562,7 @@ func (s *manifestStore) generateDescriptor(resp *http.Response, ref registry.Ref return ocispec.Descriptor{}, fmt.Errorf( "%s %q: invalid response; digest mismatch in %s: received %q when expecting %q", resp.Request.Method, resp.Request.URL, - dockerContentDigestHeader, contentDigest, + headerDockerContentDigest, contentDigest, refDigest, ) } @@ -1485,7 +1594,7 @@ func calculateDigestFromResponse(resp *http.Response, maxMetadataBytes int64) (d // OCI distribution-spec states the Docker-Content-Digest header is optional. // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.0.1/spec.md#legacy-docker-support-http-headers func verifyContentDigest(resp *http.Response, expected digest.Digest) error { - digestStr := resp.Header.Get(dockerContentDigestHeader) + digestStr := resp.Header.Get(headerDockerContentDigest) if len(digestStr) == 0 { return nil @@ -1496,7 +1605,7 @@ func verifyContentDigest(resp *http.Response, expected digest.Digest) error { return fmt.Errorf( "%s %q: invalid response header: `%s: %s`", resp.Request.Method, resp.Request.URL, - dockerContentDigestHeader, digestStr, + headerDockerContentDigest, digestStr, ) } @@ -1504,7 +1613,7 @@ func verifyContentDigest(resp *http.Response, expected digest.Digest) error { return fmt.Errorf( "%s %q: invalid response; digest mismatch in %s: received %q when expecting %q", resp.Request.Method, resp.Request.URL, - dockerContentDigestHeader, contentDigest, + headerDockerContentDigest, contentDigest, expected, ) } diff --git a/vendor/oras.land/oras-go/v2/registry/remote/url.go b/vendor/oras.land/oras-go/v2/registry/remote/url.go index d3eee3eeab..74258de7ae 100644 --- a/vendor/oras.land/oras-go/v2/registry/remote/url.go +++ b/vendor/oras.land/oras-go/v2/registry/remote/url.go @@ -101,7 +101,7 @@ func buildRepositoryBlobMountURL(plainHTTP bool, ref registry.Reference, d diges // buildReferrersURL builds the URL for querying the Referrers API. // Format: :///v2//referrers/?artifactType= -// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#listing-referrers +// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers func buildReferrersURL(plainHTTP bool, ref registry.Reference, artifactType string) string { var query string if artifactType != "" { diff --git a/vendor/oras.land/oras-go/v2/registry/remote/warning.go b/vendor/oras.land/oras-go/v2/registry/remote/warning.go new file mode 100644 index 0000000000..ff8f9c0292 --- /dev/null +++ b/vendor/oras.land/oras-go/v2/registry/remote/warning.go @@ -0,0 +1,100 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package remote + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +const ( + // headerWarning is the "Warning" header. + // Reference: https://www.rfc-editor.org/rfc/rfc7234#section-5.5 + headerWarning = "Warning" + + // warnCode299 is the 299 warn-code. + // Reference: https://www.rfc-editor.org/rfc/rfc7234#section-5.5 + warnCode299 = 299 + + // warnAgentUnknown represents an unknown warn-agent. + // Reference: https://www.rfc-editor.org/rfc/rfc7234#section-5.5 + warnAgentUnknown = "-" +) + +// errUnexpectedWarningFormat is returned by parseWarningHeader when +// an unexpected warning format is encountered. +var errUnexpectedWarningFormat = errors.New("unexpected warning format") + +// WarningValue represents the value of the Warning header. +// +// References: +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#warnings +// - https://www.rfc-editor.org/rfc/rfc7234#section-5.5 +type WarningValue struct { + // Code is the warn-code. + Code int + // Agent is the warn-agent. + Agent string + // Text is the warn-text. + Text string +} + +// Warning contains the value of the warning header and may contain +// other information related to the warning. +// +// References: +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#warnings +// - https://www.rfc-editor.org/rfc/rfc7234#section-5.5 +type Warning struct { + // WarningValue is the value of the warning header. + WarningValue +} + +// parseWarningHeader parses the warning header into WarningValue. +func parseWarningHeader(header string) (WarningValue, error) { + if len(header) < 9 || !strings.HasPrefix(header, `299 - "`) || !strings.HasSuffix(header, `"`) { + // minimum header value: `299 - "x"` + return WarningValue{}, fmt.Errorf("%s: %w", header, errUnexpectedWarningFormat) + } + + // validate text only as code and agent are fixed + quotedText := header[6:] // behind `299 - `, quoted by " + text, err := strconv.Unquote(quotedText) + if err != nil { + return WarningValue{}, fmt.Errorf("%s: unexpected text: %w: %v", header, errUnexpectedWarningFormat, err) + } + + return WarningValue{ + Code: warnCode299, + Agent: warnAgentUnknown, + Text: text, + }, nil +} + +// handleWarningHeaders parses the warning headers and handles the parsed +// warnings using handleWarning. +func handleWarningHeaders(headers []string, handleWarning func(Warning)) { + for _, h := range headers { + if value, err := parseWarningHeader(h); err == nil { + // ignore warnings in unexpected formats + handleWarning(Warning{ + WarningValue: value, + }) + } + } +} diff --git a/vendor/oras.land/oras-go/v2/registry/repository.go b/vendor/oras.land/oras-go/v2/registry/repository.go index 2dd7ff994b..b75b7b8ea4 100644 --- a/vendor/oras.land/oras-go/v2/registry/repository.go +++ b/vendor/oras.land/oras-go/v2/registry/repository.go @@ -82,7 +82,7 @@ type ReferenceFetcher interface { } // ReferrerLister provides the Referrers API. -// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#listing-referrers +// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers type ReferrerLister interface { Referrers(ctx context.Context, desc ocispec.Descriptor, artifactType string, fn func(referrers []ocispec.Descriptor) error) error } @@ -93,16 +93,19 @@ type TagLister interface { // Since the returned tag list may be paginated by the underlying // implementation, a function should be passed in to process the paginated // tag list. + // // `last` argument is the `last` parameter when invoking the tags API. // If `last` is NOT empty, the entries in the response start after the // tag specified by `last`. Otherwise, the response starts from the top // of the Tags list. + // // Note: When implemented by a remote registry, the tags API is called. // However, not all registries supports pagination or conforms the // specification. + // // References: - // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#content-discovery - // - https://docs.docker.com/registry/spec/api/#tags + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#content-discovery + // - https://docs.docker.com/registry/spec/api/#tags // See also `Tags()` in this package. Tags(ctx context.Context, last string, fn func(tags []string) error) error } diff --git a/wire_gen.go b/wire_gen.go index ee50c772b8..a99ee499aa 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -239,7 +239,6 @@ func InitializeApp() (*App, error) { } appStatusRepositoryImpl := appStatus.NewAppStatusRepositoryImpl(db, sugaredLogger) environmentRepositoryImpl := repository.NewEnvironmentRepositoryImpl(db, sugaredLogger, appStatusRepositoryImpl) - clusterRepositoryImpl := repository.NewClusterRepositoryImpl(db, sugaredLogger) httpClient := util.NewHttpClient() grafanaClientConfig, err := grafana.GetGrafanaClientConfig() if err != nil { @@ -249,11 +248,6 @@ func InitializeApp() (*App, error) { attributesServiceImpl := attributes.NewAttributesServiceImpl(sugaredLogger, attributesRepositoryImpl) grafanaClientImpl := grafana.NewGrafanaClientImpl(sugaredLogger, httpClient, grafanaClientConfig, attributesServiceImpl) installedAppRepositoryImpl := repository3.NewInstalledAppRepositoryImpl(sugaredLogger, db) - runtimeConfig, err := k8s.GetRuntimeConfig() - if err != nil { - return nil, err - } - k8sServiceImpl := k8s.NewK8sUtil(sugaredLogger, runtimeConfig) connectionConfig, err := connection.GetConfig() if err != nil { return nil, err @@ -268,19 +262,17 @@ func InitializeApp() (*App, error) { return nil, err } serviceClientImpl := cluster.NewServiceClientImpl(sugaredLogger, argoCDConnectionManagerImpl) - syncMap := informer.NewGlobalMapClusterNamespace() - k8sInformerFactoryImpl := informer.NewK8sInformerFactoryImpl(sugaredLogger, syncMap, k8sServiceImpl) + gitOpsConfigRepositoryImpl := repository2.NewGitOpsConfigRepositoryImpl(sugaredLogger, db) defaultAuthPolicyRepositoryImpl := repository4.NewDefaultAuthPolicyRepositoryImpl(db, sugaredLogger) defaultAuthRoleRepositoryImpl := repository4.NewDefaultAuthRoleRepositoryImpl(db, sugaredLogger) userAuthRepositoryImpl := repository4.NewUserAuthRepositoryImpl(db, sugaredLogger, defaultAuthPolicyRepositoryImpl, defaultAuthRoleRepositoryImpl) userRepositoryImpl := repository4.NewUserRepositoryImpl(db, sugaredLogger) roleGroupRepositoryImpl := repository4.NewRoleGroupRepositoryImpl(db, sugaredLogger) - gitOpsConfigRepositoryImpl := repository2.NewGitOpsConfigRepositoryImpl(sugaredLogger, db) - clientRuntimeConfig, err := client.GetRuntimeConfig() + runtimeConfig, err := client.GetRuntimeConfig() if err != nil { return nil, err } - k8sClient, err := client.NewK8sClient(clientRuntimeConfig) + k8sClient, err := client.NewK8sClient(runtimeConfig) if err != nil { return nil, err } @@ -309,7 +301,20 @@ func InitializeApp() (*App, error) { return nil, err } gitOpsConfigReadServiceImpl := config.NewGitOpsConfigReadServiceImpl(sugaredLogger, gitOpsConfigRepositoryImpl, userServiceImpl, environmentVariables) - clusterServiceImplExtended := cluster2.NewClusterServiceImplExtended(clusterRepositoryImpl, environmentRepositoryImpl, grafanaClientImpl, sugaredLogger, installedAppRepositoryImpl, k8sServiceImpl, serviceClientImpl, k8sInformerFactoryImpl, userAuthRepositoryImpl, userRepositoryImpl, roleGroupRepositoryImpl, gitOpsConfigReadServiceImpl) + clusterRepositoryImpl := repository.NewClusterRepositoryImpl(db, sugaredLogger) + k8sRuntimeConfig, err := k8s.GetRuntimeConfig() + if err != nil { + return nil, err + } + k8sServiceImpl := k8s.NewK8sUtil(sugaredLogger, k8sRuntimeConfig) + syncMap := informer.NewGlobalMapClusterNamespace() + k8sInformerFactoryImpl := informer.NewK8sInformerFactoryImpl(sugaredLogger, syncMap, k8sServiceImpl) + cronLoggerImpl := cron.NewCronLoggerImpl(sugaredLogger) + clusterServiceImpl, err := cluster2.NewClusterServiceImpl(clusterRepositoryImpl, sugaredLogger, k8sServiceImpl, k8sInformerFactoryImpl, userAuthRepositoryImpl, userRepositoryImpl, roleGroupRepositoryImpl, environmentVariables, cronLoggerImpl) + if err != nil { + return nil, err + } + clusterServiceImplExtended := cluster2.NewClusterServiceImplExtended(environmentRepositoryImpl, grafanaClientImpl, installedAppRepositoryImpl, serviceClientImpl, gitOpsConfigReadServiceImpl, clusterServiceImpl) loginService := middleware.NewUserLogin(sessionManager, k8sClient) userAuthServiceImpl := user.NewUserAuthServiceImpl(userAuthRepositoryImpl, sessionManager, loginService, sugaredLogger, userRepositoryImpl, roleGroupRepositoryImpl, userServiceImpl) environmentServiceImpl := cluster2.NewEnvironmentServiceImpl(environmentRepositoryImpl, clusterServiceImplExtended, sugaredLogger, k8sServiceImpl, k8sInformerFactoryImpl, userAuthServiceImpl, attributesRepositoryImpl) @@ -384,14 +389,13 @@ func InitializeApp() (*App, error) { moduleServiceHelperImpl := module.NewModuleServiceHelperImpl(serverEnvConfigServerEnvConfig) moduleResourceStatusRepositoryImpl := moduleRepo.NewModuleResourceStatusRepositoryImpl(db) moduleDataStoreModuleDataStore := moduleDataStore.InitModuleDataStore() - cronLoggerImpl := cron.NewCronLoggerImpl(sugaredLogger) moduleCronServiceImpl, err := module.NewModuleCronServiceImpl(sugaredLogger, moduleEnvConfig, moduleRepositoryImpl, serverEnvConfigServerEnvConfig, helmAppServiceImpl, moduleServiceHelperImpl, moduleResourceStatusRepositoryImpl, moduleDataStoreModuleDataStore, cronLoggerImpl) if err != nil { return nil, err } scanToolMetadataRepositoryImpl := security.NewScanToolMetadataRepositoryImpl(db, sugaredLogger) moduleServiceImpl := module.NewModuleServiceImpl(sugaredLogger, serverEnvConfigServerEnvConfig, moduleRepositoryImpl, moduleActionAuditLogRepositoryImpl, helmAppServiceImpl, serverDataStoreServerDataStore, serverCacheServiceImpl, moduleCacheServiceImpl, moduleCronServiceImpl, moduleServiceHelperImpl, moduleResourceStatusRepositoryImpl, scanToolMetadataRepositoryImpl) - argoUserServiceImpl, err := argo.NewArgoUserServiceImpl(sugaredLogger, clusterServiceImplExtended, environmentVariables, runtimeConfig, argoCDConnectionManagerImpl, versionServiceImpl, k8sServiceImpl, gitOpsConfigReadServiceImpl, moduleServiceImpl) + argoUserServiceImpl, err := argo.NewArgoUserServiceImpl(sugaredLogger, clusterServiceImplExtended, environmentVariables, k8sRuntimeConfig, argoCDConnectionManagerImpl, versionServiceImpl, k8sServiceImpl, gitOpsConfigReadServiceImpl, moduleServiceImpl) if err != nil { return nil, err }