Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock29
-rw-r--r--app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js1
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js2
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss3
-rw-r--r--app/services/resource_events/base_change_timebox_service.rb35
-rw-r--r--app/services/resource_events/change_milestone_service.rb29
-rw-r--r--app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json5
-rw-r--r--changelogs/unreleased/218234-pipeline-icon-alignment.yml5
-rw-r--r--changelogs/unreleased/sh-add-azure-blob-support.yml5
-rw-r--r--config/initializers/carrierwave_patch.rb31
-rw-r--r--config/initializers/direct_upload_support.rb20
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql14
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json43
-rw-r--r--doc/api/graphql/reference/index.md1
-rw-r--r--doc/ci/docker/using_kaniko.md1
-rw-r--r--doc/ci/yaml/README.md40
-rw-r--r--lib/object_storage/config.rb4
-rw-r--r--lib/object_storage/direct_upload.rb25
-rw-r--r--locale/gitlab.pot27
-rw-r--r--spec/initializers/carrierwave_patch_spec.rb32
-rw-r--r--spec/initializers/direct_upload_support_spec.rb12
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/object_storage/direct_upload_spec.rb43
-rw-r--r--spec/services/resource_events/change_milestone_service_spec.rb10
-rw-r--r--spec/support/shared_examples/services/resource_events/change_milestone_service_shared_examples.rb52
26 files changed, 386 insertions, 85 deletions
diff --git a/Gemfile b/Gemfile
index d24850274d4..39101188454 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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