diff --git a/app/models/group.rb b/app/models/group.rb index aa37ae26a006ccd5c69d2721f447b8ac66a74e51..a2fd4d4a1f36df2d3d5b0c66bb95283c67a997be 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -898,6 +898,10 @@ def execute_hooks(data, hooks_scope) # TODO: group hooks https://gitlab.com/gitlab-org/gitlab/-/issues/216904 end + def find_or_initialize_integration(integration) + Integration.find_or_initialize_non_project_specific_integration(integration, group_id: id) + end + def execute_integrations(data, hooks_scope) integrations.public_send(hooks_scope).each do |integration| # rubocop:disable GitlabSecurity/PublicSend integration.async_execute(data) diff --git a/app/services/integrations/update_service.rb b/app/services/integrations/update_service.rb index ba8af9c5cfbca0b47404ec7c29b73e06f2627218..ba5a547814ee67a48099c3a4eafbfb5cab98168d 100644 --- a/app/services/integrations/update_service.rb +++ b/app/services/integrations/update_service.rb @@ -14,11 +14,15 @@ def initialize(current_user:, integration:, attributes:) def execute return error('Integration not found.', :not_found) unless integration - if handle_inherited_settings? - handle_inherited_settings - else - handle_default_settings - end + response = if handle_inherited_settings? + handle_inherited_settings + else + handle_default_settings + end + + PropagateIntegrationWorker.perform_async(integration.id) unless response.error? || integration.project_level? + + response end private diff --git a/doc/api/group_integrations.md b/doc/api/group_integrations.md new file mode 100644 index 0000000000000000000000000000000000000000..638b6e997ab31857a5ee0b78bd20a751ed5ccfe9 --- /dev/null +++ b/doc/api/group_integrations.md @@ -0,0 +1,1968 @@ +--- +stage: Foundations +group: Import and Integrate +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +title: Group integrations API +--- + +DETAILS: +**Tier:** Free, Premium, Ultimate +**Offering:** GitLab.com, GitLab Self-Managed, GitLab Dedicated + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/328496) in GitLab 17.9. + +Use this API to work with external services that integrate with GitLab. + +This API requires an access token with the Maintainer or Owner role. + +## List all active integrations + +Get a list of all active group integrations. The `vulnerability_events` field is only available for GitLab Enterprise Edition. + +```plaintext +GET /groups/:id/integrations +``` + +Example response: + +```json +[ + { + "id": 75, + "title": "Jenkins CI", + "slug": "jenkins", + "created_at": "2019-11-20T11:20:25.297Z", + "updated_at": "2019-11-20T12:24:37.498Z", + "active": true, + "commit_events": true, + "push_events": true, + "issues_events": true, + "alert_events": true, + "confidential_issues_events": true, + "merge_requests_events": true, + "tag_push_events": false, + "deployment_events": false, + "note_events": true, + "confidential_note_events": true, + "pipeline_events": true, + "wiki_page_events": true, + "job_events": true, + "comment_on_event_enabled": true, + "inherited": false, + "vulnerability_events": true + }, + { + "id": 76, + "title": "Alerts endpoint", + "slug": "alerts", + "created_at": "2019-11-20T11:20:25.297Z", + "updated_at": "2019-11-20T12:24:37.498Z", + "active": true, + "commit_events": true, + "push_events": true, + "issues_events": true, + "alert_events": true, + "confidential_issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "deployment_events": false, + "note_events": true, + "confidential_note_events": true, + "pipeline_events": true, + "wiki_page_events": true, + "job_events": true, + "comment_on_event_enabled": true, + "inherited": false, + "vulnerability_events": true + } +] +``` + +## Asana + +### Set up Asana + +Set up the Asana integration for a group. + +```plaintext +PUT /groups/:id/integrations/asana +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `api_key` | string | yes | User API token. The user must have access to the task. All comments are attributed to this user. | +| `restrict_to_branch` | string | no | Comma-separated list of branches to be automatically inspected. Leave blank to include all branches. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Asana + +Disable the Asana integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/asana +``` + +### Get Asana settings + +Get the Asana integration settings for a group. + +```plaintext +GET /groups/:id/integrations/asana +``` + +## Assembla + +### Set up Assembla + +Set up the Assembla integration for a group. + +```plaintext +PUT /groups/:id/integrations/assembla +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `token` | string | yes | The authentication token. | +| `subdomain` | string | no | The subdomain setting. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Assembla + +Disable the Assembla integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/assembla +``` + +### Get Assembla settings + +Get the Assembla integration settings for a group. + +```plaintext +GET /groups/:id/integrations/assembla +``` + +## Atlassian Bamboo + +### Set up Atlassian Bamboo + +Set up the Atlassian Bamboo integration for a group. + +You must configure automatic revision labeling and a repository trigger in Bamboo. + +```plaintext +PUT /groups/:id/integrations/bamboo +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `bamboo_url` | string | yes | Bamboo root URL (for example, `https://bamboo.example.com`). | +| `enable_ssl_verification` | boolean | no | Enable SSL verification. Defaults to `true` (enabled). | +| `build_key` | string | yes | Bamboo build plan key (for example, `KEY`). | +| `username` | string | yes | User with API access to the Bamboo server. | +| `password` | string | yes | Password of the user. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Atlassian Bamboo + +Disable the Atlassian Bamboo integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/bamboo +``` + +### Get Atlassian Bamboo settings + +Get the Atlassian Bamboo integration settings for a group. + +```plaintext +GET /groups/:id/integrations/bamboo +``` + +## Bugzilla + +### Set up Bugzilla + +Set up the Bugzilla integration for a group. + +```plaintext +PUT /groups/:id/integrations/bugzilla +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `new_issue_url` | string | yes | URL of the new issue. | +| `issues_url` | string | yes | URL of the issue. | +| `project_url` | string | yes | URL of the project. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Bugzilla + +Disable the Bugzilla integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/bugzilla +``` + +### Get Bugzilla settings + +Get the Bugzilla integration settings for a group. + +```plaintext +GET /groups/:id/integrations/bugzilla +``` + +## Buildkite + +### Set up Buildkite + +Set up the Buildkite integration for a group. + +```plaintext +PUT /groups/:id/integrations/buildkite +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `token` | string | yes | Buildkite project GitLab token. | +| `project_url` | string | yes | Pipeline URL (for example, `https://buildkite.com/example/pipeline`). | +| `enable_ssl_verification` | boolean | no | **Deprecated:** This parameter has no effect because SSL verification is always enabled. | +| `push_events` | boolean | no | Enable notifications for push events. | +| `merge_requests_events` | boolean | no | Enable notifications for merge request events. | +| `tag_push_events` | boolean | no | Enable notifications for tag push events. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Buildkite + +Disable the Buildkite integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/buildkite +``` + +### Get Buildkite settings + +Get the Buildkite integration settings for a group. + +```plaintext +GET /groups/:id/integrations/buildkite +``` + +## Campfire Classic + +You can integrate with Campfire Classic. However, Campfire Classic is an old product that is +[no longer sold](https://gitlab.com/gitlab-org/gitlab/-/issues/329337) by Basecamp. + +### Set up Campfire Classic + +Set up the Campfire Classic integration for a group. + +```plaintext +PUT /groups/:id/integrations/campfire +``` + +Parameters: + +| Parameter | Type | Required | Description | +|---------------|---------|----------|---------------------------------------------------------------------------------------------| +| `token` | string | yes | API authentication token from Campfire Classic. To get the token, sign in to Campfire Classic and select **My info**. | +| `subdomain` | string | no | `.campfirenow.com` subdomain when you're signed in. | +| `room` | string | no | ID portion of the Campfire Classic room URL. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Campfire Classic + +Disable the Campfire Classic integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/campfire +``` + +### Get Campfire Classic settings + +Get the Campfire Classic integration settings for a group. + +```plaintext +GET /groups/:id/integrations/campfire +``` + +## ClickUp + +### Set up ClickUp + +Set up the ClickUp integration for a group. + +```plaintext +PUT /groups/:id/integrations/clickup +``` + +Parameters: + +| Parameter | Type | Required | Description | +| ------------- | ------ | -------- | -------------- | +| `issues_url` | string | yes | URL of the issue. | +| `project_url` | string | yes | URL of the project. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable ClickUp + +Disable the ClickUp integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/clickup +``` + +### Get ClickUp settings + +Get the ClickUp integration settings for a group. + +```plaintext +GET /groups/:id/integrations/clickup +``` + +## Confluence Workspace + +### Set up Confluence Workspace + +Set up the Confluence Workspace integration for a group. + +```plaintext +PUT /groups/:id/integrations/confluence +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `confluence_url` | string | yes | URL of the Confluence Workspace hosted on `atlassian.net`. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Confluence Workspace + +Disable the Confluence Workspace integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/confluence +``` + +### Get Confluence Workspace settings + +Get the Confluence Workspace integration settings for a group. + +```plaintext +GET /groups/:id/integrations/confluence +``` + +## Custom issue tracker + +### Set up a custom issue tracker + +Set up a custom issue tracker for a group. + +```plaintext +PUT /groups/:id/integrations/custom-issue-tracker +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `new_issue_url` | string | yes | URL of the new issue. | +| `issues_url` | string | yes | URL of the issue. | +| `project_url` | string | yes | URL of the project. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable a custom issue tracker + +Disable a custom issue tracker for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/custom-issue-tracker +``` + +### Get custom issue tracker settings + +Get the custom issue tracker settings for a group. + +```plaintext +GET /groups/:id/integrations/custom-issue-tracker +``` + +## Datadog + +### Set up Datadog + +Set up the Datadog integration for a group. + +```plaintext +PUT /groups/:id/integrations/datadog +``` + +Parameters: + +| Parameter | Type | Required | Description | +|------------------------|---------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `api_key` | string | yes | API key used for authentication with Datadog. | +| `api_url` | string | no | (Advanced) The full URL for your Datadog site. | +| `datadog_env` | string | no | For self-managed deployments, set the `env%` tag for all the data sent to Datadog. | +| `datadog_service` | string | no | Tag all data from this GitLab instance in Datadog. Can be used when managing several self-managed deployments. | +| `datadog_site` | string | no | The Datadog site to send data to. To send data to the EU site, use `datadoghq.eu`. | +| `datadog_tags` | string | no | Custom tags in Datadog. Specify one tag per line in the format `key:value\nkey2:value2` | +| `archive_trace_events` | boolean | no | When enabled, job logs are collected by Datadog and displayed along with pipeline execution traces. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Datadog + +Disable the Datadog integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/datadog +``` + +### Get Datadog settings + +Get the Datadog integration settings for a group. + +```plaintext +GET /groups/:id/integrations/datadog +``` + +## Diffblue Cover + +### Set up Diffblue Cover + +Set up the Diffblue Cover integration for a group. + +```plaintext +PUT /groups/:id/integrations/diffblue-cover +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `diffblue_license_key` | string | yes | Diffblue Cover license key. | +| `diffblue_access_token_name` | string | yes | Access token name used by Diffblue Cover in pipelines. | +| `diffblue_access_token_secret` | string | yes | Access token secret used by Diffblue Cover in pipelines. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Diffblue Cover + +Disable the Diffblue Cover integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/diffblue-cover +``` + +### Get Diffblue Cover settings + +Get the Diffblue Cover integration settings for a group. + +```plaintext +GET /groups/:id/integrations/diffblue-cover +``` + +## Discord Notifications + +### Set up Discord Notifications + +Set up Discord Notifications for a group. + +```plaintext +PUT /groups/:id/integrations/discord +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `webhook` | string | yes | Discord webhook (for example, `https://discord.com/api/webhooks/...`). | +| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. | +| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. | +| `confidential_issue_channel` | string | no | The webhook override to receive notifications for confidential issue events. | +| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. | +| `confidential_note_channel` | string | no | The webhook override to receive notifications for confidential note events. | +| `deployment_events` | boolean | no | Enable notifications for deployment events. | +| `deployment_channel` | string | no | The webhook override to receive notifications for deployment events. | +| `group_confidential_mentions_events` | boolean | no | Enable notifications for group confidential mention events. | +| `group_confidential_mentions_channel` | string | no | The webhook override to receive notifications for group confidential mention events. | +| `group_mentions_events` | boolean | no | Enable notifications for group mention events. | +| `group_mentions_channel` | string | no | The webhook override to receive notifications for group mention events. | +| `issues_events` | boolean | no | Enable notifications for issue events. | +| `issue_channel` | string | no | The webhook override to receive notifications for issue events. | +| `merge_requests_events` | boolean | no | Enable notifications for merge request events. | +| `merge_request_channel` | string | no | The webhook override to receive notifications for merge request events. | +| `note_events` | boolean | no | Enable notifications for note events. | +| `note_channel` | string | no | The webhook override to receive notifications for note events. | +| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. | +| `pipeline_events` | boolean | no | Enable notifications for pipeline events. | +| `pipeline_channel` | string | no | The webhook override to receive notifications for pipeline events. | +| `push_events` | boolean | no | Enable notifications for push events. | +| `push_channel` | string | no | The webhook override to receive notifications for push events. | +| `tag_push_events` | boolean | no | Enable notifications for tag push events. | +| `tag_push_channel` | string | no | The webhook override to receive notifications for tag push events. | +| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. | +| `wiki_page_channel` | string | no | The webhook override to receive notifications for wiki page events. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Discord Notifications + +Disable Discord Notifications for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/discord +``` + +### Get Discord Notifications settings + +Get the Discord Notifications settings for a group. + +```plaintext +GET /groups/:id/integrations/discord +``` + +## Drone + +### Set up Drone + +Set up the Drone integration for a group. + +```plaintext +PUT /groups/:id/integrations/drone-ci +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `token` | string | yes | Drone CI project specific token. | +| `drone_url` | string | yes | `http://drone.example.com`. | +| `enable_ssl_verification` | boolean | no | Enable SSL verification. Defaults to `true` (enabled). | +| `push_events` | boolean | no | Enable notifications for push events. | +| `merge_requests_events` | boolean | no | Enable notifications for merge request events. | +| `tag_push_events` | boolean | no | Enable notifications for tag push events. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Drone + +Disable the Drone integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/drone-ci +``` + +### Get Drone settings + +Get the Drone integration settings for a group. + +```plaintext +GET /groups/:id/integrations/drone-ci +``` + +## Emails on push + +### Set up emails on push + +Set up the emails on push integration for a group. + +```plaintext +PUT /groups/:id/integrations/emails-on-push +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `recipients` | string | yes | Emails separated by whitespace. | +| `disable_diffs` | boolean | no | Disable code diffs. | +| `send_from_committer_email` | boolean | no | Send from committer. | +| `push_events` | boolean | no | Enable notifications for push events. | +| `tag_push_events` | boolean | no | Enable notifications for tag push events. | +| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. Notifications are always fired for tag pushes. The default value is `all`. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable emails on push + +Disable the emails on push integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/emails-on-push +``` + +### Get emails on push settings + +Get the emails on push integration settings for a group. + +```plaintext +GET /groups/:id/integrations/emails-on-push +``` + +## Engineering Workflow Management (EWM) + +### Set up EWM + +Set up the EWM integration for a group. + +```plaintext +PUT /groups/:id/integrations/ewm +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `new_issue_url` | string | yes | URL of the new issue. | +| `project_url` | string | yes | URL of the project. | +| `issues_url` | string | yes | URL of the issue. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable EWM + +Disable the EWM integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/ewm +``` + +### Get EWM settings + +Get the EWM integration settings for a group. + +```plaintext +GET /groups/:id/integrations/ewm +``` + +## External wiki + +### Set up an external wiki + +Set up an external wiki for a group. + +```plaintext +PUT /groups/:id/integrations/external-wiki +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `external_wiki_url` | string | yes | URL of the external wiki. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable an external wiki + +Disable an external wiki for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/external-wiki +``` + +### Get external wiki settings + +Get the external wiki settings for a group. + +```plaintext +GET /groups/:id/integrations/external-wiki +``` + +## GitGuardian + +DETAILS: +**Tier:** Premium, Ultimate +**Offering:** GitLab Self-Managed, GitLab Dedicated + +FLAG: +On GitLab Self-Managed, by default this feature is available. To hide the feature, ask an administrator to [disable the feature flag](../administration/feature_flags.md) named `git_guardian_integration`. +On GitLab.com, this feature is not available. On GitLab Dedicated, this feature is available. + +[GitGuardian](https://www.gitguardian.com/) is a cybersecurity service that detects sensitive data such as API keys +and passwords in source code repositories. +It scans Git repositories, alerts on policy violations, and helps organizations +fix security issues before hackers can exploit them. + +You can configure GitLab to reject commits based on GitGuardian policies. + +### Known issues + +- Pushes can be delayed or can time out. With the GitGuardian integration, pushes are sent to a third-party, and GitLab has no control over the connection with GitGuardian or the GitGuardian process. +- Due to a [GitGuardian API limitation](https://api.gitguardian.com/docs#operation/multiple_scan), the integration ignores files over the size of 1 MB. They are not scanned. +- If a pushed file has a name over 256 characters long the push won't go through. + For more information, see [GitGuardian API documentation](https://api.gitguardian.com/docs#operation/multiple_scan) . + +Troubleshooting steps on [the integration page](../user/project/integrations/git_guardian.md#troubleshooting) +show how to mitigate some of these problems. + +### Set up GitGuardian + +Set up the GitGuardian integration for a group. + +```plaintext +PUT /groups/:id/integrations/git-guardian +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- |-----------------------------------------------| +| `token` | string | yes | GitGuardian API token with `scan` scope. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable GitGuardian + +Disable the GitGuardian integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/git-guardian +``` + +### Get GitGuardian settings + +Get the GitGuardian integration settings for a group. + +```plaintext +GET /groups/:id/integrations/git-guardian +``` + +## GitHub + +DETAILS: +**Tier:** Premium, Ultimate +**Offering:** GitLab.com, GitLab Self-Managed, GitLab Dedicated + +### Set up GitHub + +Set up the GitHub integration for a group. + +```plaintext +PUT /groups/:id/integrations/github +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `token` | string | yes | GitHub API token with `repo:status` OAuth scope. | +| `repository_url` | string | yes | GitHub repository URL. | +| `static_context` | boolean | no | Append the hostname of your GitLab instance to the [status check name](../user/project/integrations/github.md#static-or-dynamic-status-check-names). | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable GitHub + +Disable the GitHub integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/github +``` + +### Get GitHub settings + +Get the GitHub integration settings for a group. + +```plaintext +GET /groups/:id/integrations/github +``` + +## GitLab for Jira Cloud app + +The GitLab for Jira Cloud app integration is enabled or disabled automatically through [group linking and unlinking in Jira](../integration/jira/connect-app.md#configure-the-gitlab-for-jira-cloud-app). You cannot enable or disable the integration with the GitLab integrations form or the API. + +### Update integration for a group + +Use this API endpoint to update an integration you create with group linking in Jira. + +```plaintext +PUT /groups/:id/integrations/jira-cloud-app +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `jira_cloud_app_service_ids` | string | no | Jira Service Management Service IDs. Use commas (`,`) to separate multiple IDs. | +| `jira_cloud_app_enable_deployment_gating` | boolean | no | Enables deployment gating for blocked GitLab deployments from Jira Service Management. | +| `jira_cloud_app_deployment_gating_environments` | string | no | The environments (production, staging, testing, or development) to enable deployment gating. Required if deployment gating is enabled. Use commas (`,`) to separate multiple environments. | + +### Get GitLab for Jira Cloud app settings + +Get the GitLab for Jira Cloud app integration settings for a group. + +```plaintext +GET /groups/:id/integrations/jira-cloud-app +``` + +## GitLab for Slack app + +### Set up GitLab for Slack app + +Update the GitLab for Slack app integration for a group. + +You cannot create a GitLab for Slack app through the API because the integration +requires an OAuth 2.0 token that you cannot get from the GitLab API alone. +Instead, you must [install the app](../user/project/integrations/gitlab_slack_application.md#install-the-gitlab-for-slack-app) from the GitLab UI. +You can then use this API endpoint to update the integration. + +```plaintext +PUT /groups/:id/integrations/gitlab-slack-application +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `channel` | string | no | Default channel to use if no other channel is configured. | +| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. | +| `notify_only_default_branch` | boolean | no | **Deprecated:** This parameter has been replaced with `branches_to_be_notified`. | +| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. | +| `alert_events` | boolean | no | Enable notifications for alert events. | +| `issues_events` | boolean | no | Enable notifications for issue events. | +| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. | +| `merge_requests_events` | boolean | no | Enable notifications for merge request events. | +| `note_events` | boolean | no | Enable notifications for note events. | +| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. | +| `deployment_events` | boolean | no | Enable notifications for deployment events. | +| `incidents_events` | boolean | no | Enable notifications for incident events. | +| `pipeline_events` | boolean | no | Enable notifications for pipeline events. | +| `push_events` | boolean | no | Enable notifications for push events. | +| `tag_push_events` | boolean | no | Enable notifications for tag push events. | +| `vulnerability_events` | boolean | no | Enable notifications for vulnerability events. | +| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. | +| `labels_to_be_notified` | string | no | Labels to send notifications for. If not set, receive notifications for all events. | +| `labels_to_be_notified_behavior` | string | no | Labels to be notified for. Valid options are `match_any` and `match_all`. Defaults to `match_any`. | +| `push_channel` | string | no | Name of the channel to receive notifications for push events. | +| `issue_channel` | string | no | Name of the channel to receive notifications for issue events. | +| `confidential_issue_channel` | string | no | Name of the channel to receive notifications for confidential issue events. | +| `merge_request_channel` | string | no | Name of the channel to receive notifications for merge request events. | +| `note_channel` | string | no | Name of the channel to receive notifications for note events. | +| `confidential_note_channel` | string | no | Name of the channel to receive notifications for confidential note events. | +| `tag_push_channel` | string | no | Name of the channel to receive notifications for tag push events. | +| `pipeline_channel` | string | no | Name of the channel to receive notifications for pipeline events. | +| `wiki_page_channel` | string | no | Name of the channel to receive notifications for wiki page events. | +| `deployment_channel` | string | no | Name of the channel to receive notifications for deployment events. | +| `incident_channel` | string | no | Name of the channel to receive notifications for incident events. | +| `vulnerability_channel` | string | no | Name of the channel to receive notifications for vulnerability events. | +| `alert_channel` | string | no | Name of the channel to receive notifications for alert events. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable GitLab for Slack app + +Disable the GitLab for Slack app integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/gitlab-slack-application +``` + +### Get GitLab for Slack app settings + +Get the GitLab for Slack app integration settings for a group. + +```plaintext +GET /groups/:id/integrations/gitlab-slack-application +``` + +## Google Chat + +### Set up Google Chat + +Set up the Google Chat integration for a group. + +```plaintext +PUT /groups/:id/integrations/hangouts-chat +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `webhook` | string | yes | The Hangouts Chat webhook (for example, `https://chat.googleapis.com/v1/spaces...`). | +| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. | +| `notify_only_default_branch` | boolean | no | **Deprecated:** This parameter has been replaced with `branches_to_be_notified`. | +| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. | +| `push_events` | boolean | no | Enable notifications for push events. | +| `issues_events` | boolean | no | Enable notifications for issue events. | +| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. | +| `merge_requests_events` | boolean | no | Enable notifications for merge request events. | +| `tag_push_events` | boolean | no | Enable notifications for tag push events. | +| `note_events` | boolean | no | Enable notifications for note events. | +| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. | +| `pipeline_events` | boolean | no | Enable notifications for pipeline events. | +| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Google Chat + +Disable the Google Chat integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/hangouts-chat +``` + +### Get Google Chat settings + +Get the Google Chat integration settings for a group. + +```plaintext +GET /groups/:id/integrations/hangouts-chat +``` + +## Google Artifact Management + +DETAILS: +**Tier:** Free, Premium, Ultimate +**Offering:** GitLab.com +**Status:** Beta + +This feature is in [beta](../policy/experiment-beta-support.md). + +### Set up Google Artifact Management + +Set up the Google Artifact Management integration for a group. + +```plaintext +PUT /groups/:id/integrations/google-cloud-platform-artifact-registry +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `artifact_registry_project_id` | string | yes | ID of the Google Cloud project. | +| `artifact_registry_location` | string | yes | Location of the Artifact Registry repository. | +| `artifact_registry_repositories` | string | yes | Repository of Artifact Registry. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Google Artifact Management + +Disable the Google Artifact Management integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/google-cloud-platform-artifact-registry +``` + +### Get Google Artifact Management settings + +Get the Google Artifact Management integration settings for a group. + +```plaintext +GET /groups/:id/integrations/google-cloud-platform-artifact-registry +``` + +## Google Cloud Identity and Access Management (IAM) + +DETAILS: +**Tier:** Free, Premium, Ultimate +**Offering:** GitLab.com +**Status:** Beta + +This feature is in [beta](../policy/experiment-beta-support.md). + +### Set up Google Cloud Identity and Access Management + +Set up the Google Cloud Identity and Access Management integration for a group. + +```plaintext +PUT /groups/:id/integrations/google-cloud-platform-workload-identity-federation +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workload_identity_federation_project_id` | string | yes | Google Cloud project ID for the Workload Identity Federation. | +| `workload_identity_federation_project_number` | integer | yes | Google Cloud project number for the Workload Identity Federation. | +| `workload_identity_pool_id` | string | yes | ID of the Workload Identity Pool. | +| `workload_identity_pool_provider_id` | string | yes | ID of the Workload Identity Pool provider. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Google Cloud Identity and Access Management + +Disable the Google Cloud Identity and Access Management integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/google-cloud-platform-workload-identity-federation +``` + +### Get Google Cloud Identity and Access Management + +Get the settings for the Google Cloud Identity and Access Management for a group. + +```plaintext +GET /groups/:id/integration/google-cloud-platform-workload-identity-federation +``` + +## Harbor + +### Set up Harbor + +Set up the Harbor integration for a group. + +```plaintext +PUT /groups/:id/integrations/harbor +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `url` | string | yes | The base URL to the Harbor instance linked to the GitLab project. For example, `https://demo.goharbor.io`. | +| `project_name` | string | yes | The name of the project in the Harbor instance. For example, `testproject`. | +| `username` | string | yes | The username created in the Harbor interface. | +| `password` | string | yes | The password of the user. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Harbor + +Disable the Harbor integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/harbor +``` + +### Get Harbor settings + +Get the Harbor integration settings for a group. + +```plaintext +GET /groups/:id/integrations/harbor +``` + +## irker (IRC gateway) + +### Set up irker + +Set up the irker integration for a group. + +```plaintext +PUT /groups/:id/integrations/irker +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `recipients` | string | yes | Recipients or channels separated by whitespaces. | +| `default_irc_uri` | string | no | `irc://irc.network.net:6697/`. | +| `server_host` | string | no | localhost. | +| `server_port` | integer | no | 6659. | +| `colorize_messages` | boolean | no | Colorize messages. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable irker + +Disable the irker integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/irker +``` + +### Get irker settings + +Get the irker integration settings for a group. + +```plaintext +GET /groups/:id/integrations/irker +``` + +## JetBrains TeamCity + +### Set up JetBrains TeamCity + +Set up the JetBrains TeamCity integration for a group. + +The build configuration in TeamCity must use the build number format `%build.vcs.number%`. +In the advanced settings for VCS root, configure monitoring for all branches so merge requests can build. + +```plaintext +PUT /groups/:id/integrations/teamcity +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `teamcity_url` | string | yes | TeamCity root URL (for example, `https://teamcity.example.com`). | +| `enable_ssl_verification` | boolean | no | Enable SSL verification. Defaults to `true` (enabled). | +| `build_type` | string | yes | Build configuration ID. | +| `username` | string | yes | A user with permissions to trigger a manual build. | +| `password` | string | yes | The password of the user. | +| `push_events` | boolean | no | Enable notifications for push events. | +| `merge_requests_events` | boolean | no | Enable notifications for merge request events. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable JetBrains TeamCity + +Disable the JetBrains TeamCity integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/teamcity +``` + +### Get JetBrains TeamCity settings + +Get the JetBrains TeamCity integration settings for a group. + +```plaintext +GET /groups/:id/integrations/teamcity +``` + +## Jira + +### Set up Jira + +Set up the Jira integration for a group. + +```plaintext +PUT /groups/:id/integrations/jira +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `url` | string | yes | The URL to the Jira project which is being linked to this GitLab project (for example, `https://jira.example.com`). | +| `api_url` | string | no | The base URL to the Jira instance API. Web URL value is used if not set (for example, `https://jira-api.example.com`). | +| `username` | string | no | The email or username to be used with Jira. For Jira Cloud use an email, for Jira Data Center and Jira Server use a username. Required when using Basic authentication (`jira_auth_type` is `0`). | +| `password` | string | yes | The Jira API token, password, or personal access token to be used with Jira. When your authentication method is basic (`jira_auth_type` is `0`), use an API token for Jira Cloud or a password for Jira Data Center or Jira Server. When your authentication method is a Jira personal access token (`jira_auth_type` is `1`), use the personal access token. | +| `active` | boolean | no | Activates or deactivates the integration. Defaults to `false` (deactivated). | +| `jira_auth_type`| integer | no | The authentication method to be used with Jira. `0` means Basic Authentication. `1` means Jira personal access token. Defaults to `0`. | +| `jira_issue_prefix` | string | no | Prefix to match Jira issue keys. | +| `jira_issue_regex` | string | no | Regular expression to match Jira issue keys. | +| `jira_issue_transition_automatic` | boolean | no | Enable [automatic issue transitions](../integration/jira/issues.md#automatic-issue-transitions). Takes precedence over `jira_issue_transition_id` if enabled. Defaults to `false`. | +| `jira_issue_transition_id` | string | no | The ID of one or more transitions for [custom issue transitions](../integration/jira/issues.md#custom-issue-transitions). Ignored if `jira_issue_transition_automatic` is enabled. Defaults to a blank string, which disables custom transitions. | +| `commit_events` | boolean | no | Enable notifications for commit events. | +| `merge_requests_events` | boolean | no | Enable notifications for merge request events. | +| `comment_on_event_enabled` | boolean | no | Enable comments in Jira issues on each GitLab event (commit or merge request). | +| `issues_enabled` | boolean | no | Enable viewing Jira issues in GitLab. | +| `project_keys` | array of strings | no | Keys of Jira projects. When `issues_enabled` is `true`, this setting specifies which Jira projects to view issues from in GitLab. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Jira + +Disable the Jira integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/jira +``` + +### Get Jira settings + +Get the Jira integration settings for a group. + +```plaintext +GET /groups/:id/integrations/jira +``` + +## Matrix notifications + +### Set up Matrix notifications + +Set up Matrix notifications for a group. + +```plaintext +PUT /groups/:id/integrations/matrix +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `hostname` | string | no | Custom hostname of the Matrix server. The default value is `https://matrix.org`. | +| `token` | string | yes | The Matrix access token (for example, `syt-zyx57W2v1u123ew11`). | +| `room` | string | yes | Unique identifier for the target room (in the format `!qPKKM111FFKKsfoCVy:matrix.org`). | +| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. | +| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. | +| `push_events` | boolean | no | Enable notifications for push events. | +| `issues_events` | boolean | no | Enable notifications for issue events. | +| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. | +| `merge_requests_events` | boolean | no | Enable notifications for merge request events. | +| `tag_push_events` | boolean | no | Enable notifications for tag push events. | +| `note_events` | boolean | no | Enable notifications for note events. | +| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. | +| `pipeline_events` | boolean | no | Enable notifications for pipeline events. | +| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Matrix notifications + +Disable Matrix notifications for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/matrix +``` + +### Get Matrix notifications settings + +Get the Matrix notifications settings for a group. + +```plaintext +GET /groups/:id/integrations/matrix +``` + +## Mattermost notifications + +### Set up Mattermost notifications + +Set up Mattermost notifications for a group. + +```plaintext +PUT /groups/:id/integrations/mattermost +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `webhook` | string | yes | Mattermost notifications webhook (for example, `http://mattermost.example.com/hooks/...`). | +| `username` | string | no | Mattermost notifications username. | +| `channel` | string | no | Default channel to use if no other channel is configured. | +| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. | +| `notify_only_default_branch` | boolean | no | **Deprecated:** This parameter has been replaced with `branches_to_be_notified`. | +| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. | +| `labels_to_be_notified` | string | no | Labels to send notifications for. Leave blank to receive notifications for all events. | +| `labels_to_be_notified_behavior` | string | no | Labels to be notified for. Valid options are `match_any` and `match_all`. The default value is `match_any`. | +| `push_events` | boolean | no | Enable notifications for push events. | +| `issues_events` | boolean | no | Enable notifications for issue events. | +| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. | +| `merge_requests_events` | boolean | no | Enable notifications for merge request events. | +| `tag_push_events` | boolean | no | Enable notifications for tag push events. | +| `note_events` | boolean | no | Enable notifications for note events. | +| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. | +| `pipeline_events` | boolean | no | Enable notifications for pipeline events. | +| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. | +| `push_channel` | string | no | The name of the channel to receive notifications for push events. | +| `issue_channel` | string | no | The name of the channel to receive notifications for issue events. | +| `confidential_issue_channel` | string | no | The name of the channel to receive notifications for confidential issue events. | +| `merge_request_channel` | string | no | The name of the channel to receive notifications for merge request events. | +| `note_channel` | string | no | The name of the channel to receive notifications for note events. | +| `confidential_note_channel` | string | no | The name of the channel to receive notifications for confidential note events. | +| `tag_push_channel` | string | no | The name of the channel to receive notifications for tag push events. | +| `pipeline_channel` | string | no | The name of the channel to receive notifications for pipeline events. | +| `wiki_page_channel` | string | no | The name of the channel to receive notifications for wiki page events. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Mattermost notifications + +Disable Mattermost notifications for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/mattermost +``` + +### Get Mattermost notifications settings + +Get the Mattermost notifications settings for a group. + +```plaintext +GET /groups/:id/integrations/mattermost +``` + +## Mattermost slash commands + +### Set up Mattermost slash commands + +Set up Mattermost slash commands for a group. + +```plaintext +PUT /groups/:id/integrations/mattermost-slash-commands +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------------- | +| `token` | string | yes | The Mattermost token. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Mattermost slash commands + +Disable Mattermost slash commands for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/mattermost-slash-commands +``` + +### Get Mattermost slash commands settings + +Get the Mattermost slash commands settings for a group. + +```plaintext +GET /groups/:id/integrations/mattermost-slash-commands +``` + +## Microsoft Teams notifications + +### Set up Microsoft Teams notifications + +Set up Microsoft Teams notifications for a group. + +```plaintext +PUT /groups/:id/integrations/microsoft-teams +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `webhook` | string | yes | The Microsoft Teams webhook (for example, `https://outlook.office.com/webhook/...`). | +| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. | +| `notify_only_default_branch` | boolean | no | **Deprecated:** This parameter has been replaced with `branches_to_be_notified`. | +| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. | +| `push_events` | boolean | no | Enable notifications for push events. | +| `issues_events` | boolean | no | Enable notifications for issue events. | +| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. | +| `merge_requests_events` | boolean | no | Enable notifications for merge request events. | +| `tag_push_events` | boolean | no | Enable notifications for tag push events. | +| `note_events` | boolean | no | Enable notifications for note events. | +| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. | +| `pipeline_events` | boolean | no | Enable notifications for pipeline events. | +| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Microsoft Teams notifications + +Disable Microsoft Teams notifications for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/microsoft-teams +``` + +### Get Microsoft Teams notifications settings + +Get the Microsoft Teams notifications settings for a group. + +```plaintext +GET /groups/:id/integrations/microsoft-teams +``` + +## Mock CI + +This integration is only available in a development environment. +For an example Mock CI server, see [`gitlab-org/gitlab-mock-ci-service`](https://gitlab.com/gitlab-org/gitlab-mock-ci-service). + +### Set up Mock CI + +Set up the Mock CI integration for a group. + +```plaintext +PUT /groups/:id/integrations/mock-ci +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `mock_service_url` | string | yes | URL of the Mock CI integration. | +| `enable_ssl_verification` | boolean | no | Enable SSL verification. Defaults to `true` (enabled). | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Mock CI + +Disable the Mock CI integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/mock-ci +``` + +### Get Mock CI settings + +Get the Mock CI integration settings for a group. + +```plaintext +GET /groups/:id/integrations/mock-ci +``` + +## Packagist + +### Set up Packagist + +Set up the Packagist integration for a group. + +```plaintext +PUT /groups/:id/integrations/packagist +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `username` | string | yes | The username of a Packagist account. | +| `token` | string | yes | API token to the Packagist server. | +| `server` | boolean | no | URL of the Packagist server. Leave blank for the default `<https://packagist.org>`. | +| `push_events` | boolean | no | Enable notifications for push events. | +| `merge_requests_events` | boolean | no | Enable notifications for merge request events. | +| `tag_push_events` | boolean | no | Enable notifications for tag push events. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Packagist + +Disable the Packagist integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/packagist +``` + +### Get Packagist settings + +Get the Packagist integration settings for a group. + +```plaintext +GET /groups/:id/integrations/packagist +``` + +## Phorge + +### Set up Phorge + +Set up the Phorge integration for a group. + +```plaintext +PUT /groups/:id/integrations/phorge +``` + +Parameters: + +| Parameter | Type | Required | Description | +|-----------------|--------|----------|-----------------------| +| `issues_url` | string | yes | URL of the issue. | +| `project_url` | string | yes | URL of the project. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Phorge + +Disable the Phorge integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/phorge +``` + +### Get Phorge settings + +Get the Phorge integration settings for a group. + +```plaintext +GET /groups/:id/integrations/phorge +``` + +## Pipeline status emails + +### Set up pipeline status emails + +Set up pipeline status emails for a group. + +```plaintext +PUT /groups/:id/integrations/pipelines-email +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `recipients` | string | yes | Comma-separated list of recipient email addresses. | +| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. | +| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. | +| `notify_only_default_branch` | boolean | no | Send notifications for the default branch. | +| `pipeline_events` | boolean | no | Enable notifications for pipeline events. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable pipeline status emails + +Disable pipeline status emails for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/pipelines-email +``` + +### Get pipeline status emails settings + +Get the pipeline status emails settings for a group. + +```plaintext +GET /groups/:id/integrations/pipelines-email +``` + +## Pivotal Tracker + +### Set up Pivotal Tracker + +Set up the Pivotal Tracker integration for a group. + +```plaintext +PUT /groups/:id/integrations/pivotaltracker +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `token` | string | yes | The Pivotal Tracker token. | +| `restrict_to_branch` | boolean | no | Comma-separated list of branches to automatically inspect. Leave blank to include all branches. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Pivotal Tracker + +Disable the Pivotal Tracker integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/pivotaltracker +``` + +### Get Pivotal Tracker settings + +Get the Pivotal Tracker integration settings for a group. + +```plaintext +GET /groups/:id/integrations/pivotaltracker +``` + +## Pumble + +### Set up Pumble + +Set up the Pumble integration for a group. + +```plaintext +PUT /groups/:id/integrations/pumble +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `webhook` | string | yes | The Pumble webhook (for example, `https://api.pumble.com/workspaces/x/...`). | +| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default is `default`. | +| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. | +| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. | +| `issues_events` | boolean | no | Enable notifications for issue events. | +| `merge_requests_events` | boolean | no | Enable notifications for merge request events. | +| `note_events` | boolean | no | Enable notifications for note events. | +| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. | +| `pipeline_events` | boolean | no | Enable notifications for pipeline events. | +| `push_events` | boolean | no | Enable notifications for push events. | +| `tag_push_events` | boolean | no | Enable notifications for tag push events. | +| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Pumble + +Disable the Pumble integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/pumble +``` + +### Get Pumble settings + +Get the Pumble integration settings for a group. + +```plaintext +GET /groups/:id/integrations/pumble +``` + +## Pushover + +### Set up Pushover + +Set up the Pushover integration for a group. + +```plaintext +PUT /groups/:id/integrations/pushover +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `api_key` | string | yes | Your application key. | +| `user_key` | string | yes | Your user key. | +| `priority` | string | yes | The priority. | +| `device` | string | no | Leave blank for all active devices. | +| `sound` | string | no | The sound of the notification. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Pushover + +Disable the Pushover integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/pushover +``` + +### Get Pushover settings + +Get the Pushover integration settings for a group. + +```plaintext +GET /groups/:id/integrations/pushover +``` + +## Redmine + +### Set up Redmine + +Set up the Redmine integration for a group. + +```plaintext +PUT /groups/:id/integrations/redmine +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `new_issue_url` | string | yes | URL of the new issue. | +| `project_url` | string | yes | URL of the project. | +| `issues_url` | string | yes | URL of the issue. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Redmine + +Disable the Redmine integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/redmine +``` + +### Get Redmine settings + +Get the Redmine integration settings for a group. + +```plaintext +GET /groups/:id/integrations/redmine +``` + +## Slack notifications + +### Set up Slack notifications + +Set up Slack notifications for a group. + +```plaintext +PUT /groups/:id/integrations/slack +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `webhook` | string | yes | Slack notifications webhook (for example, `https://hooks.slack.com/services/...`). | +| `username` | string | no | Slack notifications username. | +| `channel` | string | no | Default channel to use if no other channel is configured. | +| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. | +| `notify_only_default_branch` | boolean | no | **Deprecated:** This parameter has been replaced with `branches_to_be_notified`. | +| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. | +| `labels_to_be_notified` | string | no | Labels to send notifications for. Leave blank to receive notifications for all events. | +| `labels_to_be_notified_behavior` | string | no | Labels to be notified for. Valid options are `match_any` and `match_all`. The default value is `match_any`. | +| `alert_channel` | string | no | The name of the channel to receive notifications for alert events. | +| `alert_events` | boolean | no | Enable notifications for alert events. | +| `commit_events` | boolean | no | Enable notifications for commit events. | +| `confidential_issue_channel` | string | no | The name of the channel to receive notifications for confidential issue events. | +| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. | +| `confidential_note_channel` | string | no | The name of the channel to receive notifications for confidential note events. | +| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. | +| `deployment_channel` | string | no | The name of the channel to receive notifications for deployment events. | +| `deployment_events` | boolean | no | Enable notifications for deployment events. | +| `incident_channel` | string | no | The name of the channel to receive notifications for incident events. | +| `incidents_events` | boolean | no | Enable notifications for incident events. | +| `issue_channel` | string | no | The name of the channel to receive notifications for issue events. | +| `issues_events` | boolean | no | Enable notifications for issue events. | +| `job_events` | boolean | no | Enable notifications for job events. | +| `merge_request_channel` | string | no | The name of the channel to receive notifications for merge request events. | +| `merge_requests_events` | boolean | no | Enable notifications for merge request events. | +| `note_channel` | string | no | The name of the channel to receive notifications for note events. | +| `note_events` | boolean | no | Enable notifications for note events. | +| `pipeline_channel` | string | no | The name of the channel to receive notifications for pipeline events. | +| `pipeline_events` | boolean | no | Enable notifications for pipeline events. | +| `push_channel` | string | no | The name of the channel to receive notifications for push events. | +| `push_events` | boolean | no | Enable notifications for push events. | +| `tag_push_channel` | string | no | The name of the channel to receive notifications for tag push events. | +| `tag_push_events` | boolean | no | Enable notifications for tag push events. | +| `wiki_page_channel` | string | no | The name of the channel to receive notifications for wiki page events. | +| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Slack notifications + +Disable Slack notifications for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/slack +``` + +### Get Slack notifications settings + +Get the Slack notifications settings for a group. + +```plaintext +GET /groups/:id/integrations/slack +``` + +## Slack slash commands + +### Set up Slack slash commands + +Set up Slack slash commands for a group. + +```plaintext +PUT /groups/:id/integrations/slack-slash-commands +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `token` | string | yes | The Slack token. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Slack slash commands + +Disable Slack slash commands for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/slack-slash-commands +``` + +### Get Slack slash commands settings + +Get the Slack slash commands settings for a group. + +```plaintext +GET /groups/:id/integrations/slack-slash-commands +``` + +Example response: + +```json +{ + "id": 4, + "title": "Slack slash commands", + "slug": "slack-slash-commands", + "created_at": "2017-06-27T05:51:39-07:00", + "updated_at": "2017-06-27T05:51:39-07:00", + "active": true, + "push_events": true, + "issues_events": true, + "confidential_issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "job_events": true, + "pipeline_events": true, + "comment_on_event_enabled": false, + "inherited": false, + "properties": { + "token": "<your_access_token>" + } +} +``` + +## Squash TM + +### Set up Squash TM + +Set up the Squash TM integration settings for a group. + +```plaintext +PUT /groups/:id/integrations/squash-tm +``` + +Parameters: + +| Parameter | Type | Required | Description | +|-------------------------|--------|----------|-------------------------------| +| `url` | string | yes | URL of the Squash TM webhook. | +| `token` | string | no | Secret token. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Squash TM + +Disable the Squash TM integration for a group. Integration settings are preserved. + +```plaintext +DELETE /groups/:id/integrations/squash-tm +``` + +### Get Squash TM settings + +Get the Squash TM integration settings for a group. + +```plaintext +GET /groups/:id/integrations/squash-tm +``` + +## Telegram + +### Set up Telegram + +Set up the Telegram integration for a group. + +```plaintext +PUT /groups/:id/integrations/telegram +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `hostname` | string | no | Custom hostname of the Telegram API. The default value is `https://api.telegram.org`. | +| `token` | string | yes | The Telegram bot token (for example, `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11`). | +| `room` | string | yes | Unique identifier for the target chat or the username of the target channel (in the format `@channelusername`). | +| `thread` | integer | no | Unique identifier for the target message thread (topic in a forum supergroup). | +| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. | +| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. | +| `push_events` | boolean | yes | Enable notifications for push events. | +| `issues_events` | boolean | yes | Enable notifications for issue events. | +| `confidential_issues_events` | boolean | yes | Enable notifications for confidential issue events. | +| `merge_requests_events` | boolean | yes | Enable notifications for merge request events. | +| `tag_push_events` | boolean | yes | Enable notifications for tag push events. | +| `note_events` | boolean | yes | Enable notifications for note events. | +| `confidential_note_events` | boolean | yes | Enable notifications for confidential note events. | +| `pipeline_events` | boolean | yes | Enable notifications for pipeline events. | +| `wiki_page_events` | boolean | yes | Enable notifications for wiki page events. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Telegram + +Disable the Telegram integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/telegram +``` + +### Get Telegram settings + +Get the Telegram integration settings for a group. + +```plaintext +GET /groups/:id/integrations/telegram +``` + +## Unify Circuit + +### Set up Unify Circuit + +Set up the Unify Circuit integration for a group. + +```plaintext +PUT /groups/:id/integrations/unify-circuit +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `webhook` | string | yes | The Unify Circuit webhook (for example, `https://circuit.com/rest/v2/webhooks/incoming/...`). | +| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. | +| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. | +| `push_events` | boolean | no | Enable notifications for push events. | +| `issues_events` | boolean | no | Enable notifications for issue events. | +| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. | +| `merge_requests_events` | boolean | no | Enable notifications for merge request events. | +| `tag_push_events` | boolean | no | Enable notifications for tag push events. | +| `note_events` | boolean | no | Enable notifications for note events. | +| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. | +| `pipeline_events` | boolean | no | Enable notifications for pipeline events. | +| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Unify Circuit + +Disable the Unify Circuit integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/unify-circuit +``` + +### Get Unify Circuit settings + +Get the Unify Circuit integration settings for a group. + +```plaintext +GET /groups/:id/integrations/unify-circuit +``` + +## Webex Teams + +### Set up Webex Teams + +Set up Webex Teams for a group. + +```plaintext +PUT /groups/:id/integrations/webex-teams +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `webhook` | string | yes | The Webex Teams webhook (for example, `https://api.ciscospark.com/v1/webhooks/incoming/...`). | +| `notify_only_broken_pipelines` | boolean | no | Send notifications for broken pipelines. | +| `branches_to_be_notified` | string | no | Branches to send notifications for. Valid options are `all`, `default`, `protected`, and `default_and_protected`. The default value is `default`. | +| `push_events` | boolean | no | Enable notifications for push events. | +| `issues_events` | boolean | no | Enable notifications for issue events. | +| `confidential_issues_events` | boolean | no | Enable notifications for confidential issue events. | +| `merge_requests_events` | boolean | no | Enable notifications for merge request events. | +| `tag_push_events` | boolean | no | Enable notifications for tag push events. | +| `note_events` | boolean | no | Enable notifications for note events. | +| `confidential_note_events` | boolean | no | Enable notifications for confidential note events. | +| `pipeline_events` | boolean | no | Enable notifications for pipeline events. | +| `wiki_page_events` | boolean | no | Enable notifications for wiki page events. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable Webex Teams + +Disable Webex Teams for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/webex-teams +``` + +### Get Webex Teams settings + +Get the Webex Teams settings for a group. + +```plaintext +GET /groups/:id/integrations/webex-teams +``` + +## YouTrack + +### Set up YouTrack + +Set up the YouTrack integration for a group. + +```plaintext +PUT /groups/:id/integrations/youtrack +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `issues_url` | string | yes | URL of the issue. | +| `project_url` | string | yes | URL of the project. | +| `use_inherited_settings` | boolean | no | Indicates whether or not to inherit default settings. Defaults to `false`. | + +### Disable YouTrack + +Disable the YouTrack integration for a group. Integration settings are reset. + +```plaintext +DELETE /groups/:id/integrations/youtrack +``` + +### Get YouTrack settings + +Get the YouTrack integration settings for a group. + +```plaintext +GET /groups/:id/integrations/youtrack +``` diff --git a/doc/api/integrations.md b/doc/api/integrations.md index 58338c8d00969c304bac20e9a00d8f2b7390e3b6..3f4e7b2eee774e26b29035d68020604593fa596d 100644 --- a/doc/api/integrations.md +++ b/doc/api/integrations.md @@ -2,14 +2,14 @@ stage: Foundations group: Import and Integrate info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments -title: Integrations API +title: Project integrations API --- DETAILS: **Tier:** Free, Premium, Ultimate **Offering:** GitLab.com, GitLab Self-Managed, GitLab Dedicated -This API enables you to work with external services that integrate with GitLab. +Use this API to work with external services that integrate with GitLab. This API requires an access token with the Maintainer or Owner role. diff --git a/doc/development/integrations/_index.md b/doc/development/integrations/_index.md index c6435ecc2ab0e24fb24cb021213a82f100a97faa..69ce8123b7579d0442e89b7954fbd1364c9289a1 100644 --- a/doc/development/integrations/_index.md +++ b/doc/development/integrations/_index.md @@ -360,7 +360,7 @@ To expose the integration in the [REST API](../../api/integrations.md): 'foo-bar' => ::Integrations::FooBar.api_arguments ``` -1. Update the reference documentation in `doc/api/integrations.md`, add a new section for your integration, and document all properties. +1. Update the reference documentation in `doc/api/integrations.md` and `doc/api/group_integrations.md`, add a new section for your integration, and document all properties. You can also refer to our [REST API style guide](../api_styleguide.md). @@ -450,8 +450,8 @@ In the major milestone of intended removal (M.0), disable the integration and de - Remove the integration from `Integration::INTEGRATION_NAMES`. - Delete the integration model's `#execute` and `#test` methods (if defined), but keep the model. - Add a post-migration to delete the integration records from PostgreSQL (see [example merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114721)). -- [Mark the integration documentation as removed](../documentation/styleguide/deprecations_and_removals.md#remove-a-page). -- [Update the integration API documentation](../../api/integrations.md). +- [Mark the integration documentation as removed](../../development/documentation/styleguide/deprecations_and_removals.md#remove-a-page). +- Update the [project](../../api/integrations.md) and [group](../../api/group_integrations.md) integrations API pages. In the next minor release (M.1): diff --git a/ee/lib/ee/api/entities/project_integration_basic.rb b/ee/lib/ee/api/entities/integration_basic.rb similarity index 87% rename from ee/lib/ee/api/entities/project_integration_basic.rb rename to ee/lib/ee/api/entities/integration_basic.rb index 6824962fc0080a4baa6872bcf7e8e6c8fc8edaa0..86567bfbce53b82c17a700d5cc356b820d4aec55 100644 --- a/ee/lib/ee/api/entities/project_integration_basic.rb +++ b/ee/lib/ee/api/entities/integration_basic.rb @@ -3,7 +3,7 @@ module EE module API module Entities - module ProjectIntegrationBasic + module IntegrationBasic extend ActiveSupport::Concern prepended do diff --git a/ee/spec/lib/ee/api/entities/project_integration_basic_spec.rb b/ee/spec/lib/ee/api/entities/integration_basic_spec.rb similarity index 67% rename from ee/spec/lib/ee/api/entities/project_integration_basic_spec.rb rename to ee/spec/lib/ee/api/entities/integration_basic_spec.rb index 3ef177f380549bc75ec97dc539f4e22dd8d036be..c52e6cfc1e67d4ed5ecac49bc4da7df8a7d201d3 100644 --- a/ee/spec/lib/ee/api/entities/project_integration_basic_spec.rb +++ b/ee/spec/lib/ee/api/entities/integration_basic_spec.rb @@ -2,11 +2,11 @@ require 'spec_helper' -RSpec.describe ::EE::API::Entities::ProjectIntegrationBasic, feature_category: :integrations do +RSpec.describe ::EE::API::Entities::IntegrationBasic, feature_category: :integrations do let_it_be(:integration) { create(:jenkins_integration) } let(:entity) do - API::Entities::ProjectIntegrationBasic.new(integration) + API::Entities::IntegrationBasic.new(integration) end subject(:representation) { entity.as_json } diff --git a/ee/spec/requests/api/integrations_spec.rb b/ee/spec/requests/api/integrations_spec.rb index ff0e660075e4d9fcd5fbbe1d9294374ccc375447..ee504f3b3623024a76e9e3ee178459bc00cec1cf 100644 --- a/ee/spec/requests/api/integrations_spec.rb +++ b/ee/spec/requests/api/integrations_spec.rb @@ -96,19 +96,31 @@ def stub_allow_list_license(allowed:) integration = params[:integration] describe "PUT /projects/:id/#{endpoint}/#{integration.dasherize}" do - it_behaves_like 'set up an integration', endpoint: endpoint, integration: integration do + it_behaves_like 'set up an integration', endpoint: endpoint, integration: integration, + parent_resource_name: 'project' do + let(:parent_resource) { project } + let(:integrations_map) { project_integrations_map } + it_behaves_like 'observes allow list settings', allowed_status: :ok, blocked_status: :bad_request end end describe "DELETE /projects/:id/#{endpoint}/#{integration.dasherize}" do - it_behaves_like 'disable an integration', endpoint: endpoint, integration: integration do + it_behaves_like 'disable an integration', endpoint: endpoint, integration: integration, + parent_resource_name: 'project' do + let(:parent_resource) { project } + let(:integrations_map) { project_integrations_map } + it_behaves_like 'observes allow list settings', allowed_status: :no_content, blocked_status: :not_found end end describe "GET /projects/:id/#{endpoint}/#{integration.dasherize}" do - it_behaves_like 'get an integration settings', endpoint: endpoint, integration: integration do + it_behaves_like 'get an integration settings', endpoint: endpoint, integration: integration, + parent_resource_name: 'project' do + let(:parent_resource) { project } + let(:integrations_map) { project_integrations_map } + it_behaves_like 'observes allow list settings', allowed_status: :ok, blocked_status: :not_found end end diff --git a/lib/api/entities/project_integration.rb b/lib/api/entities/integration.rb similarity index 64% rename from lib/api/entities/project_integration.rb rename to lib/api/entities/integration.rb index f4709ce6dabbe90676cc00c5c0e34ebdd5f62511..48ade64963ca7ed1f3907ed12e09d7959b1acbba 100644 --- a/lib/api/entities/project_integration.rb +++ b/lib/api/entities/integration.rb @@ -2,11 +2,11 @@ module API module Entities - class ProjectIntegration < Entities::ProjectIntegrationBasic + class Integration < Entities::IntegrationBasic # Expose serialized properties - expose :properties, documentation: { type: 'Hash', example: { "token" => "secr3t" } } do |integration, options| + expose :properties, documentation: { type: 'Hash', example: { "token" => "secr3t" } } do |integration, _| integration.api_field_names.index_with do |name| - integration.public_send(name) # rubocop:disable GitlabSecurity/PublicSend + integration.public_send(name) # rubocop:disable GitlabSecurity/PublicSend -- we're exposing the fields that are allowed to be exposed end end end diff --git a/lib/api/entities/project_integration_basic.rb b/lib/api/entities/integration_basic.rb similarity index 94% rename from lib/api/entities/project_integration_basic.rb rename to lib/api/entities/integration_basic.rb index e57263fafa27f19fc397cbdf8a283b59575ffe35..633687c0a6b9591de514f3ea466101b8cb80eb8d 100644 --- a/lib/api/entities/project_integration_basic.rb +++ b/lib/api/entities/integration_basic.rb @@ -2,7 +2,7 @@ module API module Entities - class ProjectIntegrationBasic < Grape::Entity + class IntegrationBasic < Grape::Entity expose :id, documentation: { type: 'integer', example: 75 } expose :title, documentation: { type: 'string', example: 'Jenkins CI' } expose :slug, documentation: { type: 'integer', example: 'jenkins' } do |integration| @@ -33,4 +33,4 @@ class ProjectIntegrationBasic < Grape::Entity end end -API::Entities::ProjectIntegrationBasic.prepend_mod +API::Entities::IntegrationBasic.prepend_mod diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index b32d7d89e364acfec6ceefee7f32207ca5886cca..74a9120a4774ec890573d811d180d615d8270c79 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -399,10 +399,14 @@ def authorize_admin_project authorize! :admin_project, user_project end - def authorize_admin_integrations + def authorize_admin_project_integrations authorize! :admin_integrations, user_project end + def authorize_admin_group_integrations + authorize! :admin_integrations, user_group + end + def authorize_admin_group authorize! :admin_group, user_group end diff --git a/lib/api/integrations.rb b/lib/api/integrations.rb index d5284c56d3d91f41bf00966dcd388b9c1abeb5fe..c32f77983f15c61e18d940fe2333b5abf68173a4 100644 --- a/lib/api/integrations.rb +++ b/lib/api/integrations.rb @@ -45,7 +45,7 @@ def integration_attributes(integration) end # The API officially documents only the `:id/integrations` API paths. - # We support the older `id:/services` path for backwards-compatibility in API V4. + # We support the older `id:/services` path for project integrations for backwards-compatibility in API V4. # The support for `:id/services` can be dropped if we create an API V5. [':id/services', ':id/integrations'].each do |path| params do @@ -53,134 +53,15 @@ def integration_attributes(integration) end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before { authenticate! } - before { authorize_admin_integrations } - - desc 'List all active integrations' do - detail 'Get a list of all active project integrations.' - success Entities::ProjectIntegrationBasic - failure [ - { code: 401, message: 'Unauthorized' }, - { code: 404, message: 'Not found' } - ] - is_array true - tags INTEGRATIONS_TAGS - end - get path do - integrations = user_project.integrations.active - - present integrations, with: Entities::ProjectIntegrationBasic - end - - INTEGRATIONS.each do |slug, settings| - desc "Create/Edit #{slug.titleize} integration" do - detail "Set #{slug.titleize} integration for a project." - success Entities::ProjectIntegrationBasic - failure [ - { code: 400, message: 'Bad request' }, - { code: 401, message: 'Unauthorized' }, - { code: 404, message: 'Not found' }, - { code: 422, message: 'Unprocessable entity' } - ] - tags INTEGRATIONS_TAGS - end - params do - settings.each do |setting| - if setting[:required] - requires setting[:name], type: setting[:type], desc: setting[:desc] - else - optional setting[:name], type: setting[:type], desc: setting[:desc] - end - end - end - put "#{path}/#{slug}" do - integration = user_project.find_or_initialize_integration(slug.underscore) - - render_api_error!('400 Integration not available', 400) if integration.nil? - - params = declared_params(include_missing: false).merge(active: true) - - unless integration.manual_activation? || integration.is_a?(::Integrations::Prometheus) - if integration.new_record? - render_api_error!("You cannot create the #{integration.class.title} integration from the API", 422) - end - - params.delete(:active) - end - - result = ::Integrations::UpdateService.new( - current_user: current_user, integration: integration, attributes: params - ).execute - - if result.success? - present integration, with: Entities::ProjectIntegration - else - render_api_error!(result.message, 400) - end - end - end - - desc "Disable an integration" do - detail "Disable the integration for a project. Integration settings are preserved." - success code: 204 - failure [ - { code: 400, message: 'Bad request' }, - { code: 401, message: 'Unauthorized' }, - { code: 404, message: 'Not found' } - ] - tags INTEGRATIONS_TAGS - end - params do - requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the integration' - end - delete "#{path}/:slug" do - integration = user_project.find_or_initialize_integration(params[:slug].underscore) - - not_found!('Integration') unless integration&.persisted? + before { authorize_admin_project_integrations } - if integration.is_a?(::Integrations::JiraCloudApp) - render_api_error!("You cannot disable the #{integration.class.title} integration from the API", 422) - end - - destroy_conditionally!(integration) do - attrs = integration_attributes(integration).index_with do |attr| - column = if integration.attribute_present?(attr) - integration.column_for_attribute(attr) - elsif integration.data_fields_present? - integration.data_fields.column_for_attribute(attr) - end - - case column - when nil, ActiveRecord::ConnectionAdapters::NullColumn - nil - else - column.default - end - end.merge(active: false) - - render_api_error!('400 Bad Request', 400) unless integration.update(attrs) + helpers do + def fetch_parent_resource + user_project end end - desc "Get an integration settings" do - detail "Get the integration settings for a project." - success Entities::ProjectIntegration - failure [ - { code: 400, message: 'Bad request' }, - { code: 401, message: 'Unauthorized' }, - { code: 404, message: 'Not found' } - ] - tags INTEGRATIONS_TAGS - end - params do - requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the integration' - end - get "#{path}/:slug" do - integration = user_project.find_or_initialize_integration(params[:slug].underscore) - - not_found!('Integration') unless integration&.persisted? - - present integration, with: Entities::ProjectIntegration - end + mount IntegratableOperations, with: { path: path } end SLASH_COMMAND_INTEGRATIONS.each do |integration_slug, settings| @@ -229,6 +110,26 @@ def slash_command_integration(project, integration_slug, params) end end + # New API endpoints should use the `:id/integrations` path exclusively. + ':id/integrations'.tap do |path| + params do + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the group' + end + + resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + before { authenticate! } + before { authorize_admin_group_integrations } + + helpers do + def fetch_parent_resource + user_group + end + end + + mount IntegratableOperations, with: { path: path } + end + end + desc "Trigger a global slack command" do detail 'Added in GitLab 9.4' failure [ diff --git a/lib/api/integrations/integratable_operations.rb b/lib/api/integrations/integratable_operations.rb new file mode 100644 index 0000000000000000000000000000000000000000..f1806f3bdd083f41611b619fd688dd7ec166e0ff --- /dev/null +++ b/lib/api/integrations/integratable_operations.rb @@ -0,0 +1,176 @@ +# frozen_string_literal: true + +module API + class Integrations + class IntegratableOperations < Grape::API # rubocop:disable API/Base -- subclassing from Grape::API is required for mounting in the main API. + INTEGRATIONS_TAGS = %w[integrations].freeze + + before do + render_api_error!('400 Integration not available', 400) if disallowed_integration_at_group_level?(params[:slug]) + end + + mounted do + helpers do + def disallowed_integration_at_group_level?(slug) + return unless slug.present? + + parent_resource.is_a?(Group) && Integration.project_specific_integration_names.include?(slug.underscore) + end + + def integration_attributes(integration) + integration.fields.inject([]) do |arr, hash| + arr << hash[:name].to_sym + end + end + + def find_or_initialize_integration(slug) + parent_resource.find_or_initialize_integration(slug.underscore) + end + + def parent_resource + unless respond_to?(:fetch_parent_resource, true) + raise NotImplementedError, + "You must implement a `fetch_parent_resource` method that returns " \ + "the integratable resource in the namespace that mounts this API." + end + + @parent_resource ||= fetch_parent_resource + end + end + + desc 'List all active integrations' do + detail "Get a list of all active integrations." + success Entities::IntegrationBasic + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags INTEGRATIONS_TAGS + end + + get(configuration[:path]) do + integrations = parent_resource.integrations.active + + present integrations, with: Entities::IntegrationBasic + end + + INTEGRATIONS.each do |slug, settings| + desc "Create/Edit #{slug.titleize} integration" do + detail "Set #{slug.titleize} integration." + success Entities::IntegrationBasic + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags INTEGRATIONS_TAGS + end + params do + settings.each do |setting| + if setting[:required] + requires setting[:name], type: setting[:type], desc: setting[:desc] + else + optional setting[:name], type: setting[:type], desc: setting[:desc] + end + end + end + put("#{configuration[:path]}/#{slug}") do + integration = find_or_initialize_integration(slug) + + params = declared_params(include_missing: false).merge(active: true) + + render_api_error!('400 Integration not available', 400) if integration.nil? + + manual_or_special = integration.manual_activation? || integration.is_a?(::Integrations::Prometheus) + + unless manual_or_special + if integration.new_record? + render_api_error!("You cannot create the #{integration.class.title} integration from the API", 422) + end + + params.delete(:active) + end + + result = ::Integrations::UpdateService.new( + current_user: current_user, integration: integration, attributes: params + ).execute + + if result.success? + present integration, with: Entities::Integration + else + message = result.message || '400 Bad Request' + render_api_error!(message, 400) + end + end + end + + desc "Disable an integration" do + detail "Disable the integration. Integration settings are preserved." + success code: 204 + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags INTEGRATIONS_TAGS + end + params do + requires :slug, type: String, values: INTEGRATIONS.keys, + desc: 'The name of the integration' + end + delete("#{configuration[:path]}/:slug") do + integration = find_or_initialize_integration(params[:slug]) + + not_found!('Integration') unless integration&.persisted? + + if integration.is_a?(::Integrations::JiraCloudApp) + render_api_error!("You cannot disable the #{integration.class.title} integration from the API", 422) + end + + destroy_conditionally!(integration) do + attrs = integration_attributes(integration).index_with do |attr| + column = if integration.attribute_present?(attr) + integration.column_for_attribute(attr) + elsif integration.data_fields_present? + integration.data_fields.column_for_attribute(attr) + end + + case column + when nil, ActiveRecord::ConnectionAdapters::NullColumn + nil + else + column.default + end + end.merge(active: false) + + render_api_error!('400 Bad Request', 400) unless integration.update(attrs) + end + end + + desc "Get an integration settings" do + detail "Get the integration settings." + success Entities::Integration + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags INTEGRATIONS_TAGS + end + params do + requires :slug, type: String, values: INTEGRATIONS.keys, + desc: 'The name of the integration' + end + get("#{configuration[:path]}/:slug") do + integration = find_or_initialize_integration(params[:slug]) + + not_found!('Integration') unless integration&.persisted? + + present integration, with: Entities::Integration + end + end + end + end +end diff --git a/spec/requests/api/integrations_spec.rb b/spec/requests/api/integrations_spec.rb index 5d353bbb9943a151b3fe6b1e70699a407505f437..66ba6f87e98971da200e5e483d78d7304c8b912c 100644 --- a/spec/requests/api/integrations_spec.rb +++ b/spec/requests/api/integrations_spec.rb @@ -7,38 +7,37 @@ let_it_be(:user) { create(:user) } let_it_be(:user2) { create(:user) } - let_it_be(:project, reload: true) { create(:project, creator_id: user.id, namespace: user.namespace) } - - let_it_be(:available_integration_names) do - excluded_integrations = [Integrations::GitlabSlackApplication.to_param, Integrations::Zentao.to_param] - - Integration.available_integration_names(include_instance_specific: false) - excluded_integrations - end + let_it_be(:project, reload: true) { create(:project, creator_id: user.id, namespace: user.namespace, owners: [user]) } + let_it_be(:project2) { create(:project, creator_id: user.id, namespace: user.namespace) } + let_it_be(:group, reload: true) { create(:group, owners: user) } - let_it_be(:project_integrations_map) do - available_integration_names.index_with do |name| - create(integration_factory(name), :inactive, project: project) + context "Project level integrations" do + let_it_be(:available_integration_names) do + excluded_integrations = [Integrations::GitlabSlackApplication.to_param, Integrations::Zentao.to_param] + Integration.available_integration_names(include_instance_specific: false) - excluded_integrations end - end - let_it_be(:project2) { create(:project, creator_id: user.id, namespace: user.namespace) } + let_it_be(:project_integrations_map) do + available_integration_names.index_with do |name| + create(integration_factory(name), :inactive, project: project) + end + end - %w[integrations services].each do |endpoint| - describe "GET /projects/:id/#{endpoint}" do - it 'returns authentication error when unauthenticated' do - get api("/projects/#{project.id}/#{endpoint}") + %w[integrations services].each do |endpoint| + describe "GET /projects/:id/#{endpoint}" do + it 'returns authentication error when unauthenticated' do + get api("/projects/#{project.id}/#{endpoint}") - expect(response).to have_gitlab_http_status(:unauthorized) - end + expect(response).to have_gitlab_http_status(:unauthorized) + end - it "returns error when authenticated but user is not a project owner" do - project.add_developer(user2) - get api("/projects/#{project.id}/#{endpoint}", user2) + it "returns error when authenticated but user is not a project owner" do + project.add_developer(user2) + get api("/projects/#{project.id}/#{endpoint}", user2) - expect(response).to have_gitlab_http_status(:forbidden) - end + expect(response).to have_gitlab_http_status(:forbidden) + end - context 'with integrations' do it "returns a list of all active integrations" do get api("/projects/#{project.id}/#{endpoint}", user) @@ -51,391 +50,490 @@ end end end - end - where(:integration) do - # The integrations API supports all project integrations. - # You cannot create a GitLab for Slack app. You must install the app from the GitLab UI. - unavailable_integration_names = [ - Integrations::GitlabSlackApplication.to_param, - Integrations::JiraCloudApp.to_param, - Integrations::Prometheus.to_param, - Integrations::Zentao.to_param - ] - - names = Integration.available_integration_names(include_instance_specific: false) - names.reject { |name| name.in?(unavailable_integration_names) } - end - - with_them do - integration = params[:integration] - - describe "PUT /projects/:id/#{endpoint}/#{integration.dasherize}" do - it_behaves_like 'set up an integration', endpoint: endpoint, integration: integration - end - - describe "DELETE /projects/:id/#{endpoint}/#{integration.dasherize}" do - it_behaves_like 'disable an integration', endpoint: endpoint, integration: integration - end + where(:integration) do + # The integrations API supports all project integrations. + # You cannot create a GitLab for Slack app. You must install the app from the GitLab UI. + unavailable_integration_names = [ + Integrations::GitlabSlackApplication.to_param, + Integrations::JiraCloudApp.to_param, + Integrations::Prometheus.to_param, + Integrations::Zentao.to_param + ] - describe "GET /projects/:id/#{endpoint}/#{integration.dasherize}" do - it_behaves_like 'get an integration settings', endpoint: endpoint, integration: integration + names = Integration.available_integration_names(include_instance_specific: false) + names.reject { |name| unavailable_integration_names.include?(name) } end - end - describe "POST /projects/:id/#{endpoint}/:slug/trigger" do - describe 'Mattermost integration' do - let(:integration_name) { 'mattermost_slash_commands' } + with_them do + integration = params[:integration] - context 'when no integration is available' do - it 'returns a not found message' do - post api("/projects/#{project.id}/#{endpoint}/idonotexist/trigger") + describe "PUT /projects/:id/#{endpoint}/#{integration.dasherize}" do + it_behaves_like 'set up an integration', endpoint: endpoint, integration: integration, parent_resource_name: 'project' do + let(:parent_resource) { project } + let(:integrations_map) { project_integrations_map } + end + end - expect(response).to have_gitlab_http_status(:not_found) - expect(json_response["error"]).to eq("404 Not Found") + describe "DELETE /projects/:id/#{endpoint}/#{integration.dasherize}" do + it_behaves_like 'disable an integration', endpoint: endpoint, integration: integration, parent_resource_name: 'project' do + let(:parent_resource) { project } + let(:integrations_map) { project_integrations_map } end end - context 'when the integration exists' do - let(:params) { { token: 'secrettoken' } } + describe "GET /projects/:id/#{endpoint}/#{integration.dasherize}" do + it_behaves_like 'get an integration settings', endpoint: endpoint, integration: integration, parent_resource_name: 'project' do + let(:parent_resource) { project } + let(:integrations_map) { project_integrations_map } + end + end + end - context 'when the integration is not active' do - before do - project_integrations_map[integration_name].deactivate! - end + describe "POST /projects/:id/#{endpoint}/:slug/trigger" do + describe 'Mattermost integration' do + let(:integration_name) { 'mattermost_slash_commands' } - it 'when the integration is inactive' do - post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params + context 'when no integration is available' do + it 'returns a not found message' do + post api("/projects/#{project.id}/#{endpoint}/idonotexist/trigger") expect(response).to have_gitlab_http_status(:not_found) + expect(json_response["error"]).to eq("404 Not Found") end end - context 'when the integration is active' do - before do - project_integrations_map[integration_name].activate! - end + context 'when the integration exists' do + let(:params) { { token: 'secrettoken' } } - it 'returns status 200' do - post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params + context 'when the integration is not active' do + before do + project_integrations_map[integration_name].deactivate! + end - expect(response).to have_gitlab_http_status(:ok) + it 'when the integration is inactive' do + post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params + + expect(response).to have_gitlab_http_status(:not_found) + end end - end - context 'when the project can not be found' do - it 'returns a generic 404' do - post api("/projects/404/#{endpoint}/#{integration_name}/trigger"), params: params + context 'when the integration is active' do + before do + project_integrations_map[integration_name].activate! + end - expect(response).to have_gitlab_http_status(:not_found) - expect(json_response["message"]).to eq("404 Integration Not Found") - end - end - end - end + it 'returns status 200' do + post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params - describe 'Slack Integration' do - let(:integration_name) { 'slack_slash_commands' } - let(:params) { { token: 'secrettoken', text: 'help' } } + expect(response).to have_gitlab_http_status(:ok) + end + end - context 'when no integration is available' do - it 'returns a not found message' do - post api("/projects/#{project.id}/#{endpoint}/idonotexist/trigger") + context 'when the project can not be found' do + it 'returns a generic 404' do + post api("/projects/404/#{endpoint}/#{integration_name}/trigger"), params: params - expect(response).to have_gitlab_http_status(:not_found) - expect(json_response["error"]).to eq("404 Not Found") + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response["message"]).to eq("404 Integration Not Found") + end + end end end - context 'when the integration exists' do - context 'when the integration is not active' do - before do - project_integrations_map[integration_name].deactivate! - end + describe 'Slack Integration' do + let(:integration_name) { 'slack_slash_commands' } + let(:params) { { token: 'secrettoken', text: 'help' } } - it 'when the integration is inactive' do - post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params + context 'when no integration is available' do + it 'returns a not found message' do + post api("/projects/#{project.id}/#{endpoint}/idonotexist/trigger") expect(response).to have_gitlab_http_status(:not_found) + expect(json_response["error"]).to eq("404 Not Found") end end - context 'when the integration is active' do - before do - project_integrations_map[integration_name].activate! + context 'when the integration exists' do + context 'when the integration is not active' do + before do + project_integrations_map[integration_name].deactivate! + end + + it 'when the integration is inactive' do + post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params + + expect(response).to have_gitlab_http_status(:not_found) + end end - it 'returns status 200' do - post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params + context 'when the integration is active' do + before do + project_integrations_map[integration_name].activate! + end - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['response_type']).to eq("ephemeral") + it 'returns status 200' do + post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['response_type']).to eq("ephemeral") + end end - end - context 'when the project can not be found' do - it 'returns a generic 404' do - post api("/projects/404/#{endpoint}/#{integration_name}/trigger"), params: params + context 'when the project can not be found' do + it 'returns a generic 404' do + post api("/projects/404/#{endpoint}/#{integration_name}/trigger"), params: params - expect(response).to have_gitlab_http_status(:not_found) - expect(json_response["message"]).to eq("404 Integration Not Found") + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response["message"]).to eq("404 Integration Not Found") + end end end end end - end - describe 'Mattermost integration' do - let(:integration_name) { 'mattermost' } - let(:params) do - { webhook: 'https://hook.example.com', username: 'username' } - end + describe 'Mattermost integration' do + let(:integration_name) { 'mattermost' } + let(:params) do + { webhook: 'https://hook.example.com', username: 'username' } + end - before do - project_integrations_map[integration_name].activate! - end + before do + project_integrations_map[integration_name].activate! + end - it 'accepts a username for update' do - put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(username: 'new_username') + it 'accepts a username for update' do + put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(username: 'new_username') - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['properties']['username']).to eq('new_username') + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['properties']['username']).to eq('new_username') + end end - end - describe 'Microsoft Teams integration' do - let_it_be(:group) { create(:group) } - let(:integration_name) { 'microsoft-teams' } - let(:params) do - { - webhook: 'https://hook.example.com', - branches_to_be_notified: 'default', - notify_only_broken_pipelines: false - } - end + describe 'Microsoft Teams integration' do + let_it_be(:group) { create(:group) } + let(:integration_name) { 'microsoft-teams' } + let(:params) do + { + webhook: 'https://hook.example.com', + branches_to_be_notified: 'default', + notify_only_broken_pipelines: false + } + end - before do - create(:microsoft_teams_integration, group: group, project: nil) - project.update!(namespace: group) - project_integrations_map[integration_name.underscore].activate! - end + before do + create(:microsoft_teams_integration, group: group, project: nil) + project.update!(namespace: group) + project_integrations_map[integration_name.underscore].activate! + end - it 'accepts branches_to_be_notified for update' do - put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), - params: params.merge(branches_to_be_notified: 'all') + it 'accepts branches_to_be_notified for update' do + put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), + params: params.merge(branches_to_be_notified: 'all') - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['properties']['branches_to_be_notified']).to eq('all') - end + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['properties']['branches_to_be_notified']).to eq('all') + end - it 'accepts notify_only_broken_pipelines for update' do - put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), - params: params.merge(notify_only_broken_pipelines: true) + it 'accepts notify_only_broken_pipelines for update' do + put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), + params: params.merge(notify_only_broken_pipelines: true) - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true) - end + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true) + end - it 'accepts `use_inherited_settings` for inheritance' do - expect do - put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), - params: params.merge(use_inherited_settings: true) - end.to change { project_integrations_map[integration_name.underscore].reload.inherit_from_id }.from(nil) + it 'accepts `use_inherited_settings` for inheritance' do + expect do + put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), + params: params.merge(use_inherited_settings: true) + end.to change { project_integrations_map[integration_name.underscore].reload.inherit_from_id }.from(nil) - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['inherited']).to eq(true) + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['inherited']).to eq(true) + end end - end - describe 'Hangouts Chat integration' do - let(:integration_name) { 'hangouts-chat' } - let(:params) do - { - webhook: 'https://hook.example.com', - branches_to_be_notified: 'default' - } - end + describe 'Hangouts Chat integration' do + let(:integration_name) { 'hangouts-chat' } + let(:params) do + { + webhook: 'https://hook.example.com', + branches_to_be_notified: 'default' + } + end - before do - project_integrations_map[integration_name.underscore].activate! - end + before do + project_integrations_map[integration_name.underscore].activate! + end - it 'accepts branches_to_be_notified for update', :aggregate_failures do - put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(branches_to_be_notified: 'all') + it 'accepts branches_to_be_notified for update', :aggregate_failures do + put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(branches_to_be_notified: 'all') - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['properties']['branches_to_be_notified']).to eq('all') - end + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['properties']['branches_to_be_notified']).to eq('all') + end - it 'only requires the webhook param' do - put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: { webhook: 'https://hook.example.com' } + it 'only requires the webhook param' do + put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: { webhook: 'https://hook.example.com' } - expect(response).to have_gitlab_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) + end end - end - describe 'Jira integration' do - let(:integration_name) { 'jira' } - let(:params) do - { url: 'https://jira.example.com', username: 'username', password: 'password', jira_auth_type: 0 } - end + describe 'Jira integration' do + let(:integration_name) { 'jira' } + let(:params) do + { url: 'https://jira.example.com', username: 'username', password: 'password', jira_auth_type: 0 } + end - before do - project_integrations_map[integration_name].properties = params - project_integrations_map[integration_name].activate! - end + before do + project_integrations_map[integration_name].properties = params + project_integrations_map[integration_name].activate! + end - it 'returns the jira_issue_transition_id for get request' do - get api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user) + it 'returns the jira_issue_transition_id for get request' do + get api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user) - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['properties']).to include('jira_issue_transition_id' => '56-1') - end + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['properties']).to include('jira_issue_transition_id' => '56-1') + end - it 'returns the jira_issue_transition_id for put request' do - put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(jira_issue_transition_id: '1') + it 'returns the jira_issue_transition_id for put request' do + put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(jira_issue_transition_id: '1') - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['properties']['jira_issue_transition_id']).to eq('1') + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['properties']['jira_issue_transition_id']).to eq('1') + end end - end - describe 'Pipelines Email Integration' do - let(:integration_name) { 'pipelines-email' } + describe 'Pipelines Email Integration' do + let(:integration_name) { 'pipelines-email' } + + context 'notify_only_broken_pipelines property was saved as a string' do + before do + project_integrations_map[integration_name.underscore].activate! + end + + it 'returns boolean values for notify_only_broken_pipelines' do + get api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user) + + expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true) + end + end + end - context 'notify_only_broken_pipelines property was saved as a string' do + describe 'GitLab for Slack app integration' do before do - project_integrations_map[integration_name.underscore].activate! + stub_application_setting(slack_app_enabled: true) + create(:gitlab_slack_application_integration, project: project) end - it 'returns boolean values for notify_only_broken_pipelines' do - get api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user) + describe "PUT /projects/:id/#{endpoint}/gitlab-slack-application" do + context 'for integration creation' do + before do + project.gitlab_slack_application_integration.destroy! + end - expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true) + it 'returns 422' do + put api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user) + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + expect(json_response['message']).to eq('You cannot create the GitLab for Slack app integration from the API') + end + end + + context 'for integration update' do + before do + project.gitlab_slack_application_integration.update!(active: false) + end + + it "does not enable the integration" do + put api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(project.gitlab_slack_application_integration.reload).to have_attributes(active: false) + end + end end - end - end - describe 'GitLab for Slack app integration' do - before do - stub_application_setting(slack_app_enabled: true) - create(:gitlab_slack_application_integration, project: project) - end + describe "GET /projects/:id/#{endpoint}/gitlab-slack-application" do + it "fetches the integration and returns the correct fields" do + get api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user) - describe "PUT /projects/:id/#{endpoint}/gitlab-slack-application" do - context 'for integration creation' do - before do - project.gitlab_slack_application_integration.destroy! + expect(response).to have_gitlab_http_status(:ok) + assert_correct_response_fields(json_response['properties'].keys, project.gitlab_slack_application_integration) end + end - it 'returns 422' do - put api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user) + describe "DELETE /projects/:id/#{endpoint}/gitlab-slack-application" do + it "disables the integration" do + expect { delete api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user) } + .to change { project.gitlab_slack_application_integration.reload.activated? }.from(true).to(false) - expect(response).to have_gitlab_http_status(:unprocessable_entity) - expect(json_response['message']).to eq('You cannot create the GitLab for Slack app integration from the API') + expect(response).to have_gitlab_http_status(:no_content) end end + end - context 'for integration update' do - before do - project.gitlab_slack_application_integration.update!(active: false) + describe 'GitLab for Jira Cloud app integration' do + before do + stub_application_setting(jira_connect_application_key: 'mock_key') + create(:jira_cloud_app_integration, project: project) + end + + describe "PUT /projects/:id/#{endpoint}/jira-cloud-app" do + context 'for integration creation' do + before do + project.jira_cloud_app_integration.destroy! + end + + it 'returns 422' do + put api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user) + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + expect(json_response['message']).to eq('You cannot create the GitLab for Jira Cloud app integration from the API') + end + end + + context 'for integration update' do + before do + project.jira_cloud_app_integration.update!(active: false) + end + + it "does not enable the integration" do + put api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(project.jira_cloud_app_integration.reload).to have_attributes(active: false) + end end + end - it "does not enable the integration" do - put api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user) + describe "GET /projects/:id/#{endpoint}/jira-cloud-app" do + it "fetches the integration and returns the correct fields" do + get api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user) expect(response).to have_gitlab_http_status(:ok) - expect(project.gitlab_slack_application_integration.reload).to have_attributes(active: false) + assert_correct_response_fields(json_response['properties'].keys, project.jira_cloud_app_integration) end end - end - describe "GET /projects/:id/#{endpoint}/gitlab-slack-application" do - it "fetches the integration and returns the correct fields" do - get api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user) + describe "DELETE /projects/:id/#{endpoint}/jira-cloud-app" do + it "does not disable the integration" do + expect { delete api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user) } + .not_to change { project.jira_cloud_app_integration.reload.activated? }.from(true) - expect(response).to have_gitlab_http_status(:ok) - assert_correct_response_fields(json_response['properties'].keys, project.gitlab_slack_application_integration) + expect(response).to have_gitlab_http_status(:unprocessable_entity) + expect(json_response['message']).to eq('You cannot disable the GitLab for Jira Cloud app integration from the API') + end end end - describe "DELETE /projects/:id/#{endpoint}/gitlab-slack-application" do - it "disables the integration" do - expect { delete api("/projects/#{project.id}/#{endpoint}/gitlab-slack-application", user) } - .to change { project.gitlab_slack_application_integration.reload.activated? }.from(true).to(false) + private - expect(response).to have_gitlab_http_status(:no_content) - end + def assert_correct_response_fields(response_keys, integration) + assert_fields_match_integration(response_keys, integration) + assert_secret_fields_filtered(response_keys, integration) end + + def assert_fields_match_integration(response_keys, integration) + expect(response_keys).to match_array(integration.api_field_names) + end + + def assert_secret_fields_filtered(response_keys, integration) + expect(response_keys).not_to include(*integration.secret_fields) unless integration.secret_fields.empty? + end + end + end + + context "Group level integrations" do + let_it_be(:available_integration_names) do + excluded_integrations = [Integrations::GitlabSlackApplication.to_param, Integrations::Zentao.to_param] + Integration.available_integration_names(include_project_specific: false, include_instance_specific: false) - excluded_integrations end - describe 'GitLab for Jira Cloud app integration' do - before do - stub_application_setting(jira_connect_application_key: 'mock_key') - create(:jira_cloud_app_integration, project: project) + let_it_be(:group_integrations_map) do + available_integration_names.index_with do |name| + create(integration_factory(name), :inactive, :group, group: group) end + end - describe "PUT /projects/:id/#{endpoint}/jira-cloud-app" do - context 'for integration creation' do - before do - project.jira_cloud_app_integration.destroy! - end + 'integrations'.tap do |endpoint| + describe "GET /groups/:id/#{endpoint}" do + let_it_be(:project_only_integration) { Integration::PROJECT_LEVEL_ONLY_INTEGRATION_NAMES } - it 'returns 422' do - put api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user) + it 'returns authentication error when unauthenticated' do + get api("/groups/#{group.id}/#{endpoint}") - expect(response).to have_gitlab_http_status(:unprocessable_entity) - expect(json_response['message']).to eq('You cannot create the GitLab for Jira Cloud app integration from the API') - end + expect(response).to have_gitlab_http_status(:unauthorized) end - context 'for integration update' do - before do - project.jira_cloud_app_integration.update!(active: false) - end + it "returns error when authenticated but user is not a group owner" do + group.add_developer(user2) + get api("/groups/#{group.id}/#{endpoint}", user2) + + expect(response).to have_gitlab_http_status(:forbidden) + end - it "does not enable the integration" do - put api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user) + it "returns a list of all active integrations" do + get api("/groups/#{group.id}/#{endpoint}", user) + aggregate_failures 'expect successful response with all active integrations' do expect(response).to have_gitlab_http_status(:ok) - expect(project.jira_cloud_app_integration.reload).to have_attributes(active: false) + expect(json_response).to be_an Array + expect(json_response.count).to eq(1) + expect(json_response.first['slug']).to eq('prometheus') + expect(response).to match_response_schema('public_api/v4/integrations') end end - end - describe "GET /projects/:id/#{endpoint}/jira-cloud-app" do - it "fetches the integration and returns the correct fields" do - get api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user) + Integration::PROJECT_LEVEL_ONLY_INTEGRATION_NAMES.map(&:dasherize).each do |integration_name| + it "returns 400 when trying to access a project-only integration" do + get api("/groups/#{group.id}/integrations/#{integration_name}", user) - expect(response).to have_gitlab_http_status(:ok) - assert_correct_response_fields(json_response['properties'].keys, project.jira_cloud_app_integration) + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response["message"]).to eq("400 Integration not available") + end end end - describe "DELETE /projects/:id/#{endpoint}/jira-cloud-app" do - it "does not disable the integration" do - expect { delete api("/projects/#{project.id}/#{endpoint}/jira-cloud-app", user) } - .not_to change { project.jira_cloud_app_integration.reload.activated? }.from(true) + where(:integration) do + # You cannot create a GitLab for Slack app. You must install the app from the GitLab UI. + unavailable_integration_names = [ + Integrations::GitlabSlackApplication.to_param, + Integrations::Zentao.to_param, + Integrations::Prometheus.to_param + ] - expect(response).to have_gitlab_http_status(:unprocessable_entity) - expect(json_response['message']).to eq('You cannot disable the GitLab for Jira Cloud app integration from the API') - end + names = Integration.available_integration_names(include_instance_specific: false, include_project_specific: false) + names.reject { |name| unavailable_integration_names.include?(name) } end - end - private + with_them do + integration = params[:integration] - def assert_correct_response_fields(response_keys, integration) - assert_fields_match_integration(response_keys, integration) - assert_secret_fields_filtered(response_keys, integration) - end + describe "PUT /groups/:id/#{endpoint}/#{integration.dasherize}" do + it_behaves_like 'set up an integration', endpoint: endpoint, integration: integration, parent_resource_name: 'group' do + let(:parent_resource) { group } + let(:integrations_map) { group_integrations_map } + end + end - def assert_fields_match_integration(response_keys, integration) - expect(response_keys).to match_array(integration.api_field_names) - end + describe "DELETE /groups/:id/#{endpoint}/#{integration.dasherize}" do + it_behaves_like 'disable an integration', endpoint: endpoint, integration: integration, parent_resource_name: 'group' do + let(:parent_resource) { group } + let(:integrations_map) { group_integrations_map } + end + end - def assert_secret_fields_filtered(response_keys, integration) - expect(response_keys).not_to include(*integration.secret_fields) unless integration.secret_fields.empty? + describe "GET /groups/:id/#{endpoint}/#{integration.dasherize}" do + it_behaves_like 'get an integration settings', endpoint: endpoint, integration: integration, parent_resource_name: 'group' do + let(:parent_resource) { group } + let(:integrations_map) { group_integrations_map } + end + end + end end end diff --git a/spec/services/integrations/update_service_spec.rb b/spec/services/integrations/update_service_spec.rb index 273264bf394f72f48b2a0dbda96eb241d67bb293..e41af6ca486a5d9b464aeee6f006dd8fd9f9b20f 100644 --- a/spec/services/integrations/update_service_spec.rb +++ b/spec/services/integrations/update_service_spec.rb @@ -18,11 +18,21 @@ end shared_examples 'success request' do + before do + allow(PropagateIntegrationWorker).to receive(:perform_async) + end + it 'returns a success response' do result expect(result).to be_success expect(result.payload).to eq(integration) + + if integration.project_level? + expect(PropagateIntegrationWorker).not_to have_received(:perform_async) + else + expect(PropagateIntegrationWorker).to have_received(:perform_async).with(integration.id) + end end end diff --git a/spec/support/shared_examples/integrations_shared_examples.rb b/spec/support/shared_examples/integrations_shared_examples.rb index 085679c9136acd127dc29972f2de9dcfb0c1ad10..db58e955208017cd829f91f7dbe7ece9781c43d5 100644 --- a/spec/support/shared_examples/integrations_shared_examples.rb +++ b/spec/support/shared_examples/integrations_shared_examples.rb @@ -1,10 +1,16 @@ # frozen_string_literal: true -RSpec.shared_examples 'set up an integration' do |endpoint:, integration:| +RSpec.shared_examples 'set up an integration' do |endpoint:, integration:, parent_resource_name:| include_context 'with integration' - let(:integration_attrs) { attributes_for(integration_factory).without(:active, :type) } - let(:url) { api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user) } + let(:integrations_map) { raise NotImplementedError, 'Define `integrations_map` in the calling context' } + let(:parent_resource) { raise NotImplementedError, 'Define `parent_resource` in the calling context' } + + let(:integration_attrs) do + attributes_for(integration_factory).without(:active, :type) + end + + let(:url) { api("/#{parent_resource_name.pluralize}/#{parent_resource.id}/#{endpoint}/#{dashed_integration}", user) } subject(:request) { put url, params: integration_attrs } @@ -14,7 +20,7 @@ expect(response).to have_gitlab_http_status(:ok) expect(json_response['slug']).to eq(dashed_integration) - current_integration = project.integrations.by_name(integration).first + current_integration = parent_resource.integrations.by_name(integration).first expect(current_integration).to have_attributes(integration_attrs) expect(json_response['properties'].keys).to match_array(current_integration.api_field_names) @@ -32,7 +38,7 @@ put url, params: flipped_attrs expect(response).to have_gitlab_http_status(:ok) - expect(project.integrations.by_name(integration).first).to have_attributes(flipped_attrs) + expect(parent_resource.integrations.by_name(integration).first).to have_attributes(flipped_attrs) end end @@ -57,24 +63,60 @@ expect(response).to have_gitlab_http_status(expected_code) end - context 'when an integration is disabled' do + context "when updates fails" do before do - allow(Integration).to receive(:disabled_integration_names).and_return([integration.to_param]) + allow_next_instance_of(::Integrations::UpdateService) do |instance| + allow(instance).to receive(:execute).and_return( + instance_double(ServiceResponse, success?: false, message: 'Update failed') + ) + end end - it 'returns bad request' do - put url, params: integration_attrs + it 'returns 400 with correct message' do + request expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq('Update failed') end end - context 'when an integration is disabled at the project-level' do - before do - allow_next_found_instance_of(Project) do |project| - allow(project).to receive(:disabled_integrations).and_return([integration]) + context 'when the integration does not exist' do + let(:fresh_parent_resource) { create(parent_resource_name.to_sym, owners: [user]) } + let(:parent_resource) { fresh_parent_resource } + + it "creates #{integration} and returns the correct fields" do + initial_count = parent_resource.integrations.by_name(integration).count + expect(initial_count).to eq(0) + + request + + current_integration = parent_resource.integrations.by_name(integration).first + manual_or_special = current_integration&.manual_activation? || + current_integration.is_a?(::Integrations::Prometheus) + delta = manual_or_special ? 1 : 0 + + expect(parent_resource.integrations.by_name(integration).count).to eq(initial_count + delta) + + if manual_or_special + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['slug']).to eq(dashed_integration) + + expect(current_integration).to have_attributes(integration_attrs) + expect(json_response['properties'].keys).to match_array(current_integration.api_field_names) + + if current_integration.secret_fields.present? + expect(json_response['properties'].keys).not_to include(*current_integration.secret_fields) + end + else + expect(response).to have_gitlab_http_status(:unprocessable_entity) end end + end + + context 'when an integration is disabled' do + before do + allow(Integration).to receive(:disabled_integration_names).and_return([integration.to_param]) + end it 'returns bad request' do put url, params: integration_attrs @@ -82,39 +124,69 @@ expect(response).to have_gitlab_http_status(:bad_request) end end + + if parent_resource_name == 'project' + context 'when an integration is disabled at the parent_resource-level' do + before do + allow_next_found_instance_of(parent_resource.class) do |instance| + allow(instance).to receive(:disabled_integrations).and_return([integration]) + end + end + + it 'returns bad request' do + put url, params: integration_attrs + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + end end -RSpec.shared_examples 'disable an integration' do |endpoint:, integration:| +RSpec.shared_examples 'disable an integration' do |endpoint:, integration:, parent_resource_name:| include_context 'with integration' - subject(:request) { delete api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user) } + let(:fresh_parent_resource) { create(parent_resource_name.to_sym, owners: [user]) } + let(:parent_resource) { fresh_parent_resource } + + subject(:request) do + delete api("/#{parent_resource_name.pluralize}/#{parent_resource.id}/#{endpoint}/#{dashed_integration}", user) + end before do - project_integrations_map[integration].activate! + integrations_map[integration].update_column(:active, true) end it "deletes #{integration}" do - request + expect do + request + end.to change { + parent_resource.integrations.where(type_new: integration_klass.name, active: true).count + }.from(1).to(0) expect(response).to have_gitlab_http_status(:no_content) - project.send(integration_method).reload - expect(project.send(integration_method).activated?).to be_falsey end it 'returns not found if integration does not exist' do - delete api("/projects/#{project2.id}/#{endpoint}/#{dashed_integration}", user) + delete api("/#{parent_resource_name.pluralize}/#{fresh_parent_resource.id}/#{endpoint}/#{dashed_integration}", user) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 Integration Not Found') end end -RSpec.shared_examples 'get an integration settings' do |endpoint:, integration:| +RSpec.shared_examples 'get an integration settings' do |endpoint:, integration:, parent_resource_name:| include_context 'with integration' - let(:initialized_integration) { project_integrations_map[integration] } + let(:initialized_integration) do + integrations_map[integration] + end + + let(:fresh_parent_resource) { create(parent_resource_name.to_sym, owners: [user]) } + let(:parent_resource) { fresh_parent_resource } - subject(:request) { get api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user) } + subject(:request) do + get api("/#{parent_resource_name.pluralize}/#{parent_resource.id}/#{endpoint}/#{dashed_integration}", user) + end def deactive_integration! return initialized_integration.deactivate! unless initialized_integration.is_a?(::Integrations::Prometheus) @@ -142,10 +214,10 @@ def activate_integration! expect(initialized_integration).not_to be_active expect(response).to have_gitlab_http_status(:ok) - expect(json_response['properties'].keys).to match_array(integration_instance.api_field_names) + expect(json_response['properties'].keys).to match_array(initialized_integration.api_field_names) - unless integration_instance.secret_fields.empty? - expect(json_response['properties'].keys).not_to include(*integration_instance.secret_fields) + unless initialized_integration.secret_fields.empty? + expect(json_response['properties'].keys).not_to include(*initialized_integration.secret_fields) end end end @@ -160,40 +232,44 @@ def activate_integration! expect(initialized_integration).to be_active expect(response).to have_gitlab_http_status(:ok) - expect(json_response['properties'].keys).to match_array(integration_instance.api_field_names) + expect(json_response['properties'].keys).to match_array(initialized_integration.api_field_names) - unless integration_instance.secret_fields.empty? - expect(json_response['properties'].keys).not_to include(*integration_instance.secret_fields) + unless initialized_integration.secret_fields.empty? + expect(json_response['properties'].keys).not_to include(*initialized_integration.secret_fields) end end end it 'returns authentication error when unauthenticated' do - get api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}") + get api("/#{parent_resource_name.pluralize}/#{fresh_parent_resource.id}/#{endpoint}/#{dashed_integration}") + expect(response).to have_gitlab_http_status(:unauthorized) end it "returns not found if integration does not exist" do - get api("/projects/#{project2.id}/#{endpoint}/#{dashed_integration}", user) + get api("/#{parent_resource_name.pluralize}/#{fresh_parent_resource.id}/#{endpoint}/#{dashed_integration}", user) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 Integration Not Found') end - it "returns not found if integration exists but is in `Project#disabled_integrations`" do - allow_next_found_instance_of(Project) do |project| - allow(project).to receive(:disabled_integrations).and_return([integration]) - end + if parent_resource_name == 'project' + it "returns not found if integration exists but is in `#{parent_resource_name}#disabled_integrations`" do + allow_next_found_instance_of(parent_resource.class) do |instance| + allow(instance).to receive(:disabled_integrations).and_return([integration]) + end - request + get api("/#{parent_resource_name.pluralize}/#{parent_resource.id}/#{endpoint}/#{dashed_integration}", user) - expect(response).to have_gitlab_http_status(:not_found) - expect(json_response['message']).to eq('404 Integration Not Found') + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq('404 Integration Not Found') + end end it "returns error when authenticated but not a project owner" do - project.add_developer(user2) - get api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user2) + parent_resource.add_developer(user2) + + get api("/#{parent_resource_name.pluralize}/#{parent_resource.id}/#{endpoint}/#{dashed_integration}", user2) expect(response).to have_gitlab_http_status(:forbidden) end