Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-08-07 00:09:45 +00:00
parent 9bef5f3d98
commit c21f4c45f9
55 changed files with 930 additions and 132 deletions

View File

@ -39,7 +39,7 @@ rails-production-server-boot-puma-example:
- sed --in-place "s:/home/git/gitlab:${PWD}:" config/puma.rb
- echo 'bind "tcp://127.0.0.1:3000"' >> config/puma.rb
- bundle exec puma --environment production --config config/puma.rb &
- sleep 40 # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114124#note_1309506358
- sleep 50 # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114124#note_1309506358
- retry_times_sleep 10 5 "curl http://127.0.0.1:3000"
- kill $(jobs -p)
@ -54,7 +54,7 @@ rails-production-server-boot-puma-cng:
- curl --silent "https://gitlab.com/gitlab-org/build/CNG/-/raw/${TRIGGER_BRANCH}/gitlab-webservice/configuration/puma.rb" > config/puma.rb
- sed --in-place "s:/srv/gitlab:${PWD}:" config/puma.rb
- bundle exec puma --environment production --config config/puma.rb &
- sleep 40 # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114124#note_1309506358
- sleep 50 # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114124#note_1309506358
- retry_times_sleep 10 5 "curl http://127.0.0.1:8080"
- kill $(jobs -p)

View File

@ -47,6 +47,10 @@ include:
inputs:
gem_name: "gitlab-topology-service-client"
gem_path_prefix: "vendor/gems/"
- local: .gitlab/ci/templates/gem.gitlab-ci.yml
inputs:
gem_name: "gitlab-duo-workflow-service-client"
gem_path_prefix: "vendor/gems/"
- local: .gitlab/ci/templates/gem.gitlab-ci.yml
inputs:
gem_name: "sidekiq-7.1.6"

11
Gemfile
View File

@ -166,6 +166,11 @@ gem 'gitlab-topology-service-client', '~> 0.1',
path: 'vendor/gems/gitlab-topology-service-client',
feature_category: :cell
# Duo Workflow
gem 'gitlab-duo-workflow-service-client', '~> 0.1',
path: 'vendor/gems/gitlab-duo-workflow-service-client',
feature_category: :duo_workflow
# Generate Fake data
gem 'ffaker', '~> 2.23' # rubocop:todo Gemfile/MissingFeatureCategory
@ -628,7 +633,11 @@ gem 'gitaly', '~> 17.2.0', feature_category: :gitaly
# KAS GRPC protocol definitions
gem 'kas-grpc', '~> 0.6.0', feature_category: :deployment_management
gem 'grpc', '~> 1.63', feature_category: :shared
# Lock the version before issues below are resolved:
# https://gitlab.com/gitlab-org/gitlab/-/issues/473169#note_2028352939
# Or we can upgrade to a more recent version as long as we can confirm
# that it doesn't have the same issues.
gem 'grpc', '= 1.63.0', feature_category: :shared
gem 'google-protobuf', '~> 3.25', '>= 3.25.3' # rubocop:todo Gemfile/MissingFeatureCategory

View File

@ -62,7 +62,7 @@
{"name":"bigdecimal","version":"3.1.7","platform":"ruby","checksum":"e799b369a0005fc6d62eed7ef19139ac9bc319cc51470c637b9dcdf593600133"},
{"name":"bindata","version":"2.4.11","platform":"ruby","checksum":"c38e0c99ffcd80c10a0a7ae6c8586d2fe26bf245cbefac90bec8764523220f6a"},
{"name":"binding_of_caller","version":"1.0.0","platform":"ruby","checksum":"3aad25d1d538fc6e7972978f9bf512ccd992784009947c81633bea776713161d"},
{"name":"bootsnap","version":"1.18.3","platform":"ruby","checksum":"d7b70de761e2fb1d63d21dd941b393c881c5cab5575211369cede788dfc034eb"},
{"name":"bootsnap","version":"1.18.4","platform":"ruby","checksum":"ac4c42af397f7ee15521820198daeff545e4c360d2772c601fbdc2c07d92af55"},
{"name":"browser","version":"5.3.1","platform":"ruby","checksum":"62745301701ff2c6c5d32d077bb12532b20be261929dcb52c6781ed0d5658b3c"},
{"name":"builder","version":"3.2.4","platform":"ruby","checksum":"99caf08af60c8d7f3a6b004029c4c3c0bdaebced6c949165fe98f1db27fbbc10"},
{"name":"bullet","version":"7.1.2","platform":"ruby","checksum":"429725c174cb74ca0ae99b9720bf22cab80be59ee9401805f7ecc9ac62cbb3bb"},

View File

@ -145,6 +145,12 @@ PATH
specs:
diff_match_patch (0.1.0)
PATH
remote: vendor/gems/gitlab-duo-workflow-service-client
specs:
gitlab-duo-workflow-service-client (0.1)
grpc
PATH
remote: vendor/gems/gitlab-topology-service-client
specs:
@ -368,7 +374,7 @@ GEM
bindata (2.4.11)
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
bootsnap (1.18.3)
bootsnap (1.18.4)
msgpack (~> 1.2)
browser (5.3.1)
builder (3.2.4)
@ -2047,6 +2053,7 @@ DEPENDENCIES
gitlab-backup-cli!
gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 4.8.0)
gitlab-duo-workflow-service-client (~> 0.1)!
gitlab-experiment (~> 0.9.1)
gitlab-fog-azure-rm (~> 2.0.1)
gitlab-glfm-markdown (~> 0.0.17)
@ -2099,7 +2106,7 @@ DEPENDENCIES
graphlyte (~> 1.0.0)
graphql (~> 2.3.5)
graphql-docs (~> 5.0.0)
grpc (~> 1.63)
grpc (= 1.63.0)
gssapi (~> 1.3.1)
guard-rspec
haml_lint (~> 0.58)

View File

@ -24,7 +24,9 @@ module OauthApplications
end
def load_scopes
@scopes ||= Doorkeeper.configuration.scopes
@scopes ||= Doorkeeper::OAuth::Scopes.from_array(
Doorkeeper.configuration.scopes.to_a - [::Gitlab::Auth::AI_WORKFLOW.to_s]
)
end
def permitted_params

View File

@ -34,6 +34,7 @@ module WikiActions
before_action do
push_frontend_feature_flag(:preserve_markdown, container)
push_force_frontend_feature_flag(:glql_integration, container&.glql_integration_feature_flag_enabled?)
end
before_action only: [:show, :edit, :update] do

View File

@ -13,6 +13,7 @@ module Groups
push_force_frontend_feature_flag(:namespace_level_work_items, namespace_work_items_enabled?)
push_force_frontend_feature_flag(:create_group_level_work_items,
group&.create_group_level_work_items_feature_flag_enabled?)
push_force_frontend_feature_flag(:glql_integration, group&.glql_integration_feature_flag_enabled?)
end
before_action :handle_new_work_item_path, only: [:show]

View File

@ -50,6 +50,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:issues_list_drawer, project)
push_frontend_feature_flag(:notifications_todos_buttons, current_user)
push_frontend_feature_flag(:comment_tooltips, current_user)
push_force_frontend_feature_flag(:glql_integration, project&.glql_integration_feature_flag_enabled?)
end
before_action only: [:index, :show] do

View File

@ -7,6 +7,10 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
feature_category :code_review_workflow
before_action do
push_force_frontend_feature_flag(:glql_integration, project&.glql_integration_feature_flag_enabled?)
end
private
# Normally the methods with `check_(\w+)_available!` pattern are

View File

@ -12,6 +12,7 @@ class Projects::WorkItemsController < Projects::ApplicationController
push_force_frontend_feature_flag(:work_items, project&.work_items_feature_flag_enabled?)
push_force_frontend_feature_flag(:work_items_beta, project&.work_items_beta_feature_flag_enabled?)
push_force_frontend_feature_flag(:work_items_alpha, project&.work_items_alpha_feature_flag_enabled?)
push_force_frontend_feature_flag(:glql_integration, project&.glql_integration_feature_flag_enabled?)
push_frontend_feature_flag(:namespace_level_work_items, project&.group)
push_frontend_feature_flag(:comment_tooltips)
end

View File

@ -952,6 +952,10 @@ class Group < Namespace
feature_flag_enabled_for_self_or_ancestor?(:work_items_rolledup_dates)
end
def glql_integration_feature_flag_enabled?
feature_flag_enabled_for_self_or_ancestor?(:glql_integration)
end
# Note: this method is overridden in EE to check the work_item_epics feature flag which also enables this feature
def namespace_work_items_enabled?(_user = nil)
::Feature.enabled?(:namespace_level_work_items, self, type: :development)

View File

@ -33,6 +33,10 @@ class MergeRequestsClosingIssues < ApplicationRecord
preload(merge_request: [:target_project, :author])
end
def preload_issue
preload(:issue)
end
def count_for_collection(ids, current_user)
closing_merge_requests(ids, current_user).group(:issue_id).pluck('issue_id', Arel.sql('COUNT(*) as count'))
end

View File

@ -3273,6 +3273,10 @@ class Project < ApplicationRecord
group&.work_items_alpha_feature_flag_enabled? || Feature.enabled?(:work_items_alpha)
end
def glql_integration_feature_flag_enabled?
group&.glql_integration_feature_flag_enabled? || Feature.enabled?(:glql_integration, self)
end
def enqueue_record_project_target_platforms
return unless Gitlab.com?

View File

@ -53,27 +53,49 @@ module MergeRequests
def close_issues(merge_request)
return unless merge_request.target_branch == project.default_branch
closed_issues = merge_request.visible_closing_issues_for(current_user)
closed_issues.each do |issue|
# We are intentionally only closing Issues asynchronously (excluding ExternalIssues)
# as the worker only supports finding an Issue. We are also only experiencing
# SQL timeouts when closing an Issue.
if issue.is_a?(Issue)
next unless issue.autoclose_by_merged_closing_merge_request?
MergeRequests::CloseIssueWorker.perform_async(
project.id,
current_user.id,
issue.id,
merge_request.id
)
else
Issues::CloseService.new(container: project, current_user: current_user).execute(issue, commit: merge_request)
if merge_request.target_project.has_external_issue_tracker?
merge_request.closes_issues(current_user).each do |issue|
close_issue(issue, merge_request)
end
else
merge_request.merge_requests_closing_issues.preload_issue.find_each(batch_size: 100) do |closing_issue| # rubocop:disable CodeReuse/ActiveRecord -- Would require exact redefinition of the method
close_issue(closing_issue.issue, merge_request, !closing_issue.from_mr_description)
end
end
end
def close_issue(issue, merge_request, skip_authorization = false)
# We are intentionally only closing Issues asynchronously (excluding ExternalIssues)
# as the worker only supports finding an Issue. We are also only experiencing
# SQL timeouts when closing an Issue.
if issue.is_a?(Issue)
# Doing this check here only to save a scheduled worker. The worker will also do this policy check.
return if !skip_authorization && !current_user.can?(:update_issue, issue)
return unless issue.autoclose_by_merged_closing_merge_request?
MergeRequests::CloseIssueWorker.perform_async(
*close_worker_arguments(issue, merge_request, skip_authorization)
)
else
Issues::CloseService.new(container: project, current_user: current_user).execute(issue, commit: merge_request)
end
end
def close_worker_arguments(issue, merge_request, skip_authorization)
worker_arguments = [
project.id,
current_user.id,
issue.id,
merge_request.id
]
if Feature.enabled?(:mr_merge_skips_close_issue_authorization, project)
worker_arguments << { skip_authorization: skip_authorization }
end
worker_arguments
end
def delete_non_latest_diffs(merge_request)
DeleteNonLatestDiffsService.new(merge_request).execute
end

View File

@ -0,0 +1,12 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Duo Workflow application settings",
"type": "object",
"additionalProperties": false,
"properties": {
"duo_workflow_oauth_application_id": {
"type": "integer",
"description": "oauth application id for duo workflow"
}
}
}

View File

@ -15,7 +15,7 @@ module MergeRequests
# This worker only accepts ID of an Issue. We are intentionally using this
# worker to close Issues asynchronously as we only experience SQL timeouts
# when closing an Issue.
def perform(project_id, user_id, issue_id, merge_request_id)
def perform(project_id, user_id, issue_id, merge_request_id, params = {})
project = Project.find_by_id(project_id)
unless project
@ -46,7 +46,11 @@ module MergeRequests
Issues::CloseService
.new(container: project, current_user: user)
.execute(issue, commit: merge_request)
.execute(
issue,
commit: merge_request,
skip_authorization: !!params.with_indifferent_access[:skip_authorization]
)
end
end
end

View File

@ -0,0 +1,9 @@
---
name: mr_merge_skips_close_issue_authorization
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/471849
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/160914
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/475108
milestone: '17.3'
group: group::project management
type: beta
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: add_validation_for_push_rules
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121030
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/411901
milestone: '16.1'
type: development
group: group::source code
default_enabled: true

View File

@ -0,0 +1,9 @@
---
name: glql_integration
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/161942
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/476990
feature_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/14767
milestone: '17.3'
type: development
group: group::knowledge
default_enabled: false

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddDuoWorkflowToApplicationSettings < Gitlab::Database::Migration[2.2]
milestone '17.3'
def change
add_column :application_settings, :duo_workflow, :jsonb, default: {}, null: true
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
class AddDuoWorkflowHashConstraintToApplicationSetting < Gitlab::Database::Migration[2.2]
milestone '17.3'
disable_ddl_transaction!
CONSTRAINT_NAME = 'check_application_settings_duo_workflow_is_hash'
def up
add_check_constraint(
:application_settings,
"(jsonb_typeof(duo_workflow) = 'object')",
CONSTRAINT_NAME
)
end
def down
remove_check_constraint :application_settings, CONSTRAINT_NAME
end
end

View File

@ -0,0 +1 @@
5c547fdc271a155354843e424c47ebe3e643688d5e013a96501edf051baf63b3

View File

@ -0,0 +1 @@
902245f1d45510a06f5bba17841cfff0a6c7391beeef2e46638ffed5353a7935

View File

@ -5882,6 +5882,7 @@ CREATE TABLE application_settings (
code_suggestions_api_rate_limit integer DEFAULT 60 NOT NULL,
ai_action_api_rate_limit integer DEFAULT 160 NOT NULL,
require_personal_access_token_expiry boolean DEFAULT true NOT NULL,
duo_workflow jsonb DEFAULT '{}'::jsonb,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),
@ -5932,6 +5933,7 @@ CREATE TABLE application_settings (
CONSTRAINT check_app_settings_sentry_clientside_traces_sample_rate_range CHECK (((sentry_clientside_traces_sample_rate >= (0)::double precision) AND (sentry_clientside_traces_sample_rate <= (1)::double precision))),
CONSTRAINT check_application_settings_clickhouse_is_hash CHECK ((jsonb_typeof(clickhouse) = 'object'::text)),
CONSTRAINT check_application_settings_code_creation_is_hash CHECK ((jsonb_typeof(code_creation) = 'object'::text)),
CONSTRAINT check_application_settings_duo_workflow_is_hash CHECK ((jsonb_typeof(duo_workflow) = 'object'::text)),
CONSTRAINT check_application_settings_importers_is_hash CHECK ((jsonb_typeof(importers) = 'object'::text)),
CONSTRAINT check_application_settings_package_registry_is_hash CHECK ((jsonb_typeof(package_registry) = 'object'::text)),
CONSTRAINT check_application_settings_rate_limits_is_hash CHECK ((jsonb_typeof(rate_limits) = 'object'::text)),

View File

@ -0,0 +1,181 @@
---
stage: AI-powered
group: AI Framework
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
---
# Setting up local development for Duo Workflow
## Prerequisites
- Docker
- NOTE: We aren't allowed to use [Docker Desktop](https://handbook.gitlab.com/handbook/tools-and-tips/mac/#docker-desktop).
- [AI Gateway](../ai_features/index.md)
## Setting up Duo Workflow
### Set up Duo Workflow Service
1. Clone the [Duo Workflow Service](https://gitlab.com/gitlab-org/duo-workflow/duo-workflow-service)
```shell
git clone git@gitlab.com:gitlab-org/duo-workflow/duo-workflow-service.git
```
1. Navigate to the Duo Workflow Service directory
1. Install dependencies
```shell
poetry install
```
1. Copy the example env file in the Service repo. Enter your Anthropic API key.
```shell
cp .env.example .env
```
```dotenv
ANTHROPIC_API_KEY=<YOUR_API_KEY>
```
You can disable auth for local development by setting DUO_WORKFLOW_AUTH__ENABLED=false in `.env`
1. Run Duo Workflow Service
```shell
poetry run python -m duo_workflow_service.server
```
1. In your local GitLab instance enable the `duo_workflow` feature flag from the rails console:
```ruby
Feature.enable(:duo_workflow)
```
1. Create a [personal access token](../../user/profile/personal_access_tokens.md) in your local GitLab instance with `api` scope
1. Generate a workflow ID. Replace `$GITLAB_TOKEN` with a PAT from your local GitLab instance.
```shell
curl POST --verbose \
--header "Authorization: Bearer $GITLAB_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"project_id": "7"
}' \
http://<GDK_HOST>:3000/api/v4/ai/duo_workflows/workflows
```
### Set up the Duo Workflow Executor
1. Clone the [Duo Workflow Executor](https://gitlab.com/gitlab-org/duo-workflow/duo-workflow-executor)
1. Navigate to the Duo Workflow Executor directory
1. Create a Dockerfile
```Dockerfile
FROM alpine
RUN apk add go busybox-extras git bash
```
1. Run the executor with your GitLab token and workflow ID. Below is an example prompt goal
```shell
make && \
./bin/duo-workflow-executor \
--goal='Can you fix the pipeline for the Merge request: 1 in the project 60003631.
You will have to clone the repository if you need to fix files. You can use the `run_command` tool to do so."
Please also checkout the right branch before making the changes.
You can fetch the repository name from the `get_project` tool.
Once you have fixed the pipeline please push the code to the same branch.' \
--workflow-id=$WORKFLOW_ID \
--token=$GITLAB_TOKEN \
--base-url="http://<GDK_HOST>:3000" \
--realm="saas" \
--userID="777"
```
You can also find more instructions to [run the executor locally in a Docker container](https://gitlab.com/gitlab-org/duo-workflow/duo-workflow-executor/-/blob/9dbe47ce3c61e1274af184f595ae5af1417d7a39/README.md).
1. Verify that the checkpoints for workflow have been created
```shell
curl --verbose \
--header "Authorization: Bearer $GITLAB_TOKEN" \
http://<GDK_HOST>/api/v4/ai/duo_workflows/workflows/<workflow_id>/checkpoints
```
## Optional: Testing the Auth flow via GitLab
Please note that this work is in progress and only half-implemented. These instructions are left here in case they are useful for testing purposes.
1. Generate a Cloud Connector token and grab your GitLab instance ID from the rails console
```shell
cd <GDK-root>
gdk start
gdk rails console
```
To grab your Cloud Connector token:
```ruby
::Gitlab::CloudConnector::SelfIssuedToken.new(
audience: "gitlab-duo-workflow-service",
subject: Gitlab::CurrentSettings.uuid,
scopes: ["duo_workflow_generate_token"]).encoded
```
To grab your GitLab instance ID:
<!-- markdownlint-disable MD044 -->
```ruby
Gitlab::CurrentSettings.uuid
```
<!-- markdownlint-enable MD044 -->
1. Navigate to the [client.py file](https://gitlab.com/gitlab-org/duo-workflow/duo-workflow-service/-/blob/83b62846cad3cfb633c31c0c9aa02e4535f44941/duo_workflow_service/client.py#L32-38) and replace the values in this file with the token and instance ID from the previous step.
```python
token = "<set-your-local-gdk-token>"
# To get your gitlab_instance_id, run in gdk rails console:
# ```
# puts Gitlab::CurrentSettings.uuid
# ```
gitlab_instance_id = "<set-your-local-gdk-instance-id>"
```
1. Update the metadata in the [client.py file](https://gitlab.com/gitlab-org/duo-workflow/duo-workflow-service/-/blob/83b62846cad3cfb633c31c0c9aa02e4535f44941/duo_workflow_service/client.py#L40-46) to the following:
```python
metadata = [
("authorization", f"Bearer {token}"),
("x-gitlab-authentication-type", "oidc"),
("x-gitlab-realm", "saas"),
("x-gitlab-instance-id", gitlab_instance_id),
("x-gitlab-global-user-id", "777"),
]
```
## Troubleshooting
### Issues connecting to 50052 port
JAMF may be listening on the `50052` port which will conflict with Duo Workflow Service.
```shell
$ sudo lsof -i -P | grep LISTEN | grep :50052
jamfRemot <redacted> root 11u IPv4 <redacted> 0t0 TCP localhost:50052 (LISTEN)
```
To work around this,run the serveron 50053 with:
```shell
PORT=50053 poetry run duo-workflow-service
```

View File

@ -24,6 +24,53 @@ Download the extension from the [JetBrains Plugin Marketplace](https://plugins.j
Instructions for getting started can be found in the project README under [setup](https://gitlab.com/gitlab-org/editor-extensions/gitlab-jetbrains-plugin#setup).
### Add a custom certificate for Code Suggestions
> - [Introduced](https://gitlab.com/gitlab-org/editor-extensions/gitlab-jetbrains-plugin/-/issues/561) in GitLab Duo 2.10.0.
GitLab Duo attempts to detect [trusted root certificates](https://www.jetbrains.com/help/idea/ssl-certificates.html)
without configuration on your part. If needed, you can configure your JetBrains IDE to allow the GitLab Duo plugin
to connect to your GitLab instance using a custom certificate.
To use a custom certificate:
1. In your IDE, on the top bar, select your IDE name, then select **Settings**.
1. On the left sidebar, select **Tools > GitLab Duo**.
1. Under **Connection**, enter the **URL to GitLab instance**.
1. To verify your connection, select **Verify setup**.
1. Select **OK**.
If your IDE detects a non-trusted certificate:
1. The GitLab Duo plugin displays a confirmation dialog.
1. Review the certificate details shown.
- Confirm that when you connect to GitLab in your browser, you see the same certificate details.
1. If the certificate matches your expectations, select **Accept**.
To review certificates you've already accepted:
1. In your IDE, on the top bar, select your IDE name, then select **Settings**.
1. On the left sidebar, select **Tools > Server Certificates**.
1. Select [**Server Certificates**](https://www.jetbrains.com/help/idea/settings-tools-server-certificates.html).
1. Select a certificate to view it.
### Allow a custom certificate for Code Suggestions
GitLab Duo attempts to pass custom certificate details to the GitLab Language Server process without configuration on your part.
To enforce a specific custom certificate:
1. In your IDE, on the top bar, select your IDE name, then select **Settings**.
1. On the left sidebar, select **Tools > GitLab Duo**.
1. Under **Advanced**, select **GitLab Language Server**.
1. Under **GitLab Language Server**, select **HTTP Agent Options**.
1. Under **HTTP Agent Options**:
1. For **Certificate authority (CA)**, enter the full path to your server's certificate authority.
1. Optional. In **Certificate**, enter the full file path to your client certificate.
1. Optional. In **Certificate key**, enter the full file path to your private key.
1. Insert the full path to the PEM-encoded certificate authority.
1. Select **OK**.
### Integrate with 1Password CLI
DETAILS:
@ -61,6 +108,25 @@ From the IDE:
1. Optional. To verify your credentials, select **Verify setup**.
1. Select **OK**.
## Troubleshooting
### Error: `unable to find valid certification path to requested target`
The GitLab Duo plugin verifies TLS certificate information before connecting to your GitLab instance.
If necessary you can [allow a custom certificate](#allow-a-custom-certificate-for-code-suggestions).
### Error: `Failed to check token`
This error occurs when the provided connection instance URL and authentication token passed through to the
GitLab Language Server process are invalid. To re-enable code suggestions:
1. In your IDE, on the top bar, select your IDE name, then select **Settings**.
1. On the left sidebar, select **Tools > GitLab Duo**.
1. Under **Connection**, select **Verify setup**.
1. Update your **Connection** details as needed.
1. Select **Verify setup**, and confirm that authentication succeeds.
1. Select **OK**.
## Report issues with the extension
Report any issues, bugs, or feature requests in the

View File

@ -23,6 +23,8 @@ module Gitlab
# Scopes for Duo
AI_FEATURES = :ai_features
AI_FEATURES_SCOPES = [AI_FEATURES].freeze
AI_WORKFLOW = :ai_workflows
AI_WORKFLOW_SCOPES = [AI_WORKFLOW].freeze
PROFILE_SCOPE = :profile
EMAIL_SCOPE = :email
@ -415,7 +417,7 @@ module Gitlab
# Other available scopes
def optional_scopes
all_available_scopes + OPENID_SCOPES + PROFILE_SCOPES - DEFAULT_SCOPES
all_available_scopes + OPENID_SCOPES + PROFILE_SCOPES - DEFAULT_SCOPES + AI_WORKFLOW_SCOPES
end
def registry_scopes
@ -448,7 +450,12 @@ module Gitlab
end
def unavailable_scopes_for_resource(resource)
unavailable_observability_scopes_for_resource(resource)
unavailable_ai_features_scopes +
unavailable_observability_scopes_for_resource(resource)
end
def unavailable_ai_features_scopes
AI_WORKFLOW_SCOPES
end
def unavailable_observability_scopes_for_resource(resource)

View File

@ -4993,7 +4993,7 @@ msgstr ""
msgid "AiImpactAnalytics|Code Suggestions usage"
msgstr ""
msgid "AiImpactAnalytics|Duo Pro seats: Assigned and used"
msgid "AiImpactAnalytics|Duo seats: Assigned and used"
msgstr ""
msgid "AiImpactAnalytics|Monthly user engagement with AI Code Suggestions. Percentage ratio calculated as monthly unique Code Suggestions users / total monthly unique code contributors."

View File

@ -57,8 +57,9 @@ task :delete_projects, [:dry_run] do |_, args|
end
desc "Deletes test users"
task :delete_test_users, [:delete_before, :dry_run, :exclude_users] do |_, args|
QA::Tools::DeleteTestUsers.new(args).run
task :delete_test_users, [:dry_run, :exclude_users] do |_, args|
args.with_defaults(dry_run: false, exclude_users: nil)
QA::Tools::DeleteTestUsers.new(dry_run: args[:dry_run], exclude_users: args[:exclude_users]).run
end
desc "Deletes snippets"

View File

@ -58,27 +58,46 @@ module QA
# Deletes a list of resources
#
# @param [Array<Hash>] resources
# @param [Boolean] wait until the end of the script to verify deletions. used for deletions that take a long time
# @param [Hash] API call options
# @return [Array<String, Hash>] results
def delete_resources(resources)
def delete_resources(resources, delayed_verification = false, **options)
logger.info("Deleting #{resources.length} #{@type}s...\n")
resources.filter_map do |resource|
unverified_deletions = []
results = []
resources.each do |resource|
path = resource_path(resource)
logger.info("Deleting #{@type} #{path}...")
delete_resource(resource)
result = delete_resource(resource, delayed_verification, **options)
if result.is_a?(Array)
results.append(result)
else
unverified_deletions << result
end
end
results.concat(verify_deletions(unverified_deletions)) unless unverified_deletions.empty?
results
end
# Deletes a given resource
#
# @param [<Hash>] resource
# @param [Boolean] wait until the end of the script to verify deletion
# @param [Hash] API call options
# @return [Array<String, Hash>] results
def delete_resource(resource)
def delete_resource(resource, delayed_verification = false, **options)
# If delayed deletion is not enabled, resource will be permanently deleted
response = delete(resource_request(resource))
response = delete(resource_request(resource, **options))
if success?(response&.code) || response.include?("already marked for deletion")
return resource if delayed_verification
wait_for_resource_deletion(resource)
return log_permanent_deletion(resource) if permanently_deleted?(resource)
@ -300,12 +319,27 @@ module QA
personal_access_token: token)
end
def verify_deletions(unverified_deletions)
logger.info('Verifying deletions...')
unverified_deletions.filter_map do |resource|
wait_for_resource_deletion(resource, permanent: true)
response = get(resource_request(resource))
if response&.code == HTTP_STATUS_NOT_FOUND
log_permanent_deletion(resource)
else
log_failure(resource, response)
end
end
end
# Wait for resource to be deleted (resource cannot be found or resource has been marked for deletion)
#
# @param resource [Hash] Resource to wait for deletion for
# @return [Boolean] Whether the resource was deleted
def wait_for_resource_deletion(resource, permanent = false)
wait_until(max_duration: 60, sleep_interval: 1, raise_on_failure: false) do
wait_until(max_duration: 160, sleep_interval: 1, raise_on_failure: false) do
response = get(resource_request(resource))
deleted = response&.code == HTTP_STATUS_NOT_FOUND

View File

@ -1,86 +1,69 @@
# frozen_string_literal: true
# This script deletes users with a username starting with "qa-user-"
# - Specify `delete_before` to delete only keys that were created before the given date (default: yesterday)
# - If `dry_run` is true the script will list the users to be deleted by username, but it won't delete them
# This script deletes users with a username starting with "qa-user-" or "test-user-"
# - If `dry_run` is true the script will list the users to be deleted, but it won't delete them
# - Specify `exclude_users` as a comma-separated list of usernames to not delete.
#
# Required environment variables: GITLAB_QA_ADMIN_ACCESS_TOKEN and GITLAB_ADDRESS
# Required environment variables: GITLAB_QA_ADMIN_ACCESS_TOKEN, GITLAB_QA_ACCESS_TOKEN, and GITLAB_ADDRESS
# - GITLAB_QA_ADMIN_ACCESS_TOKEN must have admin API access
# Optional environment variables: DELETE_BEFORE (default: 1 day ago)
# - Set DELETE_BEFORE to only delete users that were created before a given date, otherwise defaults to 1 day ago
# Run `rake delete_test_users`
module QA
module Tools
class DeleteTestUsers
include Support::API
class DeleteTestUsers < DeleteResourceBase
EXCLUDE_USERS = %w[gitlab-qa gitlab-qa-user-for-ai].freeze
ITEMS_PER_PAGE = '100'
EXCLUDE_USERS = %w[qa-user-abc123].freeze
FALSY_VALUES = %w[false no 0].freeze
def initialize(dry_run: false, exclude_users: nil)
super(dry_run: dry_run)
def initialize(delete_before: (Date.today - 1).to_s, dry_run: 'false', exclude_users: nil)
raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS']
raise ArgumentError, "Please provide GITLAB_QA_ADMIN_ACCESS_TOKEN" unless ENV['GITLAB_QA_ADMIN_ACCESS_TOKEN']
@api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['GITLAB_QA_ADMIN_ACCESS_TOKEN'])
@dry_run = FALSY_VALUES.exclude?(dry_run.to_s.downcase)
@delete_before = Date.parse(delete_before)
@page_no = '1'
@exclude_users = Array(exclude_users.to_s.split(',')) + EXCLUDE_USERS
@type = 'user'
end
def run
puts "Deleting users with a username starting with 'qa-user-' or 'test-user-' created before #{@delete_before}..."
users = fetch_test_users
while page_no.present?
users = fetch_test_users
results = delete_test_users(users)
delete_test_users(users) if users.present?
end
puts "\nDone"
log_results(results)
end
private
attr_reader :dry_run, :page_no
alias_method :dry_run?, :dry_run
def fetch_test_users
puts "Fetching QA test users from page #{page_no}..."
users = fetch_resources("/users")
response = get Runtime::API::Request.new(@api_client, "/users", page: page_no, per_page: ITEMS_PER_PAGE).url
# When we reach the last page, the x-next-page header is a blank string
@page_no = response.headers[:x_next_page].to_s
if @page_no.to_i > 1000
puts "Finishing early to avoid timing out the CI job"
exit
end
JSON.parse(response.body).select do |user|
user['username'].start_with?('qa-user-', 'test-user-') \
&& (user['name'] == 'QA Tests' || user['name'].start_with?('QA User')) \
&& @exclude_users.exclude?(user['username']) \
&& Date.parse(user.fetch('created_at', Date.today.to_s)) < @delete_before
users.select do |user|
user[:username].start_with?('qa-user-', 'test-user-') \
&& user[:name].start_with?('QA User', 'QA Test') \
&& @exclude_users.exclude?(user[:username]) \
&& Date.parse(user.fetch(:created_at, Date.today.to_s)) < @delete_before
end
end
def delete_test_users(users)
usernames = users.map { |user| user['username'] }.join(', ')
if dry_run?
puts "Dry run: found users with usernames #{usernames}"
if @dry_run
log_dry_run_output(users)
return
end
puts "Deleting #{users.length} users with usernames #{usernames}..."
users.each do |user|
delete_response = delete Runtime::API::Request.new(@api_client, "/users/#{user['id']}", hard_delete: 'true').url
dot_or_f = delete_response.code == 204 ? "\e[32m.\e[0m" : "\e[31mF\e[0m"
print dot_or_f
if users.empty?
logger.info("No users found\n")
return
end
print "\n"
delete_resources(users, true, hard_delete: 'true')
end
def resource_request(user, **options)
Runtime::API::Request.new(@api_client, "/users/#{user[:id]}", **options).url
end
end
end

View File

@ -27,6 +27,16 @@ RSpec.describe UserSettings::PersonalAccessTokensController, feature_category: :
expect(PersonalAccessToken.active).to include(created_token)
end
it "does not allow creation of a token with workflow scope" do
name = 'My PAT'
scopes = %w[ai_workflow]
post :create, params: { personal_access_token: token_attributes.merge(scopes: scopes, name: name) }
expect(created_token).to be_nil
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
it "allows creation of a token with an expiry date" do
expires_at = 5.days.from_now.to_date

View File

@ -88,9 +88,25 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate
it 'optional_scopes contains all non-default scopes' do
expect(subject.optional_scopes).to match_array %i[
read_user read_api read_repository write_repository read_registry read_service_ping
write_registry sudo admin_mode openid profile email read_observability write_observability
create_runner manage_runner k8s_proxy ai_features
admin_mode
ai_features
ai_workflows
create_runner
email
k8s_proxy
manage_runner
openid
profile
read_api
read_observability
read_registry
read_repository
read_service_ping
read_user
sudo
write_observability
write_registry
write_repository
]
end

View File

@ -3837,6 +3837,13 @@ RSpec.describe Group, feature_category: :groups_and_projects do
end
end
describe '#glql_integration_feature_flag_enabled?' do
it_behaves_like 'checks self and root ancestor feature flag' do
let(:feature_flag) { :glql_integration }
let(:feature_flag_method) { :glql_integration_feature_flag_enabled? }
end
end
describe '#supports_lock_on_merge?' do
it_behaves_like 'checks self and root ancestor feature flag' do
let(:feature_flag) { :enforce_locked_labels_on_merge }

View File

@ -8939,30 +8939,20 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
describe '#work_items_feature_flag_enabled?' do
let_it_be(:group_project) { create(:project, :in_subgroup) }
it_behaves_like 'checks parent group feature flag' do
it_behaves_like 'checks parent group and self feature flag' do
let(:feature_flag_method) { :work_items_feature_flag_enabled? }
let(:feature_flag) { :work_items }
let(:subject_project) { group_project }
end
end
context 'when feature flag is enabled for the project' do
subject { subject_project.work_items_feature_flag_enabled? }
describe '#glql_integration_feature_flag_enabled?' do
let_it_be(:group_project) { create(:project, :in_subgroup) }
before do
stub_feature_flags(work_items: subject_project)
end
context 'when project belongs to a group' do
let(:subject_project) { group_project }
it { is_expected.to be_truthy }
end
context 'when project does not belong to a group' do
let(:subject_project) { create(:project, namespace: create(:namespace)) }
it { is_expected.to be_truthy }
end
it_behaves_like 'checks parent group and self feature flag' do
let(:feature_flag_method) { :glql_integration_feature_flag_enabled? }
let(:feature_flag) { :glql_integration }
let(:subject_project) { group_project }
end
end

View File

@ -14,5 +14,7 @@ RSpec.describe Admin::ApplicationsController, :enable_admin_mode, feature_catego
include_examples 'applications controller - GET #show'
include_examples 'applications controller - GET #new'
include_examples 'applications controller - POST #create'
end

View File

@ -16,5 +16,7 @@ RSpec.describe Groups::Settings::ApplicationsController, feature_category: :syst
include_examples 'applications controller - GET #show'
include_examples 'applications controller - GET #new'
include_examples 'applications controller - POST #create'
end

View File

@ -14,5 +14,7 @@ RSpec.describe Oauth::ApplicationsController, feature_category: :system_access d
include_examples 'applications controller - GET #show'
include_examples 'applications controller - GET #new'
include_examples 'applications controller - POST #create'
end

View File

@ -273,7 +273,7 @@ RSpec.describe 'OpenID Connect requests', feature_category: :system_access do
let(:expected_scopes) do
%w[
admin_mode api read_user read_api read_repository write_repository sudo openid profile email
read_observability write_observability create_runner manage_runner k8s_proxy ai_features read_service_ping
read_observability write_observability create_runner manage_runner k8s_proxy ai_features read_service_ping ai_workflows
]
end

View File

@ -250,7 +250,11 @@ RSpec.describe MergeRequests::MergeService, feature_category: :code_review_workf
end
end
context 'closes related issues' do
context 'closes related issues', :sidekiq_inline do
let_it_be_with_refind(:group) { create(:group) }
let_it_be_with_refind(:other_project) { create(:project, group: group) }
let_it_be(:other_issue) { create(:issue, project: other_project) }
let_it_be(:group_issue) { create(:issue, :group_level, namespace: group) }
let(:issue1) { create(:issue, project: project) }
let(:issue2) { create(:issue, project: project) }
let(:commit) do
@ -268,7 +272,7 @@ RSpec.describe MergeRequests::MergeService, feature_category: :code_review_workf
)
end
it 'closes GitLab issue tracker issues', :sidekiq_inline do
it 'closes GitLab issue tracker issues' do
merge_request.cache_merge_request_closes_issues!
expect do
@ -278,21 +282,74 @@ RSpec.describe MergeRequests::MergeService, feature_category: :code_review_workf
)
end
context 'when issue project has auto close disabled', :sidekiq_inline do
let_it_be(:group) { create(:group) }
let_it_be(:other_project) { create(:project, autoclose_referenced_issues: false, group: group) }
let_it_be(:no_close_issue) { create(:issue, project: other_project) }
let_it_be(:group_issue) { create(:issue, :group_level, namespace: group) }
context 'when closing issues exist in a namespace the merging user doesn\'t have access to' do
context 'when the closing work item was created in the merge request description' do
before do
create(
:merge_requests_closing_issues,
issue: other_issue,
merge_request: merge_request,
from_mr_description: true
)
create(
:merge_requests_closing_issues,
issue: group_issue,
merge_request: merge_request,
from_mr_description: true
)
end
it 'does not close the related issues' do
merge_request.cache_merge_request_closes_issues!
expect do
service.execute(merge_request)
end.to not_change { other_issue.reload.opened? }.from(true).and(
not_change { group_issue.reload.opened? }.from(true)
)
end
end
context 'when the closing work item was not created in the merge request description' do
before do
create(
:merge_requests_closing_issues,
issue: other_issue,
merge_request: merge_request,
from_mr_description: false
)
create(
:merge_requests_closing_issues,
issue: group_issue,
merge_request: merge_request,
from_mr_description: false
)
end
it 'closes the related issues' do
merge_request.cache_merge_request_closes_issues!
expect do
service.execute(merge_request)
end.to change { other_issue.reload.opened? }.from(true).to(false).and(
# Autoclose is disabled for group level issues until we introduce a setting at the grouo level
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/472907
not_change { group_issue.reload.opened? }.from(true)
)
end
end
end
context 'when issue project has auto close disabled' do
before_all do
other_project.add_developer(user)
other_project.update!(autoclose_referenced_issues: false)
group.add_developer(user)
end
before do
create(
:merge_requests_closing_issues,
issue: no_close_issue,
issue: other_issue,
merge_request: merge_request,
from_mr_description: false
)
@ -312,7 +369,7 @@ RSpec.describe MergeRequests::MergeService, feature_category: :code_review_workf
end.to change { issue1.reload.closed? }.from(false).to(true).and(
change { issue2.reload.closed? }.from(false).to(true)
).and(
not_change { no_close_issue.reload.opened? }.from(true)
not_change { other_issue.reload.opened? }.from(true)
).and(
not_change { group_issue.reload.opened? }.from(true)
)

View File

@ -7,7 +7,7 @@ RSpec.describe MergeRequests::PostMergeService, feature_category: :code_review_w
let_it_be(:user) { create(:user) }
let_it_be(:merge_request, reload: true) { create(:merge_request, assignees: [user]) }
let_it_be(:project) { merge_request.project }
let_it_be(:project, reload: true) { merge_request.project }
let(:params) { {} }
subject { described_class.new(project: project, current_user: user, params: params).execute(merge_request) }
@ -85,22 +85,47 @@ RSpec.describe MergeRequests::PostMergeService, feature_category: :code_review_w
merge_request.update!(target_branch: 'foo')
allow(project).to receive(:default_branch).and_return('foo')
allow(merge_request).to receive(:visible_closing_issues_for).and_return([issue])
allow(merge_request).to receive(:closes_issues).and_return([issue])
end
it 'performs MergeRequests::CloseIssueWorker asynchronously' do
create(:merge_requests_closing_issues, merge_request: merge_request, issue: issue)
expect(MergeRequests::CloseIssueWorker)
.to receive(:perform_async)
.with(project.id, user.id, issue.id, merge_request.id)
.with(project.id, user.id, issue.id, merge_request.id, { skip_authorization: false })
subject
expect(merge_request.reload).to be_merged
end
context 'when mr_merge_skips_close_issue_authorization feature flag is disabled' do
before do
stub_feature_flags(mr_merge_skips_close_issue_authorization: false)
end
it 'does not use the last optional argument of the worker' do
create(:merge_requests_closing_issues, merge_request: merge_request, issue: issue)
expect(MergeRequests::CloseIssueWorker)
.to receive(:perform_async)
.with(project.id, user.id, issue.id, merge_request.id)
subject
expect(merge_request.reload).to be_merged
end
end
context 'when issue is an external issue' do
let_it_be(:issue) { ExternalIssue.new('JIRA-123', project) }
before do
project.update!(has_external_issue_tracker: true)
merge_request.reload
end
it 'executes Issues::CloseService' do
expect_next_instance_of(Issues::CloseService) do |close_service|
expect(close_service).to receive(:execute).with(issue, commit: merge_request)

View File

@ -61,6 +61,30 @@ RSpec.shared_examples 'checks parent group feature flag' do
end
end
RSpec.shared_examples 'checks parent group and self feature flag' do
it_behaves_like 'checks parent group feature flag'
context 'when feature flag is enabled for the project' do
before do
stub_feature_flags(feature_flag => subject_project)
end
context 'when project belongs to a group' do
let(:subject_project) { group_project }
it { is_expected.to be_truthy }
end
context 'when project does not belong to a group' do
let(:subject_project) do
create(:project, namespace: create(:namespace))
end
it { is_expected.to be_truthy }
end
end
end
RSpec.shared_examples 'refreshes project.lfs_file_locks_changed_epoch value' do
it 'updates the lfs_file_locks_changed_epoch value', :clean_gitlab_redis_cache do
travel_to(1.hour.ago) { project.refresh_lfs_file_locks_changed_epoch }

View File

@ -10,6 +10,32 @@ RSpec.shared_examples 'applications controller - GET #show' do
end
end
RSpec.shared_examples 'applications controller - GET #new' do
it "sets `@scopes` to list of all scopes that should be shown" do
create_application
expect(assigns[:scopes]).to match_array(%w[
admin_mode
ai_features
api
create_runner
email
k8s_proxy
manage_runner
openid
profile
read_api
read_observability
read_repository
read_service_ping
read_user
sudo
write_observability
write_repository
])
end
end
RSpec.shared_examples 'applications controller - POST #create' do
it "sets `@created` instance variable to `true`" do
create_application

View File

@ -13,7 +13,7 @@ RSpec.describe MergeRequests::CloseIssueWorker, feature_category: :code_review_w
it 'calls the close issue service' do
expect_next_instance_of(Issues::CloseService, container: project, current_user: user) do |service|
expect(service).to receive(:execute).with(issue, commit: merge_request)
expect(service).to receive(:execute).with(issue, commit: merge_request, skip_authorization: false)
end
subject.perform(project.id, user.id, issue.id, merge_request.id)

View File

@ -0,0 +1,5 @@
include:
- local: gems/gem.gitlab-ci.yml
inputs:
gem_name: "gitlab-duo-workflow-service-client"
gem_path_prefix: "vendor/gems/"

View File

@ -0,0 +1 @@
@gitlab-org/ai-powered/ai-framework/engineering/all

View File

@ -0,0 +1,5 @@
# Duo Workflow Service Client Gem
This Ruby Gem is meant to initialize a client to the Duo Workflow Service
This Gem is automatically generated via GRPC from the
repository https://gitlab.com/gitlab-org/duo-workflow/duo-workflow-service

View File

@ -0,0 +1,6 @@
# frozen_string_literal: true
source "https://rubygems.org"
# Specify your gem's dependencies in ruby.gemspec
gemspec

View File

@ -0,0 +1,142 @@
PATH
remote: .
specs:
gitlab-duo-workflow-service-client (0.1)
grpc
GEM
remote: https://rubygems.org/
specs:
activesupport (7.1.3.2)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
minitest (>= 5.1)
mutex_m
tzinfo (~> 2.0)
ast (2.4.2)
base64 (0.2.0)
bigdecimal (3.1.8)
binding_of_caller (1.0.1)
debug_inspector (>= 1.2.0)
concurrent-ruby (1.2.3)
connection_pool (2.4.1)
debug_inspector (1.2.0)
diff-lcs (1.5.1)
drb (2.2.1)
gitlab-styles (10.1.0)
rubocop (~> 1.50.2)
rubocop-graphql (~> 0.18)
rubocop-performance (~> 1.15)
rubocop-rails (~> 2.17)
rubocop-rspec (~> 2.22)
google-protobuf (4.27.3)
bigdecimal
rake (>= 13)
googleapis-common-protos-types (1.15.0)
google-protobuf (>= 3.18, < 5.a)
grpc (1.65.2)
google-protobuf (>= 3.25, < 5.0)
googleapis-common-protos-types (~> 1.0)
grpc-tools (1.65.2)
i18n (1.14.5)
concurrent-ruby (~> 1.0)
json (2.7.1)
minitest (5.22.3)
mutex_m (0.2.0)
parallel (1.24.0)
parser (3.3.0.5)
ast (~> 2.4.1)
racc
proc_to_ast (0.2.0)
parser
rouge
unparser
racc (1.7.3)
rack (3.0.11)
rainbow (3.1.1)
rake (13.2.1)
regexp_parser (2.9.0)
rexml (3.2.6)
rouge (4.3.0)
rspec (3.13.0)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.0)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-parameterized (1.0.2)
rspec-parameterized-core (< 2)
rspec-parameterized-table_syntax (< 2)
rspec-parameterized-core (1.0.1)
parser
proc_to_ast (>= 0.2.0)
rspec (>= 2.13, < 4)
unparser
rspec-parameterized-table_syntax (1.0.1)
binding_of_caller
rspec-parameterized-core (< 2)
rspec-support (3.13.0)
rubocop (1.50.2)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.2.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.28.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.30.0)
parser (>= 3.2.1.0)
rubocop-capybara (2.20.0)
rubocop (~> 1.41)
rubocop-factory_bot (2.25.1)
rubocop (~> 1.41)
rubocop-graphql (0.19.0)
rubocop (>= 0.87, < 2)
rubocop-performance (1.20.2)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.30.0, < 2.0)
rubocop-rails (2.23.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
rubocop-ast (>= 1.30.0, < 2.0)
rubocop-rspec (2.29.2)
rubocop (~> 1.40)
rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22)
rubocop-rspec_rails (~> 2.28)
rubocop-rspec_rails (2.28.3)
rubocop (~> 1.40)
ruby-progressbar (1.13.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0)
unparser (0.6.15)
diff-lcs (~> 1.3)
parser (>= 3.3.0)
PLATFORMS
ruby
DEPENDENCIES
gitlab-duo-workflow-service-client!
gitlab-styles (~> 10.1.0)
grpc-tools
rspec (~> 3.0)
rspec-parameterized (~> 1.0.2)
rubocop (~> 1.21)
BUNDLED WITH
2.4.4

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
require_relative "lib/gitlab/duo_workflow_service/version"
Gem::Specification.new do |spec|
spec.name = "gitlab-duo-workflow-service-client"
spec.version = Gitlab::DuoWorkflowService::VERSION
spec.authors = ["group::ai framework"]
spec.email = ["engineering@gitlab.com"]
spec.summary = "Client library to interact with the Duo Workflow Service"
spec.homepage = "https://gitlab.com/gitlab-org/duo-workflow/duo-workflow-service"
spec.license = "MIT"
spec.required_ruby_version = ">= 2.6.0"
spec.files = Dir['lib/**/*.rb']
spec.require_paths = ["lib"]
spec.add_dependency "grpc"
spec.add_development_dependency "gitlab-styles", "~> 10.1.0"
spec.add_development_dependency "rspec", "~> 3.0"
spec.add_development_dependency "rspec-parameterized", "~> 1.0.2"
spec.add_development_dependency "rubocop", "~> 1.21"
spec.add_development_dependency 'grpc-tools'
end

View File

@ -0,0 +1,5 @@
# frozen_string_literal: true
require_relative "duo_workflow_service/version"
require "proto/contract_services_pb"

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
module Gitlab
module DuoWorkflowService
VERSION = "0.1"
end
end

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: contract.proto
require 'google/protobuf'
descriptor_data = "\n\x0e\x63ontract.proto\"s\n\x0b\x43lientEvent\x12-\n\x0cstartRequest\x18\x01 \x01(\x0b\x32\x15.StartWorkflowRequestH\x00\x12)\n\x0e\x61\x63tionResponse\x18\x02 \x01(\x0b\x32\x0f.ActionResponseH\x00\x42\n\n\x08response\"k\n\x14StartWorkflowRequest\x12\x15\n\rclientVersion\x18\x01 \x01(\t\x12\x12\n\nworkflowID\x18\x02 \x01(\t\x12\x1a\n\x12workflowDefinition\x18\x03 \x01(\t\x12\x0c\n\x04goal\x18\x04 \x01(\t\"5\n\x0e\x41\x63tionResponse\x12\x11\n\trequestID\x18\x01 \x01(\t\x12\x10\n\x08response\x18\x02 \x01(\t\"\xbf\x01\n\x06\x41\x63tion\x12\x11\n\trequestID\x18\x01 \x01(\t\x12\'\n\nrunCommand\x18\x02 \x01(\x0b\x32\x11.RunCommandActionH\x00\x12)\n\x0erunHTTPRequest\x18\x03 \x01(\x0b\x32\x0f.RunHTTPRequestH\x00\x12 \n\x0brunReadFile\x18\x04 \x01(\x0b\x32\t.ReadFileH\x00\x12\"\n\x0crunWriteFile\x18\x05 \x01(\x0b\x32\n.WriteFileH\x00\x42\x08\n\x06\x61\x63tion\"#\n\x10RunCommandAction\x12\x0f\n\x07\x63ommand\x18\x01 \x01(\t\"\x1c\n\x08ReadFile\x12\x10\n\x08\x66ilepath\x18\x01 \x01(\t\"/\n\tWriteFile\x12\x10\n\x08\x66ilepath\x18\x01 \x01(\t\x12\x10\n\x08\x63ontents\x18\x02 \x01(\t\"J\n\x0eRunHTTPRequest\x12\x0e\n\x06method\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\t\x12\x11\n\x04\x62ody\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x07\n\x05_body\"\x16\n\x14GenerateTokenRequest\"9\n\x15GenerateTokenResponse\x12\r\n\x05token\x18\x01 \x01(\t\x12\x11\n\texpiresAt\x18\x02 \x01(\x03\x32{\n\x0b\x44uoWorkflow\x12,\n\x0f\x45xecuteWorkflow\x12\x0c.ClientEvent\x1a\x07.Action(\x01\x30\x01\x12>\n\rGenerateToken\x12\x15.GenerateTokenRequest\x1a\x16.GenerateTokenResponseBfZOgitlab.com/gitlab-org/ai-powered/ai-framework/duo_workflow_executor/pkg/service\xea\x02\x12\x44uoWorkflowServiceb\x06proto3"
pool = Google::Protobuf::DescriptorPool.generated_pool
pool.add_serialized_file(descriptor_data)
module DuoWorkflowService
ClientEvent = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ClientEvent").msgclass
StartWorkflowRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("StartWorkflowRequest").msgclass
ActionResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ActionResponse").msgclass
Action = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("Action").msgclass
RunCommandAction = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("RunCommandAction").msgclass
ReadFile = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("ReadFile").msgclass
WriteFile = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("WriteFile").msgclass
RunHTTPRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("RunHTTPRequest").msgclass
GenerateTokenRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("GenerateTokenRequest").msgclass
GenerateTokenResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("GenerateTokenResponse").msgclass
end

View File

@ -0,0 +1,23 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# Source: contract.proto for package 'DuoWorkflowService'
require 'grpc'
require_relative 'contract_pb'
module DuoWorkflowService
module DuoWorkflow
class Service
include ::GRPC::GenericService
self.marshal_class_method = :encode
self.unmarshal_class_method = :decode
self.service_name = 'DuoWorkflow'
rpc :ExecuteWorkflow, stream(::DuoWorkflowService::ClientEvent), stream(::DuoWorkflowService::Action)
rpc :GenerateToken, ::DuoWorkflowService::GenerateTokenRequest, ::DuoWorkflowService::GenerateTokenResponse
end
Stub = Service.rpc_stub_class
end
end