diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-14 03:10:45 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-14 03:10:45 +0300 |
commit | c7e53e67898682ac7ccb61525f66980989ddb75e (patch) | |
tree | 2b79592f82d78bbe393af80f73e5917f2b68540e | |
parent | 2d4f258f067f55894862e47ec32bbc54649d4741 (diff) |
Add latest changes from gitlab-org/gitlab@master
35 files changed, 847 insertions, 65 deletions
@@ -32,7 +32,7 @@ gem 'bcrypt', '~> 3.1', '>= 3.1.14' gem 'doorkeeper', '~> 5.5.0.rc2' gem 'doorkeeper-openid_connect', '~> 1.7.5' gem 'rexml', '~> 3.2.5' -gem 'ruby-saml', '~> 1.12.1' +gem 'ruby-saml', '~> 1.13.0' gem 'omniauth', '~> 1.8' gem 'omniauth-auth0', '~> 2.0.0' gem 'omniauth-azure-activedirectory-v2', '~> 1.0' diff --git a/Gemfile.lock b/Gemfile.lock index 8b8cd1691e0..50f396a5743 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1124,7 +1124,7 @@ GEM mini_portile2 (~> 2.5.0) ruby-prof (1.3.1) ruby-progressbar (1.11.0) - ruby-saml (1.12.1) + ruby-saml (1.13.0) nokogiri (>= 1.10.5) rexml ruby-statistics (2.1.2) @@ -1606,7 +1606,7 @@ DEPENDENCIES ruby-magic (~> 0.4) ruby-prof (~> 1.3.0) ruby-progressbar (~> 1.10) - ruby-saml (~> 1.12.1) + ruby-saml (~> 1.13.0) ruby_parser (~> 3.15) rubyzip (~> 2.0.0) rugged (~> 1.1) diff --git a/app/graphql/mutations/dependency_proxy/image_ttl_group_policy/update.rb b/app/graphql/mutations/dependency_proxy/image_ttl_group_policy/update.rb new file mode 100644 index 00000000000..a5eb114b2da --- /dev/null +++ b/app/graphql/mutations/dependency_proxy/image_ttl_group_policy/update.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module Mutations + module DependencyProxy + module ImageTtlGroupPolicy + class Update < Mutations::BaseMutation + include Mutations::ResolvesGroup + + graphql_name 'UpdateDependencyProxyImageTtlGroupPolicy' + + authorize :admin_dependency_proxy + + argument :group_path, + GraphQL::Types::ID, + required: true, + description: 'Group path for the group dependency proxy image TTL policy.' + + argument :enabled, + GraphQL::Types::Boolean, + required: false, + description: copy_field_description(Types::DependencyProxy::ImageTtlGroupPolicyType, :enabled) + + argument :ttl, + GraphQL::Types::Int, + required: false, + description: copy_field_description(Types::DependencyProxy::ImageTtlGroupPolicyType, :ttl) + + field :dependency_proxy_image_ttl_policy, + Types::DependencyProxy::ImageTtlGroupPolicyType, + null: true, + description: 'Group image TTL policy after mutation.' + + def resolve(group_path:, **args) + group = authorized_find!(group_path: group_path) + + result = ::DependencyProxy::ImageTtlGroupPolicies::UpdateService + .new(container: group, current_user: current_user, params: args) + .execute + + { + dependency_proxy_image_ttl_policy: result.payload[:dependency_proxy_image_ttl_policy], + errors: result.errors + } + end + + private + + def find_object(group_path:) + resolve_group(full_path: group_path) + end + end + end + end +end diff --git a/app/graphql/types/dependency_proxy/image_ttl_group_policy_type.rb b/app/graphql/types/dependency_proxy/image_ttl_group_policy_type.rb new file mode 100644 index 00000000000..29bba7122d0 --- /dev/null +++ b/app/graphql/types/dependency_proxy/image_ttl_group_policy_type.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Types + class DependencyProxy::ImageTtlGroupPolicyType < BaseObject + graphql_name 'DependencyProxyImageTtlGroupPolicy' + + description 'Group-level Dependency Proxy TTL policy settings' + + authorize :read_dependency_proxy + + field :enabled, GraphQL::Types::Boolean, null: false, description: 'Indicates whether the policy is enabled or disabled.' + field :ttl, GraphQL::Types::Int, null: true, description: 'Number of days to retain a cached image file.' + field :created_at, Types::TimeType, null: true, description: 'Timestamp of creation.' + field :updated_at, Types::TimeType, null: true, description: 'Timestamp of the most recent update.' + end +end diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb index 80b87044298..8fe4ba557ea 100644 --- a/app/graphql/types/group_type.rb +++ b/app/graphql/types/group_type.rb @@ -163,6 +163,11 @@ module Types null: false, description: 'Prefix for pulling images when using the dependency proxy.' + field :dependency_proxy_image_ttl_policy, + Types::DependencyProxy::ImageTtlGroupPolicyType, + null: true, + description: 'Dependency proxy TTL policy for the group.' + def label(title:) BatchLoader::GraphQL.for(title).batch(key: group) do |titles, loader, args| LabelsFinder diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 31f46f65733..11e9d896ff0 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -34,6 +34,7 @@ module Types mount_mutation Mutations::Commits::Create, calls_gitaly: true mount_mutation Mutations::CustomEmoji::Create, feature_flag: :custom_emoji mount_mutation Mutations::Discussions::ToggleResolve + mount_mutation Mutations::DependencyProxy::ImageTtlGroupPolicy::Update mount_mutation Mutations::Environments::CanaryIngress::Update mount_mutation Mutations::Issues::Create mount_mutation Mutations::Issues::SetAssignees diff --git a/app/models/group.rb b/app/models/group.rb index c6ab8ac7a64..3e1b40e21bd 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -764,6 +764,10 @@ class Group < Namespace ::CustomerRelations::Contact.where(group_id: self.id) end + def dependency_proxy_image_ttl_policy + super || build_dependency_proxy_image_ttl_policy + end + private def max_member_access(user_ids) diff --git a/app/models/user.rb b/app/models/user.rb index 8dd9f9f90c7..79df91e3d5f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -256,10 +256,7 @@ class User < ApplicationRecord validates :website_url, allow_blank: true, url: true, if: :website_url_changed? before_validation :sanitize_attrs - before_validation :reset_secondary_emails, if: :email_changed? before_save :default_private_profile_to_false - before_save :set_public_email, if: :public_email_changed? # in case validation is skipped - before_save :set_commit_email, if: :commit_email_changed? # in case validation is skipped before_save :ensure_incoming_email_token before_save :ensure_user_rights_and_limits, if: ->(user) { user.new_record? || user.external_changed? } before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) } @@ -2029,30 +2026,6 @@ class User < ApplicationRecord errors.add(:commit_email, _("must be an email you have verified")) unless verified_emails.include?(commit_email) end - def set_notification_email - if verified_emails.exclude?(notification_email) - self.notification_email = nil - end - end - - def set_public_email - if verified_emails.exclude?(public_email) - self.public_email = '' - end - end - - def set_commit_email - if verified_emails.exclude?(commit_email) - self.commit_email = nil - end - end - - def reset_secondary_emails - set_public_email - set_commit_email - set_notification_email - end - def callouts_by_feature_name @callouts_by_feature_name ||= callouts.index_by(&:feature_name) end diff --git a/app/policies/dependency_proxy/image_ttl_group_policy_policy.rb b/app/policies/dependency_proxy/image_ttl_group_policy_policy.rb new file mode 100644 index 00000000000..cf7e1ded137 --- /dev/null +++ b/app/policies/dependency_proxy/image_ttl_group_policy_policy.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +module DependencyProxy + class ImageTtlGroupPolicyPolicy < BasePolicy + delegate { @subject.group } + end +end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index ffe908593ae..96cfa5c3e6c 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -228,8 +228,9 @@ class GroupPolicy < BasePolicy rule { dependency_proxy_access_allowed & dependency_proxy_available } .enable :read_dependency_proxy - rule { developer & dependency_proxy_available } - .enable :admin_dependency_proxy + rule { developer & dependency_proxy_available }.policy do + enable :admin_dependency_proxy + end rule { can?(:admin_group) & resource_access_token_feature_available }.policy do enable :read_resource_access_tokens diff --git a/app/services/dependency_proxy/image_ttl_group_policies/update_service.rb b/app/services/dependency_proxy/image_ttl_group_policies/update_service.rb new file mode 100644 index 00000000000..3c0b6412dc5 --- /dev/null +++ b/app/services/dependency_proxy/image_ttl_group_policies/update_service.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module DependencyProxy + module ImageTtlGroupPolicies + class UpdateService < BaseContainerService + include Gitlab::Utils::StrongMemoize + + ALLOWED_ATTRIBUTES = %i[enabled ttl].freeze + + def execute + return ServiceResponse.error(message: 'Access Denied', http_status: 403) unless allowed? + return ServiceResponse.error(message: 'Dependency proxy image TTL Policy not found', http_status: 404) unless dependency_proxy_image_ttl_policy + + if dependency_proxy_image_ttl_policy.update(dependency_proxy_image_ttl_policy_params) + ServiceResponse.success(payload: { dependency_proxy_image_ttl_policy: dependency_proxy_image_ttl_policy }) + else + ServiceResponse.error( + message: dependency_proxy_image_ttl_policy.errors.full_messages.to_sentence || 'Bad request', + http_status: 400 + ) + end + end + + private + + def dependency_proxy_image_ttl_policy + strong_memoize(:dependency_proxy_image_ttl_policy) do + container.dependency_proxy_image_ttl_policy + end + end + + def allowed? + Ability.allowed?(current_user, :admin_dependency_proxy, container) + end + + def dependency_proxy_image_ttl_policy_params + params.slice(*ALLOWED_ATTRIBUTES) + end + end + end +end diff --git a/config/feature_flags/development/infinitely_collapsible_sections.yml b/config/feature_flags/development/infinitely_collapsible_sections.yml index 44f37c06d70..d0bf063c6f6 100644 --- a/config/feature_flags/development/infinitely_collapsible_sections.yml +++ b/config/feature_flags/development/infinitely_collapsible_sections.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/335297 milestone: '14.1' type: development group: group::pipeline execution -default_enabled: false +default_enabled: true diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index a5e3b8b24e7..a8881fd8a2e 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -959,6 +959,11 @@ production: &base # (default: false) auto_link_saml_user: false + # CAUTION! + # Allows larger SAML messages to be received. Numeric value in bytes (default: 250000) + # Too high limits exposes instance to decompression DDoS attack type. + saml_message_max_byte_size: 250000 + # Allow users with existing accounts to sign in and auto link their account via OmniAuth # login, without having to do a manual login first and manually add OmniAuth. Links on email. # Define the allowed providers using an array, e.g. ["saml", "twitter"], or as true/false to diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 34f8080ac23..1c22216d442 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -95,6 +95,7 @@ Settings.omniauth['block_auto_created_users'] = true if Settings.omniauth['block Settings.omniauth['auto_link_ldap_user'] = false if Settings.omniauth['auto_link_ldap_user'].nil? Settings.omniauth['auto_link_saml_user'] = false if Settings.omniauth['auto_link_saml_user'].nil? Settings.omniauth['auto_link_user'] = false if Settings.omniauth['auto_link_user'].nil? +Settings.omniauth['saml_message_max_byte_size'] = 250000 if Settings.omniauth['saml_message_max_byte_size'].nil? Settings.omniauth['sync_profile_from_provider'] = false if Settings.omniauth['sync_profile_from_provider'].nil? Settings.omniauth['sync_profile_attributes'] = ['email'] if Settings.omniauth['sync_profile_attributes'].nil? diff --git a/data/deprecations/templates/example.yml b/data/deprecations/templates/example.yml index 18ec63c0a46..fa90dba6e84 100644 --- a/data/deprecations/templates/example.yml +++ b/data/deprecations/templates/example.yml @@ -16,13 +16,11 @@ body: | # Do not modify this line, instead modify the lines below. <!-- START OF BODY COMMENT - This area supports markdown. + This area supports markdown. Delete this entire comment and replace it with your markdown content. - It is recommended to copy and paste the release post entry content here. + Make sure to run `bin/rake gitlab:docs:compile_deprecations` locally before committing and pushing your changes. - You can shorten it if needed, but remember that the entry itself has already been reviewed by Tech Writing so don't feel like you need to reword anything. - - Delete this entire comment and replace it with your markdown content. + When ready, assign to your tech writer to review and merge. END OF BODY COMMENT --> stage: # (optional - may be required in the future) String value of the stage that the feature was created in. e.g., Growth diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index fa19a502c8f..83d3e67e5fc 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -4180,6 +4180,27 @@ Input type: `UpdateContainerExpirationPolicyInput` | <a id="mutationupdatecontainerexpirationpolicycontainerexpirationpolicy"></a>`containerExpirationPolicy` | [`ContainerExpirationPolicy`](#containerexpirationpolicy) | Container expiration policy after mutation. | | <a id="mutationupdatecontainerexpirationpolicyerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | +### `Mutation.updateDependencyProxyImageTtlGroupPolicy` + +Input type: `UpdateDependencyProxyImageTtlGroupPolicyInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationupdatedependencyproxyimagettlgrouppolicyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationupdatedependencyproxyimagettlgrouppolicyenabled"></a>`enabled` | [`Boolean`](#boolean) | Indicates whether the policy is enabled or disabled. | +| <a id="mutationupdatedependencyproxyimagettlgrouppolicygrouppath"></a>`groupPath` | [`ID!`](#id) | Group path for the group dependency proxy image TTL policy. | +| <a id="mutationupdatedependencyproxyimagettlgrouppolicyttl"></a>`ttl` | [`Int`](#int) | Number of days to retain a cached image file. | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationupdatedependencyproxyimagettlgrouppolicyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationupdatedependencyproxyimagettlgrouppolicydependencyproxyimagettlpolicy"></a>`dependencyProxyImageTtlPolicy` | [`DependencyProxyImageTtlGroupPolicy`](#dependencyproxyimagettlgrouppolicy) | Group image TTL policy after mutation. | +| <a id="mutationupdatedependencyproxyimagettlgrouppolicyerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | + ### `Mutation.updateEpic` Input type: `UpdateEpicInput` @@ -5249,6 +5270,29 @@ The edge type for [`ComplianceFramework`](#complianceframework). | <a id="complianceframeworkedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | | <a id="complianceframeworkedgenode"></a>`node` | [`ComplianceFramework`](#complianceframework) | The item at the end of the edge. | +#### `ConnectedAgentConnection` + +The connection type for [`ConnectedAgent`](#connectedagent). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="connectedagentconnectionedges"></a>`edges` | [`[ConnectedAgentEdge]`](#connectedagentedge) | A list of edges. | +| <a id="connectedagentconnectionnodes"></a>`nodes` | [`[ConnectedAgent]`](#connectedagent) | A list of nodes. | +| <a id="connectedagentconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | + +#### `ConnectedAgentEdge` + +The edge type for [`ConnectedAgent`](#connectedagent). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="connectedagentedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | +| <a id="connectedagentedgenode"></a>`node` | [`ConnectedAgent`](#connectedagent) | The item at the end of the edge. | + #### `ContainerRepositoryConnection` The connection type for [`ContainerRepository`](#containerrepository). @@ -8290,6 +8334,7 @@ GitLab CI/CD configuration template. | Name | Type | Description | | ---- | ---- | ----------- | +| <a id="clusteragentconnections"></a>`connections` | [`ConnectedAgentConnection`](#connectedagentconnection) | Active connections for the cluster agent. (see [Connections](#connections)) | | <a id="clusteragentcreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp the cluster agent was created. | | <a id="clusteragentcreatedbyuser"></a>`createdByUser` | [`UserCore`](#usercore) | User object, containing information about the person who created the agent. | | <a id="clusteragentid"></a>`id` | [`ID!`](#id) | ID of the cluster agent. | @@ -8452,6 +8497,18 @@ Conan metadata. | <a id="conanmetadatarecipepath"></a>`recipePath` | [`String!`](#string) | Recipe path of the Conan package. | | <a id="conanmetadataupdatedat"></a>`updatedAt` | [`Time!`](#time) | Date of most recent update. | +### `ConnectedAgent` + +Connection details for an Agent. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="connectedagentconnectedat"></a>`connectedAt` | [`Time`](#time) | When the connection was established. | +| <a id="connectedagentconnectionid"></a>`connectionId` | [`BigInt`](#bigint) | ID of the connection. | +| <a id="connectedagentmetadata"></a>`metadata` | [`JSON`](#json) | Information about the Agent. | + ### `ContainerExpirationPolicy` A tag expiration policy designed to keep only the images that matter most. @@ -8752,6 +8809,19 @@ Dependency proxy blob. | <a id="dependencyproxyblobsize"></a>`size` | [`String!`](#string) | Size of the blob file. | | <a id="dependencyproxyblobupdatedat"></a>`updatedAt` | [`Time!`](#time) | Date of most recent update. | +### `DependencyProxyImageTtlGroupPolicy` + +Group-level Dependency Proxy TTL policy settings. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="dependencyproxyimagettlgrouppolicycreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of creation. | +| <a id="dependencyproxyimagettlgrouppolicyenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether the policy is enabled or disabled. | +| <a id="dependencyproxyimagettlgrouppolicyttl"></a>`ttl` | [`Int`](#int) | Number of days to retain a cached image file. | +| <a id="dependencyproxyimagettlgrouppolicyupdatedat"></a>`updatedAt` | [`Time`](#time) | Timestamp of the most recent update. | + ### `DependencyProxyManifest` Dependency proxy manifest. @@ -9830,6 +9900,7 @@ four standard [pagination arguments](#connection-pagination-arguments): | <a id="groupdependencyproxyblobs"></a>`dependencyProxyBlobs` | [`DependencyProxyBlobConnection`](#dependencyproxyblobconnection) | Dependency Proxy blobs. (see [Connections](#connections)) | | <a id="groupdependencyproxyimagecount"></a>`dependencyProxyImageCount` | [`Int!`](#int) | Number of dependency proxy images cached in the group. | | <a id="groupdependencyproxyimageprefix"></a>`dependencyProxyImagePrefix` | [`String!`](#string) | Prefix for pulling images when using the dependency proxy. | +| <a id="groupdependencyproxyimagettlpolicy"></a>`dependencyProxyImageTtlPolicy` | [`DependencyProxyImageTtlGroupPolicy`](#dependencyproxyimagettlgrouppolicy) | Dependency proxy TTL policy for the group. | | <a id="groupdependencyproxymanifests"></a>`dependencyProxyManifests` | [`DependencyProxyManifestConnection`](#dependencyproxymanifestconnection) | Dependency Proxy manifests. (see [Connections](#connections)) | | <a id="groupdependencyproxysetting"></a>`dependencyProxySetting` | [`DependencyProxySetting`](#dependencyproxysetting) | Dependency Proxy settings for the group. | | <a id="groupdependencyproxytotalsize"></a>`dependencyProxyTotalSize` | [`String!`](#string) | Total size of the dependency proxy cached images. | diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 10713f3ae7c..6f55d1b3604 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -1867,36 +1867,43 @@ In this example, only runners with *both* the `ruby` and `postgres` tags can run ### `allow_failure` -Use `allow_failure` when you want to let a job fail without impacting the rest of the CI -suite. The default value is `false`, except for [manual](../jobs/job_control.md#create-a-job-that-must-be-run-manually) jobs that use -the [`when: manual`](#when) syntax. +Use `allow_failure` to determine whether a pipeline should continue running when a job fails. -In jobs that use [`rules:`](#rules), all jobs default to `allow_failure: false`, -*including* `when: manual` jobs. +- To let the pipeline continue running subsequent jobs, use `allow_failure: true`. +- To stop the pipeline from running subsequent jobs, use `allow_failure: false`. -When `allow_failure` is set to `true` and the job fails, the job shows an orange warning in the UI. -However, the logical flow of the pipeline considers the job a -success/passed, and is not blocked. +When jobs are allowed to fail (`allow_failure: true`) an orange warning (**{status_warning}**) +indicates that a job failed. However, the pipeline is successful and the associated commit +is marked as passed with no warnings. -Assuming all other jobs are successful, the job's stage and its pipeline -show the same orange warning. However, the associated commit is marked as -"passed", without warnings. +This same warning is displayed when: -In the following example, `job1` and `job2` run in parallel. If `job1` -fails, it doesn't stop the next stage from running, because it's marked with -`allow_failure: true`: +- All other jobs in the stage are successful. +- All other jobs in the pipeline are successful. + +The default value for `allow_failure` is: + +- `true` for [manual jobs](../jobs/job_control.md#create-a-job-that-must-be-run-manually). +- `false` for manual jobs that also use [`rules`](#rules). +- `false` in all other cases. + +**Keyword type**: Job keyword. You can use it only as part of a job. + +**Possible inputs**: `true` or `false`. + +**Example of `allow_failure`**: ```yaml job1: stage: test script: - - execute_script_that_will_fail - allow_failure: true + - execute_script_1 job2: stage: test script: - - execute_script_that_will_succeed + - execute_script_2 + allow_failure: true job3: stage: deploy @@ -1904,14 +1911,35 @@ job3: - deploy_to_staging ``` +In this example, `job1` and `job2` run in parallel: + +- If `job1` fails, jobs in the `deploy` stage do not start. +- If `job2` fails, jobs in the `deploy` stage can still start. + +**Additional details**: + +- You can use `allow_failure` as a subkey of [`rules:`](#rulesallow_failure). +- You can use `allow_failure: false` with a manual job to create a [blocking manual job](../jobs/job_control.md#types-of-manual-jobs). + A blocked pipeline does not run any jobs in later stages until the manual job + is started and completes successfully. + #### `allow_failure:exit_codes` > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/273157) in GitLab 13.8. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/292024) in GitLab 13.9. -Use `allow_failure:exit_codes` to dynamically control if a job should be allowed -to fail. You can list which exit codes are not considered failures. The job fails -for any other exit code: +Use `allow_failure:exit_codes` to control when a job should be +allowed to fail. The job is `allow_failure: true` for any of the listed exit codes, +and `allow_failure` false for any other exit code. + +**Keyword type**: Job keyword. You can use it only as part of a job. + +**Possible inputs**: + +- A single exit code. +- An array of exit codes. + +**Example of `allow_failure`**: ```yaml test_job_1: diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md index 85eca17f6e7..a5c3a2a7aeb 100644 --- a/doc/user/admin_area/index.md +++ b/doc/user/admin_area/index.md @@ -199,6 +199,18 @@ The following totals are also included: GitLab billing is based on the number of [**Billable users**](../../subscriptions/self_managed/index.md#billable-users). +#### Add email to user + +You must be an administrator to manually add emails to users: + +1. On the top bar, select **Menu > Admin**. +1. On the left sidebar, select **Overview > Users** (`/admin/users`). +1. Locate the user and select them. +1. Select **Edit**. +1. In **Email**, enter the new email address. This adds the new email address to the + user and sets the previous email address to be a secondary. +1. Select **Save changes**. + ### User cohorts The [Cohorts](user_cohorts.md) tab displays the monthly cohorts of new users and their activities over time. diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md index 82a56d00118..24006e6f875 100644 --- a/doc/user/profile/index.md +++ b/doc/user/profile/index.md @@ -59,6 +59,17 @@ To change your username: 1. In the **Change username** section, enter a new username as the path. 1. Select **Update username**. +## Add emails to your user profile + +To add new email to your account: + +1. In the top-right corner, select your avatar. +1. Select **Edit profile**. +1. On the left sidebar, select **Emails**. +1. In the **Email** box, enter the new email. +1. Select **Add email address**. +1. Verify your email address with the verification email received. + ## Make your user profile page private You can make your user profile visible to only you and GitLab administrators. diff --git a/doc/user/project/integrations/img/slack_setup.png b/doc/user/project/integrations/img/slack_setup.png Binary files differindex 7928fb7d495..8acae659fb4 100644 --- a/doc/user/project/integrations/img/slack_setup.png +++ b/doc/user/project/integrations/img/slack_setup.png diff --git a/doc/user/project/integrations/slack_slash_commands.md b/doc/user/project/integrations/slack_slash_commands.md index 3da50524a4d..066a2f83753 100644 --- a/doc/user/project/integrations/slack_slash_commands.md +++ b/doc/user/project/integrations/slack_slash_commands.md @@ -31,12 +31,13 @@ are scoped to a project. Select **Add Slash Command Integration**. 1. Complete the rest of the fields in the Slack configuration page using information from the GitLab browser tab. In particular, make sure you copy and paste the **URL**. + + ![Slack setup instructions](img/slack_setup.png) + 1. On the Slack configuration page, select **Save Integration** and copy the **Token**. 1. Go back to the GitLab configuration page and paste in the **Token**. 1. Ensure the **Active** checkbox is selected and select **Save changes**. -![Slack setup instructions](img/slack_setup.png) - ## Slash commands You can now use the available [Slack slash commands](../../../integration/slash_commands.md). diff --git a/lib/gitlab/kas/client.rb b/lib/gitlab/kas/client.rb index 842ee98e4da..753a185344e 100644 --- a/lib/gitlab/kas/client.rb +++ b/lib/gitlab/kas/client.rb @@ -7,6 +7,7 @@ module Gitlab JWT_AUDIENCE = 'gitlab-kas' STUB_CLASSES = { + agent_tracker: Gitlab::Agent::AgentTracker::Rpc::AgentTracker::Stub, configuration_project: Gitlab::Agent::ConfigurationProject::Rpc::ConfigurationProject::Stub }.freeze @@ -17,6 +18,15 @@ module Gitlab raise ConfigurationError, 'KAS internal URL is not configured' unless Gitlab::Kas.internal_url.present? end + def get_connected_agents(project:) + request = Gitlab::Agent::AgentTracker::Rpc::GetConnectedAgentsRequest.new(project_id: project.id) + + stub_for(:agent_tracker) + .get_connected_agents(request, metadata: metadata) + .agents + .to_a + end + def list_agent_config_files(project:) request = Gitlab::Agent::ConfigurationProject::Rpc::ListAgentConfigFilesRequest.new( repository: repository(project), @@ -49,7 +59,7 @@ module Gitlab end def kas_endpoint_url - Gitlab::Kas.internal_url.sub(%r{^grpc://|^grpcs://}, '') + Gitlab::Kas.internal_url.sub(%r{^grpcs?://}, '') end def credentials diff --git a/spec/factories/dependency_proxy/image_ttl_group_policies.rb b/spec/factories/dependency_proxy/image_ttl_group_policies.rb new file mode 100644 index 00000000000..21e5dd44cf5 --- /dev/null +++ b/spec/factories/dependency_proxy/image_ttl_group_policies.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :image_ttl_group_policy, class: 'DependencyProxy::ImageTtlGroupPolicy' do + group + + enabled { true } + ttl { 90 } + end +end diff --git a/spec/features/atom/merge_requests_spec.rb b/spec/features/atom/merge_requests_spec.rb index 29f0b4660c9..48db8fcbf1e 100644 --- a/spec/features/atom/merge_requests_spec.rb +++ b/spec/features/atom/merge_requests_spec.rb @@ -4,8 +4,8 @@ require 'spec_helper' RSpec.describe 'Merge Requests Feed' do describe 'GET /merge_requests' do - let_it_be_with_reload(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') } - let_it_be(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') } + let_it_be_with_reload(:user) { create(:user, email: 'private1@example.com') } + let_it_be(:assignee) { create(:user, email: 'private2@example.com') } let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, :repository) } let_it_be(:merge_request) { create(:merge_request, source_project: project, assignees: [assignee]) } diff --git a/spec/graphql/mutations/dependency_proxy/image_ttl_group_policy/update_spec.rb b/spec/graphql/mutations/dependency_proxy/image_ttl_group_policy/update_spec.rb new file mode 100644 index 00000000000..792e87f0d25 --- /dev/null +++ b/spec/graphql/mutations/dependency_proxy/image_ttl_group_policy/update_spec.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::DependencyProxy::ImageTtlGroupPolicy::Update do + using RSpec::Parameterized::TableSyntax + + let_it_be_with_reload(:group) { create(:group) } + let_it_be(:user) { create(:user) } + + let(:params) { { group_path: group.full_path } } + + specify { expect(described_class).to require_graphql_authorizations(:admin_dependency_proxy) } + + describe '#resolve' do + subject { described_class.new(object: group, context: { current_user: user }, field: nil).resolve(**params) } + + shared_examples 'returning a success' do + it 'returns the dependency proxy image ttl group policy with no errors' do + expect(subject).to eq( + dependency_proxy_image_ttl_policy: ttl_policy, + errors: [] + ) + end + end + + shared_examples 'updating the dependency proxy image ttl policy' do + it_behaves_like 'updating the dependency proxy image ttl policy attributes', + from: { enabled: true, ttl: 90 }, + to: { enabled: false, ttl: 2 } + + it_behaves_like 'returning a success' + + context 'with invalid params' do + let_it_be(:params) { { group_path: group.full_path, enabled: nil } } + + it "doesn't create the dependency proxy image ttl policy" do + expect { subject }.not_to change { DependencyProxy::ImageTtlGroupPolicy.count } + end + + it 'does not update' do + expect { subject } + .not_to change { ttl_policy.reload.enabled } + end + + it 'returns an error' do + expect(subject).to eq( + dependency_proxy_image_ttl_policy: nil, + errors: ['Enabled is not included in the list'] + ) + end + end + end + + shared_examples 'denying access to dependency proxy image ttl policy' do + it 'raises Gitlab::Graphql::Errors::ResourceNotAvailable' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + before do + stub_config(dependency_proxy: { enabled: true }) + end + + context 'with existing dependency proxy image ttl policy' do + let_it_be(:ttl_policy) { create(:image_ttl_group_policy, group: group) } + let_it_be(:params) do + { group_path: group.full_path, + enabled: false, + ttl: 2 } + end + + where(:user_role, :shared_examples_name) do + :maintainer | 'updating the dependency proxy image ttl policy' + :developer | 'updating the dependency proxy image ttl policy' + :reporter | 'denying access to dependency proxy image ttl policy' + :guest | 'denying access to dependency proxy image ttl policy' + :anonymous | 'denying access to dependency proxy image ttl policy' + end + + with_them do + before do + group.send("add_#{user_role}", user) unless user_role == :anonymous + end + + it_behaves_like params[:shared_examples_name] + end + end + + context 'without existing dependency proxy image ttl policy' do + let_it_be(:ttl_policy) { group.dependency_proxy_image_ttl_policy } + + where(:user_role, :shared_examples_name) do + :maintainer | 'creating the dependency proxy image ttl policy' + :developer | 'creating the dependency proxy image ttl policy' + :reporter | 'denying access to dependency proxy image ttl policy' + :guest | 'denying access to dependency proxy image ttl policy' + :anonymous | 'denying access to dependency proxy image ttl policy' + end + + with_them do + before do + group.send("add_#{user_role}", user) unless user_role == :anonymous + end + + it_behaves_like params[:shared_examples_name] + end + end + end +end diff --git a/spec/graphql/types/dependency_proxy/image_ttl_group_policy_type_spec.rb b/spec/graphql/types/dependency_proxy/image_ttl_group_policy_type_spec.rb new file mode 100644 index 00000000000..46347e0434f --- /dev/null +++ b/spec/graphql/types/dependency_proxy/image_ttl_group_policy_type_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['DependencyProxyImageTtlGroupPolicy'] do + it { expect(described_class.graphql_name).to eq('DependencyProxyImageTtlGroupPolicy') } + + it { expect(described_class.description).to eq('Group-level Dependency Proxy TTL policy settings') } + + it { expect(described_class).to require_graphql_authorizations(:read_dependency_proxy) } + + it 'includes dependency proxy image ttl policy fields' do + expected_fields = %w[enabled ttl created_at updated_at] + + expect(described_class).to have_graphql_fields(*expected_fields).only + end +end diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb index 35fda69ffe3..dca2c930eea 100644 --- a/spec/graphql/types/group_type_spec.rb +++ b/spec/graphql/types/group_type_spec.rb @@ -21,8 +21,8 @@ RSpec.describe GitlabSchema.types['Group'] do packages dependency_proxy_setting dependency_proxy_manifests dependency_proxy_blobs dependency_proxy_image_count dependency_proxy_blob_count dependency_proxy_total_size - dependency_proxy_image_prefix shared_runners_setting - timelogs organizations contacts + dependency_proxy_image_prefix dependency_proxy_image_ttl_policy + shared_runners_setting timelogs organizations contacts ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/lib/gitlab/kas/client_spec.rb b/spec/lib/gitlab/kas/client_spec.rb index 40e18f58ee4..5b89023cc13 100644 --- a/spec/lib/gitlab/kas/client_spec.rb +++ b/spec/lib/gitlab/kas/client_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe Gitlab::Kas::Client do let_it_be(:project) { create(:project) } + let_it_be(:agent) { create(:cluster_agent, project: project) } describe '#initialize' do context 'kas is not enabled' do @@ -44,6 +45,32 @@ RSpec.describe Gitlab::Kas::Client do expect(token).to receive(:audience=).with(described_class::JWT_AUDIENCE) end + describe '#get_connected_agents' do + let(:stub) { instance_double(Gitlab::Agent::AgentTracker::Rpc::AgentTracker::Stub) } + let(:request) { instance_double(Gitlab::Agent::AgentTracker::Rpc::GetConnectedAgentsRequest) } + let(:response) { double(Gitlab::Agent::AgentTracker::Rpc::GetConnectedAgentsResponse, agents: connected_agents) } + + let(:connected_agents) { [double] } + + subject { described_class.new.get_connected_agents(project: project) } + + before do + expect(Gitlab::Agent::AgentTracker::Rpc::AgentTracker::Stub).to receive(:new) + .with('example.kas.internal', :this_channel_is_insecure, timeout: described_class::TIMEOUT) + .and_return(stub) + + expect(Gitlab::Agent::AgentTracker::Rpc::GetConnectedAgentsRequest).to receive(:new) + .with(project_id: project.id) + .and_return(request) + + expect(stub).to receive(:get_connected_agents) + .with(request, metadata: { 'authorization' => 'bearer test-token' }) + .and_return(response) + end + + it { expect(subject).to eq(connected_agents) } + end + describe '#list_agent_config_files' do let(:stub) { instance_double(Gitlab::Agent::ConfigurationProject::Rpc::ConfigurationProject::Stub) } diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 8e6be32ea3c..38f3eadbb22 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -2743,4 +2743,28 @@ RSpec.describe Group do expect(group.dependency_proxy_image_prefix).not_to include('http') end end + + describe '#dependency_proxy_image_ttl_policy' do + subject(:ttl_policy) { group.dependency_proxy_image_ttl_policy } + + it 'builds a new policy if one does not exist', :aggregate_failures do + expect(ttl_policy.ttl).to eq(90) + expect(ttl_policy.enabled).to eq(false) + expect(ttl_policy.created_at).to be_nil + expect(ttl_policy.updated_at).to be_nil + end + + context 'with existing policy' do + before do + group.dependency_proxy_image_ttl_policy.update!(ttl: 30, enabled: true) + end + + it 'returns the policy if it already exists', :aggregate_failures do + expect(ttl_policy.ttl).to eq(30) + expect(ttl_policy.enabled).to eq(true) + expect(ttl_policy.created_at).not_to be_nil + expect(ttl_policy.updated_at).not_to be_nil + end + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 4a83d62c401..ba20343c30a 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -681,6 +681,22 @@ RSpec.describe User do end end end + + context 'when secondary email is same as primary' do + let(:user) { create(:user, email: 'user@example.com') } + + it 'lets user change primary email without failing validations' do + user.commit_email = user.email + user.notification_email = user.email + user.public_email = user.email + user.save! + + user.email = 'newemail@example.com' + user.confirm + + expect(user).to be_valid + end + end end end diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index 95144fae9ce..482e12c029d 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -893,6 +893,34 @@ RSpec.describe GroupPolicy do end end + describe 'dependency proxy' do + context 'feature disabled' do + let(:current_user) { owner } + + it { is_expected.to be_disallowed(:read_dependency_proxy) } + it { is_expected.to be_disallowed(:admin_dependency_proxy) } + end + + context 'feature enabled' do + before do + stub_config(dependency_proxy: { enabled: true }) + group.create_dependency_proxy_setting!(enabled: true) + end + + context 'reporter' do + let(:current_user) { reporter } + + it { is_expected.to be_disallowed(:admin_dependency_proxy) } + end + + context 'developer' do + let(:current_user) { developer } + + it { is_expected.to be_allowed(:admin_dependency_proxy) } + end + end + end + context 'deploy token access' do let!(:group_deploy_token) do create(:group_deploy_token, group: group, deploy_token: deploy_token) @@ -920,6 +948,18 @@ RSpec.describe GroupPolicy do it { is_expected.to be_allowed(:read_contact) } it { is_expected.to be_disallowed(:destroy_package) } end + + context 'a deploy token with dependency proxy scopes' do + let_it_be(:deploy_token) { create(:deploy_token, :group, :dependency_proxy_scopes) } + + before do + stub_config(dependency_proxy: { enabled: true }) + group.create_dependency_proxy_setting!(enabled: true) + end + + it { is_expected.to be_allowed(:read_dependency_proxy) } + it { is_expected.to be_disallowed(:admin_dependency_proxy) } + end end it_behaves_like 'Self-managed Core resource access tokens' diff --git a/spec/requests/api/graphql/group/dependency_proxy_image_ttl_policy_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_image_ttl_policy_spec.rb new file mode 100644 index 00000000000..c8797d84906 --- /dev/null +++ b/spec/requests/api/graphql/group/dependency_proxy_image_ttl_policy_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'getting dependency proxy image ttl policy for a group' do + using RSpec::Parameterized::TableSyntax + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be_with_reload(:group) { create(:group) } + + let(:dependency_proxy_image_ttl_policy_fields) do + <<~GQL + #{all_graphql_fields_for('dependency_proxy_image_ttl_group_policy'.classify, max_depth: 1)} + GQL + end + + let(:fields) do + <<~GQL + #{query_graphql_field('dependency_proxy_image_ttl_policy', {}, dependency_proxy_image_ttl_policy_fields)} + GQL + end + + let(:query) do + graphql_query_for( + 'group', + { 'fullPath' => group.full_path }, + fields + ) + end + + let(:variables) { {} } + let(:dependency_proxy_image_ttl_policy_response) { graphql_data.dig('group', 'dependencyProxyImageTtlPolicy') } + + before do + stub_config(dependency_proxy: { enabled: true }) + end + + subject { post_graphql(query, current_user: user, variables: variables) } + + it_behaves_like 'a working graphql query' do + before do + subject + end + end + + context 'with different permissions' do + where(:group_visibility, :role, :access_granted) do + :private | :maintainer | true + :private | :developer | true + :private | :reporter | true + :private | :guest | true + :private | :anonymous | false + :public | :maintainer | true + :public | :developer | true + :public | :reporter | true + :public | :guest | true + :public | :anonymous | false + end + + with_them do + before do + group.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(group_visibility.to_s.upcase, false)) + group.add_user(user, role) unless role == :anonymous + end + + it 'return the proper response' do + subject + + if access_granted + expect(dependency_proxy_image_ttl_policy_response).to eq("createdAt" => nil, "enabled" => false, "ttl" => 90, "updatedAt" => nil) + else + expect(dependency_proxy_image_ttl_policy_response).to be_blank + end + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/dependency_proxy/image_ttl_group_policy/update_spec.rb b/spec/requests/api/graphql/mutations/dependency_proxy/image_ttl_group_policy/update_spec.rb new file mode 100644 index 00000000000..c9e9a22ee0b --- /dev/null +++ b/spec/requests/api/graphql/mutations/dependency_proxy/image_ttl_group_policy/update_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Updating the dependency proxy image ttl policy' do + include GraphqlHelpers + using RSpec::Parameterized::TableSyntax + + let_it_be(:user) { create(:user) } + + let(:params) do + { + group_path: group.full_path, + enabled: false, + ttl: 2 + } + end + + let(:mutation) do + graphql_mutation(:update_dependency_proxy_image_ttl_group_policy, params) do + <<~QL + dependencyProxyImageTtlPolicy { + enabled + ttl + } + errors + QL + end + end + + let(:mutation_response) { graphql_mutation_response(:update_dependency_proxy_image_ttl_group_policy) } + let(:ttl_policy_response) { mutation_response['dependencyProxyImageTtlPolicy'] } + + before do + stub_config(dependency_proxy: { enabled: true }) + end + + describe 'post graphql mutation' do + subject { post_graphql_mutation(mutation, current_user: user) } + + let_it_be(:ttl_policy, reload: true) { create(:image_ttl_group_policy) } + let_it_be(:group, reload: true) { ttl_policy.group } + + context 'without permission' do + it 'returns no response' do + subject + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response).to be_nil + end + end + + context 'with permission' do + before do + group.add_developer(user) + end + + it 'returns the updated dependency proxy image ttl policy', :aggregate_failures do + subject + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['errors']).to be_empty + expect(ttl_policy_response).to include( + 'enabled' => params[:enabled], + 'ttl' => params[:ttl] + ) + end + end + end +end diff --git a/spec/services/dependency_proxy/image_ttl_group_policies/update_service_spec.rb b/spec/services/dependency_proxy/image_ttl_group_policies/update_service_spec.rb new file mode 100644 index 00000000000..ceac8985c8e --- /dev/null +++ b/spec/services/dependency_proxy/image_ttl_group_policies/update_service_spec.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::DependencyProxy::ImageTtlGroupPolicies::UpdateService do + using RSpec::Parameterized::TableSyntax + + let_it_be_with_reload(:group) { create(:group) } + let_it_be(:user) { create(:user) } + let_it_be(:params) { {} } + + describe '#execute' do + subject { described_class.new(container: group, current_user: user, params: params).execute } + + shared_examples 'returning a success' do + it 'returns a success' do + result = subject + + expect(result.payload[:dependency_proxy_image_ttl_policy]).to be_present + expect(result).to be_success + end + end + + shared_examples 'returning an error' do |message, http_status| + it 'returns an error' do + result = subject + + expect(result).to have_attributes( + message: message, + status: :error, + http_status: http_status + ) + end + end + + shared_examples 'updating the dependency proxy image ttl policy' do + it_behaves_like 'updating the dependency proxy image ttl policy attributes', + from: { enabled: true, ttl: 90 }, + to: { enabled: false, ttl: 2 } + + it_behaves_like 'returning a success' + + context 'with invalid params' do + let_it_be(:params) { { enabled: nil } } + + it_behaves_like 'not creating the dependency proxy image ttl policy' + + it "doesn't update" do + expect { subject } + .not_to change { ttl_policy.reload.enabled } + end + + it_behaves_like 'returning an error', 'Enabled is not included in the list', 400 + end + end + + shared_examples 'denying access to dependency proxy image ttl policy' do + context 'with existing dependency proxy image ttl policy' do + it_behaves_like 'not creating the dependency proxy image ttl policy' + + it_behaves_like 'returning an error', 'Access Denied', 403 + end + end + + before do + stub_config(dependency_proxy: { enabled: true }) + end + + context 'with existing dependency proxy image ttl policy' do + let_it_be(:ttl_policy) { create(:image_ttl_group_policy, group: group) } + let_it_be(:params) { { enabled: false, ttl: 2 } } + + where(:user_role, :shared_examples_name) do + :maintainer | 'updating the dependency proxy image ttl policy' + :developer | 'updating the dependency proxy image ttl policy' + :reporter | 'denying access to dependency proxy image ttl policy' + :guest | 'denying access to dependency proxy image ttl policy' + :anonymous | 'denying access to dependency proxy image ttl policy' + end + + with_them do + before do + group.send("add_#{user_role}", user) unless user_role == :anonymous + end + + it_behaves_like params[:shared_examples_name] + end + end + + context 'without existing dependency proxy image ttl policy' do + let_it_be(:ttl_policy) { group.dependency_proxy_image_ttl_policy } + + where(:user_role, :shared_examples_name) do + :maintainer | 'creating the dependency proxy image ttl policy' + :developer | 'creating the dependency proxy image ttl policy' + :reporter | 'denying access to dependency proxy image ttl policy' + :guest | 'denying access to dependency proxy image ttl policy' + :anonymous | 'denying access to dependency proxy image ttl policy' + end + + with_them do + before do + group.send("add_#{user_role}", user) unless user_role == :anonymous + end + + it_behaves_like params[:shared_examples_name] + end + + context 'when the policy is not found' do + before do + group.add_developer(user) + expect(group).to receive(:dependency_proxy_image_ttl_policy).and_return nil + end + + it_behaves_like 'returning an error', 'Dependency proxy image TTL Policy not found', 404 + end + end + end +end diff --git a/spec/support/shared_examples/services/dependency_proxy_ttl_policies_shared_examples.rb b/spec/support/shared_examples/services/dependency_proxy_ttl_policies_shared_examples.rb new file mode 100644 index 00000000000..f6692646ca8 --- /dev/null +++ b/spec/support/shared_examples/services/dependency_proxy_ttl_policies_shared_examples.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'updating the dependency proxy image ttl policy attributes' do |from: {}, to:| + it_behaves_like 'not creating the dependency proxy image ttl policy' + + it 'updates the dependency proxy image ttl policy' do + expect { subject } + .to change { group.dependency_proxy_image_ttl_policy.reload.enabled }.from(from[:enabled]).to(to[:enabled]) + .and change { group.dependency_proxy_image_ttl_policy.reload.ttl }.from(from[:ttl]).to(to[:ttl]) + end +end + +RSpec.shared_examples 'not creating the dependency proxy image ttl policy' do + it "doesn't create the dependency proxy image ttl policy" do + expect { subject }.not_to change { DependencyProxy::ImageTtlGroupPolicy.count } + end +end + +RSpec.shared_examples 'creating the dependency proxy image ttl policy' do + it 'creates a new package setting' do + expect { subject }.to change { DependencyProxy::ImageTtlGroupPolicy.count }.by(1) + end + + it 'saves the settings' do + subject + + expect(group.dependency_proxy_image_ttl_policy).to have_attributes( + enabled: ttl_policy[:enabled], + ttl: ttl_policy[:ttl] + ) + end + + it_behaves_like 'returning a success' +end |