diff --git a/app/models/concerns/has_user_type.rb b/app/models/concerns/has_user_type.rb index d77aa8821b0413105a108a85ad7dbc41a0432308..48aac1f2c0fb61adcf7cbae2b468560c6fe3964b 100644 --- a/app/models/concerns/has_user_type.rb +++ b/app/models/concerns/has_user_type.rb @@ -21,7 +21,8 @@ module HasUserType llm_bot: 14, placeholder: 15, duo_code_review_bot: 16, - import_user: 17 + import_user: 17, + dependency_management_bot: 18, }.with_indifferent_access.freeze BOT_USER_TYPES = %w[ diff --git a/config/bounded_contexts.yml b/config/bounded_contexts.yml index e008c57b9a64317f8c0e72a201cb66962bbf63f0..dbfe07b35f95f4b738ca73b80bb792530741f3d9 100644 --- a/config/bounded_contexts.yml +++ b/config/bounded_contexts.yml @@ -128,6 +128,12 @@ domains: - virtual_registry - package_registry + DependencyManagement: + description: Management of dependency updates for project dependencies. + feature_categories: + - software_composition_analysis + - dependency_management + DesignManagement: description: feature_categories: diff --git a/ee/app/services/dependency_management/update_dependency_service.rb b/ee/app/services/dependency_management/update_dependency_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..595186303e5c374d91545682e52298016ddc6630 --- /dev/null +++ b/ee/app/services/dependency_management/update_dependency_service.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module DependencyManagement + class UpdateDependencyService + SOURCE = :dependency_update + + def initialize(project:, input_file:, component_name:, component_version:) + @project = project + @input_file = input_file + @component_name = component_version + @component_version = component_version + end + + def execute + # 1. Create a new branch to update the dependency + ref = ::Branches::CreateService.new(branch_name, project.default_branch) + # 2. Open a merge request to update the dependency + result = ::Ci::CreatePipelineService.new(project, dependency_management_bot, ref: ref).execute(SOURCE, content: pipeline_config) + + return result + # 3. Clean up if merge request could not be opened or is merged + end + + private + + attr_reader :project, :input_file, :component_name, :component_version + + def pipeline_config(dependency) + <<~YAML + inputs: + component_name: #{component_name} + copmonent_version: #{component_version} + --- + include: + - Jobs/Dependency-Management.gitlab-ci.yml + YAML + end + + def dependency_management_bot + ::Users::Internal.dependency_management_bot + end + + def branch_name + # TODO: sluggify name and version + "dependency-management/update-#{component_name}-#{component_version}" + end + end +end diff --git a/lib/gitlab/ci/templates/Jobs/Dependency-Management.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Dependency-Management.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..ef54a45afc6bfb435094511275db79f8ee90756e --- /dev/null +++ b/lib/gitlab/ci/templates/Jobs/Dependency-Management.gitlab-ci.yml @@ -0,0 +1,31 @@ +spec: + inputs: + image_host: + description: "Host for the dependency management container images" + type: "string" + default: "${CI_TEMPLATE_REGISTRY_HOST}/security-products" + image_version: + description: "Version of the dependency managers to use" + type: "string" + default: "1.0.0" + component_name: + description: "Name of the software component to update" + type: "string" + component_version: + description: "Version of the software component to update" + type: "string" + +--- + +.dependency-management-base: + script: echo noop + +gradle-dependency-management: + image: "$[[ inputs.image_prefix ]]/dependency-manager-gradle:$[[ inputs.image_version]]" + +maven-dependency-management: + image: "$[[ inputs.image_prefix ]]/dependency-manager-maven:$[[ inputs.image_version ]]" + +npm-dependency-management: + image: "$[[ inputs.image_prefix ]]/dependency-manager-npm:$[[ inputs.image_version]]" + diff --git a/lib/users/internal.rb b/lib/users/internal.rb index bee09537f865979395a19991d505276ba5359d11..96eecd24be8d6db0af74ab8d2f41f7d721092c17 100644 --- a/lib/users/internal.rb +++ b/lib/users/internal.rb @@ -9,7 +9,7 @@ class << self def_delegators :new, :bot_avatar, :ghost, :support_bot, :alert_bot, :migration_bot, :security_bot, :automation_bot, :llm_bot, - :duo_code_review_bot, :admin_bot + :duo_code_review_bot, :admin_bot, :dependency_management_bot def for_organization(organization) new(organization: organization) @@ -151,6 +151,19 @@ def bot_avatar(image:) Rails.root.join('lib', 'assets', 'images', 'bot_avatars', image).open end + def dependency_management_bot + email_pattern = "dependency-management%s@#{Settings.gitlab.host}" + + unique_internal(User.where(user_type: :dependency_management_bot), 'GitLabDependencyManagement', email_pattern) do |u| + u.bio = 'The GitLab Dependency Management bot used for updating project dependencies' + u.name = 'GitLab Dependency Management' + # TODO: Use dedicated avatar + u.avatar = bot_avatar(image: 'support-bot.png') + u.confirmed_at = Time.zone.now + u.private_profile = true + end + end + private # NOTE: This method is patched in spec/spec_helper.rb to allow use of exclusive lease in RSpec's