diff options
26 files changed, 386 insertions, 85 deletions
@@ -119,6 +119,7 @@ gem 'fog-local', '~> 0.6' gem 'fog-openstack', '~> 1.0' gem 'fog-rackspace', '~> 0.1.1' gem 'fog-aliyun', '~> 0.3' +gem 'gitlab-fog-azure-rm', '~> 0.7', require: false # for Google storage gem 'google-api-client', '~> 0.33' diff --git a/Gemfile.lock b/Gemfile.lock index aa786f37ca0..1202f3f4cd9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -112,6 +112,15 @@ GEM aws-sigv4 (~> 1.1) aws-sigv4 (1.2.1) aws-eventstream (~> 1, >= 1.0.2) + azure-core (0.1.15) + faraday (~> 0.9) + faraday_middleware (~> 0.10) + nokogiri (~> 1.6) + azure-storage (0.15.0.preview) + azure-core (~> 0.1) + faraday (~> 0.9) + faraday_middleware (~> 0.10) + nokogiri (~> 1.6, >= 1.6.8) babosa (1.0.2) base32 (0.3.2) batch-loader (1.4.0) @@ -313,6 +322,9 @@ GEM railties (>= 4.2.0) faraday (0.17.3) multipart-post (>= 1.2, < 3) + faraday-cookie_jar (0.0.6) + faraday (>= 0.7.4) + http-cookie (~> 1.0.0) faraday-http-cache (2.0.0) faraday (~> 0.8) faraday_middleware (0.14.0) @@ -407,6 +419,12 @@ GEM github-markup (1.7.0) gitlab-chronic (0.10.5) numerizer (~> 0.2) + gitlab-fog-azure-rm (0.7.0) + azure-storage (~> 0.15.0.preview) + fog-core (= 2.1.0) + fog-json (~> 1.2.0) + mime-types + ms_rest_azure (~> 0.12.0) gitlab-labkit (0.12.1) actionpack (>= 5.0.0, < 6.1.0) activesupport (>= 5.0.0, < 6.1.0) @@ -668,6 +686,15 @@ GEM mini_mime (1.0.2) mini_portile2 (2.4.0) minitest (5.11.3) + ms_rest (0.7.6) + concurrent-ruby (~> 1.0) + faraday (>= 0.9, < 2.0.0) + timeliness (~> 0.3.10) + ms_rest_azure (0.12.0) + concurrent-ruby (~> 1.0) + faraday (>= 0.9, < 2.0.0) + faraday-cookie_jar (~> 0.0.6) + ms_rest (~> 0.7.6) msgpack (1.3.1) multi_json (1.14.1) multi_xml (0.6.0) @@ -1104,6 +1131,7 @@ GEM thrift (0.11.0.0) tilt (2.0.10) timecop (0.9.1) + timeliness (0.3.10) timfel-krb5-auth (0.8.3) toml (0.2.0) parslet (~> 1.8.0) @@ -1275,6 +1303,7 @@ DEPENDENCIES gitaly (~> 13.3.0.pre.rc1) github-markup (~> 1.7.0) gitlab-chronic (~> 0.10.5) + gitlab-fog-azure-rm (~> 0.7) gitlab-labkit (= 0.12.1) gitlab-license (~> 1.0) gitlab-mail_room (~> 0.0.6) diff --git a/app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js b/app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js deleted file mode 100644 index ff8b4c56321..00000000000 --- a/app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js +++ /dev/null @@ -1 +0,0 @@ -export default {}; diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js index f609ca5f22d..4b35b7d360c 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js @@ -2,7 +2,6 @@ import $ from 'jquery'; import Vue from 'vue'; import Cookies from 'js-cookie'; import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui'; -import filterMixins from 'ee_else_ce/analytics/cycle_analytics/mixins/filter_mixins'; import Flash from '../flash'; import { __ } from '~/locale'; import Translate from '../vue_shared/translate'; @@ -45,7 +44,6 @@ export default () => { import('ee_component/analytics/shared/components/date_range_dropdown.vue'), 'stage-nav-item': stageNavItem, }, - mixins: [filterMixins], data() { return { store: CycleAnalyticsStore, diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index d180e64b8dd..fc3b786b365 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -673,11 +673,10 @@ border-radius: 100%; display: block; padding: 0; - line-height: initial; + line-height: 0; svg { fill: $gl-text-color-secondary; - vertical-align: initial; } .spinner { diff --git a/app/services/resource_events/base_change_timebox_service.rb b/app/services/resource_events/base_change_timebox_service.rb new file mode 100644 index 00000000000..5c83f7b12f7 --- /dev/null +++ b/app/services/resource_events/base_change_timebox_service.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module ResourceEvents + class BaseChangeTimeboxService + attr_reader :resource, :user, :event_created_at + + def initialize(resource, user, created_at: Time.current) + @resource = resource + @user = user + @event_created_at = created_at + end + + def execute + create_event + + resource.expire_note_etag_cache + end + + private + + def create_event + raise NotImplementedError + end + + def build_resource_args + key = resource.class.name.foreign_key + + { + user_id: user.id, + created_at: event_created_at, + key => resource.id + } + end + end +end diff --git a/app/services/resource_events/change_milestone_service.rb b/app/services/resource_events/change_milestone_service.rb index 82c3e2acad5..dcdf87599ac 100644 --- a/app/services/resource_events/change_milestone_service.rb +++ b/app/services/resource_events/change_milestone_service.rb @@ -1,37 +1,30 @@ # frozen_string_literal: true module ResourceEvents - class ChangeMilestoneService - attr_reader :resource, :user, :event_created_at, :milestone, :old_milestone + class ChangeMilestoneService < BaseChangeTimeboxService + attr_reader :milestone, :old_milestone def initialize(resource, user, created_at: Time.current, old_milestone:) - @resource = resource - @user = user - @event_created_at = created_at + super(resource, user, created_at: created_at) + @milestone = resource&.milestone @old_milestone = old_milestone end - def execute - ResourceMilestoneEvent.create(build_resource_args) + private - resource.expire_note_etag_cache + def create_event + ResourceMilestoneEvent.create(build_resource_args) end - private - def build_resource_args action = milestone.blank? ? :remove : :add - key = resource.class.name.foreign_key - { - user_id: user.id, - created_at: event_created_at, - milestone_id: action == :add ? milestone&.id : old_milestone&.id, + super.merge({ state: ResourceMilestoneEvent.states[resource.state], - action: ResourceMilestoneEvent.actions[action], - key => resource.id - } + action: ResourceTimeboxEvent.actions[action], + milestone_id: milestone.blank? ? old_milestone&.id : milestone&.id + }) end end end diff --git a/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json b/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json index aa4dd60a9fb..995f2ad6616 100644 --- a/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json +++ b/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json @@ -6,6 +6,7 @@ "type": "string", "default_value": "", "value": "", + "size": "MEDIUM", "description": "Analyzer image's registry prefix (or Name of the registry providing the analyzers' image)" }, { @@ -14,6 +15,7 @@ "type": "string", "default_value": "", "value": "", + "size": "LARGE", "description": "Comma-separated list of paths to be excluded from analyzer output. Patterns can be globs, file paths, or folder paths." }, { @@ -22,6 +24,7 @@ "type": "string", "default_value": "", "value": "", + "size": "SMALL", "description": "Analyzer image's tag" } ], @@ -32,6 +35,7 @@ "type": "string", "default_value": "", "value": "", + "size": "MEDIUM", "description": "Pipeline stage in which the scan jobs run" }, { @@ -40,6 +44,7 @@ "type": "string", "default_value": "", "value": "", + "size": "SMALL", "description": "Maximum depth of language and framework detection" } ], diff --git a/changelogs/unreleased/218234-pipeline-icon-alignment.yml b/changelogs/unreleased/218234-pipeline-icon-alignment.yml new file mode 100644 index 00000000000..18e40bb8a25 --- /dev/null +++ b/changelogs/unreleased/218234-pipeline-icon-alignment.yml @@ -0,0 +1,5 @@ +--- +title: Center align pipeline graph icons +merge_request: 39848 +author: +type: fixed diff --git a/changelogs/unreleased/sh-add-azure-blob-support.yml b/changelogs/unreleased/sh-add-azure-blob-support.yml new file mode 100644 index 00000000000..32af5deef20 --- /dev/null +++ b/changelogs/unreleased/sh-add-azure-blob-support.yml @@ -0,0 +1,5 @@ +--- +title: Add Azure Blob Storage support +merge_request: 38882 +author: +type: added diff --git a/config/initializers/carrierwave_patch.rb b/config/initializers/carrierwave_patch.rb index 94a79e5990d..53fba307926 100644 --- a/config/initializers/carrierwave_patch.rb +++ b/config/initializers/carrierwave_patch.rb @@ -4,6 +4,12 @@ require "carrierwave/storage/fog" # This pulls in https://github.com/carrierwaveuploader/carrierwave/pull/2504 to support # sending AWS S3 encryption headers when copying objects. +# +# This patch also incorporates +# https://github.com/carrierwaveuploader/carrierwave/pull/2375 to +# provide Azure support. This is already in CarrierWave v2.1.x, but +# upgrading this gem is a significant task: +# https://gitlab.com/gitlab-org/gitlab/-/issues/216067 module CarrierWave module Storage class Fog < Abstract @@ -16,6 +22,31 @@ module CarrierWave def copy_to_options acl_header.merge(@uploader.fog_attributes) end + + def authenticated_url(options = {}) + if %w[AWS Google Rackspace OpenStack AzureRM].include?(@uploader.fog_credentials[:provider]) + # avoid a get by using local references + local_directory = connection.directories.new(key: @uploader.fog_directory) + local_file = local_directory.files.new(key: path) + expire_at = ::Fog::Time.now + @uploader.fog_authenticated_url_expiration + case @uploader.fog_credentials[:provider] + when 'AWS', 'Google' + # Older versions of fog-google do not support options as a parameter + if url_options_supported?(local_file) + local_file.url(expire_at, options) + else + warn "Options hash not supported in #{local_file.class}. You may need to upgrade your Fog provider." + local_file.url(expire_at) + end + when 'Rackspace' + connection.get_object_https_url(@uploader.fog_directory, path, expire_at, options) + when 'OpenStack' + connection.get_object_https_url(@uploader.fog_directory, path, expire_at) + else + local_file.url(expire_at) + end + end + end end end end diff --git a/config/initializers/direct_upload_support.rb b/config/initializers/direct_upload_support.rb index 0fc6e82207e..94e90727f0c 100644 --- a/config/initializers/direct_upload_support.rb +++ b/config/initializers/direct_upload_support.rb @@ -1,5 +1,5 @@ class DirectUploadsValidator - SUPPORTED_DIRECT_UPLOAD_PROVIDERS = %w(Google AWS).freeze + SUPPORTED_DIRECT_UPLOAD_PROVIDERS = %w(Google AWS AzureRM).freeze ValidationError = Class.new(StandardError) @@ -13,22 +13,32 @@ class DirectUploadsValidator raise ValidationError, "No provider configured for '#{uploader_type}'. #{supported_provider_text}" if provider.blank? - return if SUPPORTED_DIRECT_UPLOAD_PROVIDERS.include?(provider) + return if provider_loaded?(provider) raise ValidationError, "Object storage provider '#{provider}' is not supported " \ "when 'direct_upload' is used for '#{uploader_type}'. #{supported_provider_text}" end + private + + def provider_loaded?(provider) + return false unless SUPPORTED_DIRECT_UPLOAD_PROVIDERS.include?(provider) + + require 'fog/azurerm' if provider == 'AzureRM' + + true + end + def supported_provider_text - "Only #{SUPPORTED_DIRECT_UPLOAD_PROVIDERS.join(', ')} are supported." + "Only #{SUPPORTED_DIRECT_UPLOAD_PROVIDERS.to_sentence} are supported." end end DirectUploadsValidator.new.tap do |validator| CONFIGS = { artifacts: Gitlab.config.artifacts, - uploads: Gitlab.config.uploads, - lfs: Gitlab.config.lfs + lfs: Gitlab.config.lfs, + uploads: Gitlab.config.uploads }.freeze CONFIGS.each do |uploader_type, uploader| diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index b0d04a2f48d..1d920894eec 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -13750,6 +13750,11 @@ type SastCiConfigurationEntity { ): SastCiConfigurationOptionsEntityConnection """ + Size of the UI component. + """ + size: SastUiComponentSize + + """ Type of the field value. """ type: String @@ -13846,6 +13851,15 @@ type SastCiConfigurationOptionsEntityEdge { } """ +Size of UI component in SAST configuration page +""" +enum SastUiComponentSize { + LARGE + MEDIUM + SMALL +} + +""" Represents a resource scanned by a security scan """ type ScannedResource { diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index a332c218a84..7ee37fb4d43 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -40152,6 +40152,20 @@ "deprecationReason": null }, { + "name": "size", + "description": "Size of the UI component.", + "args": [ + + ], + "type": { + "kind": "ENUM", + "name": "SastUiComponentSize", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { "name": "type", "description": "Type of the field value.", "args": [ @@ -40453,6 +40467,35 @@ "possibleTypes": null }, { + "kind": "ENUM", + "name": "SastUiComponentSize", + "description": "Size of UI component in SAST configuration page", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "SMALL", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MEDIUM", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LARGE", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { "kind": "OBJECT", "name": "ScannedResource", "description": "Represents a resource scanned by a security scan", diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 21aba30d90f..8ba1862b009 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1948,6 +1948,7 @@ Represents an entity in SAST CI configuration | `description` | String | Entity description that is displayed on the form. | | `field` | String | CI keyword of entity. | | `label` | String | Label for entity used in the form. | +| `size` | SastUiComponentSize | Size of the UI component. | | `type` | String | Type of the field value. | | `value` | String | Current value of the entity. | diff --git a/doc/ci/docker/using_kaniko.md b/doc/ci/docker/using_kaniko.md index 1580080ac6e..41c8f04f66e 100644 --- a/doc/ci/docker/using_kaniko.md +++ b/doc/ci/docker/using_kaniko.md @@ -63,6 +63,7 @@ build: name: gcr.io/kaniko-project/executor:debug entrypoint: [""] script: + - mkdir -p /kaniko/.docker - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG only: diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 5f0c8e1cd3d..694754a33d1 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -926,14 +926,14 @@ For example, the following are equivalent configuration: ``` NOTE: **Note:** -A pipeline won't be created if it only contains jobs in `.pre` or `.post` stages. +A pipeline is not created if all jobs are in `.pre` or `.post` stages. ### `extends` > Introduced in GitLab 11.3. -`extends` defines entry names that a job that uses `extends` is going to -inherit from. +`extends` defines entry names that a job that uses `extends` +inherits from. It's an alternative to using [YAML anchors](#anchors) and is a little more flexible and readable: @@ -955,12 +955,12 @@ rspec: ``` In the example above, the `rspec` job inherits from the `.tests` template job. -GitLab will perform a reverse deep merge based on the keys. GitLab will: +GitLab performs a reverse deep merge based on the keys. GitLab: -- Merge the `rspec` contents into `.tests` recursively. -- Not merge the values of the keys. +- Merges the `rspec` contents into `.tests` recursively. +- Doesn't merge the values of the keys. -This results in the following `rspec` job: +The result is this `rspec` job: ```yaml rspec: @@ -981,8 +981,8 @@ If you do want to include the `rake test`, see [`before_script` and `after_scrip `.tests` in this example is a [hidden job](#hide-jobs), but it's possible to inherit from regular jobs as well. -`extends` supports multi-level inheritance, however it's not recommended to -use more than three levels. The maximum nesting level that is supported is 10. +`extends` supports multi-level inheritance. You should avoid using more than 3 levels, +but you can use as many as ten. The following example has two levels of inheritance: ```yaml @@ -1016,7 +1016,7 @@ In GitLab 12.0 and later, it's also possible to use multiple parents for `extends` is able to merge hashes but not arrays. The algorithm used for merge is "closest scope wins", so -keys from the last member will always override anything defined on other +keys from the last member always override anything defined on other levels. For example: ```yaml @@ -1098,7 +1098,7 @@ useTemplate: extends: .template ``` -This will run a job called `useTemplate` that runs `echo Hello!` as defined in +This example runs a job called `useTemplate` that runs `echo Hello!` as defined in the `.template` job, and uses the `alpine` Docker image as defined in the local job. ### `rules` @@ -1113,8 +1113,8 @@ If included, the job also has [certain attributes](#rules-attributes) added to it. CAUTION: **Caution:** -`rules` can't be used in combination with [`only/except`](#onlyexcept-basic) because it is a replacement for -that functionality. If you attempt to do this, the linter returns a +`rules` replaces [`only/except`](#onlyexcept-basic) and can't be used in conjunction with it. +If you attempt to use both keywords in the same job, the linter returns a `key may not be used with rules` error. #### Rules attributes @@ -1466,7 +1466,7 @@ use `when: never`. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/24021) in GitLab 12.4. -`exists` accepts an array of paths and will match if any of these paths exist +`exists` accepts an array of paths and matches if any of these paths exist as files in the repository. For example: @@ -1494,7 +1494,7 @@ job: NOTE: **Note:** For performance reasons, using `exists` with patterns is limited to 10000 -checks. After the 10000th check, rules with patterned globs will always match. +checks. After the 10000th check, rules with patterned globs always match. #### `rules:allow_failure` @@ -1517,18 +1517,18 @@ job: allow_failure: true ``` -In this example, if the first rule matches, then the job will have `when: manual` and `allow_failure: true`. +In this example, if the first rule matches, then the job has `when: manual` and `allow_failure: true`. #### Complex rule clauses -To conjoin `if`, `changes`, and `exists` clauses with an AND, use them in the +To conjoin `if`, `changes`, and `exists` clauses with an `AND`, use them in the same rule. In the following example: -- We run the job manually if `Dockerfile` or any file in `docker/scripts/` - has changed AND `$VAR == "string value"`. -- Otherwise, the job won't be included in the pipeline. +- If the dockerfile or any file in `/docker/scripts` has changed, and var=blah, + then the job runs manually +- Otherwise, the job isn't included in the pipeline. ```yaml docker build: diff --git a/lib/object_storage/config.rb b/lib/object_storage/config.rb index 2a91c726ec9..d0777914cb5 100644 --- a/lib/object_storage/config.rb +++ b/lib/object_storage/config.rb @@ -58,6 +58,10 @@ module ObjectStorage provider == 'Google' end + def azure? + provider == 'AzureRM' + end + def fog_attributes @fog_attributes ||= begin return {} unless enabled? && aws? diff --git a/lib/object_storage/direct_upload.rb b/lib/object_storage/direct_upload.rb index 5784a089bba..90199114f2c 100644 --- a/lib/object_storage/direct_upload.rb +++ b/lib/object_storage/direct_upload.rb @@ -62,8 +62,16 @@ module ObjectStorage end def workhorse_client_hash - return {} unless config.aws? + if config.aws? + workhorse_aws_hash + elsif config.azure? + workhorse_azure_hash + else + {} + end + end + def workhorse_aws_hash { UseWorkhorseClient: use_workhorse_s3_client?, RemoteTempObjectID: object_name, @@ -82,6 +90,21 @@ module ObjectStorage } end + def workhorse_azure_hash + { + # Azure requires Workhorse client because direct uploads can't + # use pre-signed URLs without buffering the whole file to disk. + UseWorkhorseClient: true, + RemoteTempObjectID: object_name, + ObjectStorage: { + Provider: 'AzureRM', + GoCloudConfig: { + URL: "azblob://#{bucket_name}" + } + } + } + end + def use_workhorse_s3_client? return false unless Feature.enabled?(:use_workhorse_s3_client, default_enabled: true) return false unless config.use_iam_profile? || config.consolidated_settings? diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 497757c72d0..e45e5a8e035 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1410,12 +1410,6 @@ msgstr[1] "" msgid "Add %{linkStart}assets%{linkEnd} to your Release. GitLab automatically includes read-only assets, like source code and release evidence." msgstr "" -msgid "Add .gitlab-ci.yml to enable or configure SAST" -msgstr "" - -msgid "Add .gitlab-ci.yml to enable or configure SAST security scanning using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings." -msgstr "" - msgid "Add CHANGELOG" msgstr "" @@ -22200,6 +22194,12 @@ msgstr "" msgid "Set %{epic_ref} as the parent epic." msgstr "" +msgid "Set .gitlab-ci.yml to enable or configure SAST" +msgstr "" + +msgid "Set .gitlab-ci.yml to enable or configure SAST security scanning using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings." +msgstr "" + msgid "Set a default template for issue descriptions." msgstr "" @@ -24830,6 +24830,9 @@ msgstr "" msgid "There was an error while fetching the table data." msgstr "" +msgid "There was an error while fetching value stream analytics %{requestTypeName} data." +msgstr "" + msgid "There was an error while fetching value stream analytics data." msgstr "" @@ -24839,12 +24842,6 @@ msgstr "" msgid "There was an error while fetching value stream analytics duration median data." msgstr "" -msgid "There was an error while fetching value stream analytics recent activity data." -msgstr "" - -msgid "There was an error while fetching value stream analytics time summary data." -msgstr "" - msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again." msgstr "" @@ -29728,6 +29725,9 @@ msgstr "" msgid "quick actions" msgstr "" +msgid "recent activity" +msgstr "" + msgid "register" msgstr "" @@ -29892,6 +29892,9 @@ msgstr "" msgid "this document" msgstr "" +msgid "time summary" +msgstr "" + msgid "to help your contributors communicate effectively!" msgstr "" diff --git a/spec/initializers/carrierwave_patch_spec.rb b/spec/initializers/carrierwave_patch_spec.rb new file mode 100644 index 00000000000..d577eca2ac7 --- /dev/null +++ b/spec/initializers/carrierwave_patch_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'CarrierWave::Storage::Fog::File' do + let(:uploader_class) { Class.new(CarrierWave::Uploader::Base) } + let(:uploader) { uploader_class.new } + let(:storage) { CarrierWave::Storage::Fog.new(uploader) } + let(:azure_options) do + { + azure_storage_account_name: 'AZURE_ACCOUNT_NAME', + azure_storage_access_key: 'AZURE_ACCESS_KEY', + provider: 'AzureRM' + } + end + + subject { CarrierWave::Storage::Fog::File.new(uploader, storage, 'test') } + + before do + require 'fog/azurerm' + allow(uploader).to receive(:fog_credentials).and_return(azure_options) + Fog.mock! + end + + describe '#authenticated_url' do + context 'with Azure' do + it 'has an authenticated URL' do + expect(subject.authenticated_url).to eq("https://sa.blob.core.windows.net/test_container/test_blob?token") + end + end + end +end diff --git a/spec/initializers/direct_upload_support_spec.rb b/spec/initializers/direct_upload_support_spec.rb index aa77c0905c9..670deecb4f1 100644 --- a/spec/initializers/direct_upload_support_spec.rb +++ b/spec/initializers/direct_upload_support_spec.rb @@ -8,7 +8,7 @@ RSpec.describe 'Direct upload support' do end where(:config_name) do - %w(lfs artifacts uploads) + %w(artifacts lfs uploads) end with_them do @@ -52,11 +52,19 @@ RSpec.describe 'Direct upload support' do end end + context 'when provider is AzureRM' do + let(:provider) { 'AzureRM' } + + it 'succeeds' do + expect { subject }.not_to raise_error + end + end + context 'when connection is empty' do let(:connection) { nil } it 'raises an error' do - expect { subject }.to raise_error "No provider configured for '#{config_name}'. Only Google, AWS are supported." + expect { subject }.to raise_error "No provider configured for '#{config_name}'. Only Google, AWS, and AzureRM are supported." end end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 73ba89c4a96..37b5d8a1021 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -12,6 +12,7 @@ issues: - resource_weight_events - resource_milestone_events - resource_state_events +- resource_iteration_events - sent_notifications - sentry_issue - label_links diff --git a/spec/lib/object_storage/direct_upload_spec.rb b/spec/lib/object_storage/direct_upload_spec.rb index 7e0f31cbd23..b11926aeb49 100644 --- a/spec/lib/object_storage/direct_upload_spec.rb +++ b/spec/lib/object_storage/direct_upload_spec.rb @@ -105,7 +105,7 @@ RSpec.describe ObjectStorage::DirectUpload do end end - describe '#to_hash' do + describe '#to_hash', :aggregate_failures do subject { direct_upload.to_hash } shared_examples 'a valid S3 upload' do @@ -200,6 +200,21 @@ RSpec.describe ObjectStorage::DirectUpload do end end + shared_examples 'a valid AzureRM upload' do + before do + require 'fog/azurerm' + end + + it_behaves_like 'a valid upload' + + it 'enables the Workhorse client' do + expect(subject[:UseWorkhorseClient]).to be true + expect(subject[:RemoteTempObjectID]).to eq(object_name) + expect(subject[:ObjectStorage][:Provider]).to eq('AzureRM') + expect(subject[:ObjectStorage][:GoCloudConfig]).to eq({ URL: "azblob://#{bucket_name}" }) + end + end + shared_examples 'a valid upload' do it "returns valid structure" do expect(subject).to have_key(:Timeout) @@ -370,5 +385,31 @@ RSpec.describe ObjectStorage::DirectUpload do it_behaves_like 'a valid upload without multipart data' end end + + context 'when AzureRM is used' do + let(:credentials) do + { + provider: 'AzureRM', + azure_storage_account_name: 'azuretest', + azure_storage_access_key: 'ABCD1234' + } + end + + let(:storage_url) { 'https://azuretest.blob.core.windows.net' } + + context 'when length is known' do + let(:has_length) { true } + + it_behaves_like 'a valid AzureRM upload' + it_behaves_like 'a valid upload without multipart data' + end + + context 'when length is unknown' do + let(:has_length) { false } + + it_behaves_like 'a valid AzureRM upload' + it_behaves_like 'a valid upload without multipart data' + end + end end end diff --git a/spec/services/resource_events/change_milestone_service_spec.rb b/spec/services/resource_events/change_milestone_service_spec.rb index 9c0f9420f7a..3a9dadbd40e 100644 --- a/spec/services/resource_events/change_milestone_service_spec.rb +++ b/spec/services/resource_events/change_milestone_service_spec.rb @@ -3,9 +3,15 @@ require 'spec_helper' RSpec.describe ResourceEvents::ChangeMilestoneService do + let_it_be(:timebox) { create(:milestone) } + + let(:created_at_time) { Time.utc(2019, 12, 30) } + let(:add_timebox_args) { { created_at: created_at_time, old_milestone: nil } } + let(:remove_timebox_args) { { created_at: created_at_time, old_milestone: timebox } } + [:issue, :merge_request].each do |issuable| - it_behaves_like 'a milestone events creator' do - let(:resource) { create(issuable) } + it_behaves_like 'timebox(milestone or iteration) resource events creator', ResourceMilestoneEvent do + let_it_be(:resource) { create(issuable) } end end end diff --git a/spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb b/spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb index ef41c2fcc13..d70ed707822 100644 --- a/spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb +++ b/spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb @@ -1,49 +1,63 @@ # frozen_string_literal: true -RSpec.shared_examples 'a milestone events creator' do +RSpec.shared_examples 'timebox(milestone or iteration) resource events creator' do |timebox_event_class| let_it_be(:user) { create(:user) } - let(:created_at_time) { Time.utc(2019, 12, 30) } - let(:service) { described_class.new(resource, user, created_at: created_at_time, old_milestone: nil) } - - context 'when milestone is present' do - let_it_be(:milestone) { create(:milestone) } + context 'when milestone/iteration is added' do + let(:service) { described_class.new(resource, user, add_timebox_args) } before do - resource.milestone = milestone + set_timebox(timebox_event_class, timebox) end it 'creates the expected event record' do - expect { service.execute }.to change { ResourceMilestoneEvent.count }.by(1) + expect { service.execute }.to change { timebox_event_class.count }.by(1) - expect_event_record(ResourceMilestoneEvent.last, action: 'add', milestone: milestone, state: 'opened') + expect_event_record(timebox_event_class, timebox_event_class.last, action: 'add', state: 'opened', timebox: timebox) end end - context 'when milestones is not present' do + context 'when milestone/iteration is removed' do + let(:service) { described_class.new(resource, user, remove_timebox_args) } + before do - resource.milestone = nil + set_timebox(timebox_event_class, nil) end - let(:old_milestone) { create(:milestone, project: resource.project) } - let(:service) { described_class.new(resource, user, created_at: created_at_time, old_milestone: old_milestone) } - it 'creates the expected event records' do - expect { service.execute }.to change { ResourceMilestoneEvent.count }.by(1) + expect { service.execute }.to change { timebox_event_class.count }.by(1) - expect_event_record(ResourceMilestoneEvent.last, action: 'remove', milestone: old_milestone, state: 'opened') + expect_event_record(timebox_event_class, timebox_event_class.last, action: 'remove', timebox: timebox, state: 'opened') end end - def expect_event_record(event, expected_attrs) + def expect_event_record(timebox_event_class, event, expected_attrs) expect(event.action).to eq(expected_attrs[:action]) - expect(event.state).to eq(expected_attrs[:state]) expect(event.user).to eq(user) expect(event.issue).to eq(resource) if resource.is_a?(Issue) expect(event.issue).to be_nil unless resource.is_a?(Issue) expect(event.merge_request).to eq(resource) if resource.is_a?(MergeRequest) expect(event.merge_request).to be_nil unless resource.is_a?(MergeRequest) - expect(event.milestone).to eq(expected_attrs[:milestone]) expect(event.created_at).to eq(created_at_time) + expect_timebox(timebox_event_class, event, expected_attrs) + end + + def set_timebox(timebox_event_class, timebox) + case timebox_event_class.name + when 'ResourceMilestoneEvent' + resource.milestone = timebox + when 'ResourceIterationEvent' + resource.iteration = timebox + end + end + + def expect_timebox(timebox_event_class, event, expected_attrs) + case timebox_event_class.name + when 'ResourceMilestoneEvent' + expect(event.state).to eq(expected_attrs[:state]) + expect(event.milestone).to eq(expected_attrs[:timebox]) + when 'ResourceIterationEvent' + expect(event.iteration).to eq(expected_attrs[:timebox]) + end end end |