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:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-10-07 00:09:01 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-10-07 00:09:01 +0300
commitca3ff7f842fb1e4bf124356a6faccd3e65b7aba3 (patch)
treebefb345c84e172b0592378ca17298f7a0c955a06
parent1287690a3678ad0ec21c9b2f3b21ae18257d5e22 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop_todo/style/class_and_module_children.yml1
-rw-r--r--app/assets/javascripts/alert_management/components/alert_management_table.vue7
-rw-r--r--app/assets/javascripts/editor/schema/ci.json506
-rw-r--r--app/assets/javascripts/integrations/edit/components/integration_form.vue32
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/constants.js2
-rw-r--r--app/assets/stylesheets/components/milestone_combobox.scss5
-rw-r--r--app/assets/stylesheets/components/release_block.scss3
-rw-r--r--app/assets/stylesheets/page_bundles/operations.scss (renamed from app/assets/stylesheets/components/dashboard_skeleton.scss)24
-rw-r--r--app/assets/stylesheets/page_bundles/releases.scss (renamed from app/assets/stylesheets/components/release_block_milestone_info.scss)6
-rw-r--r--app/assets/stylesheets/pages/notes.scss4
-rw-r--r--app/finders/labels_finder.rb2
-rw-r--r--app/graphql/resolvers/base_issues_resolver.rb1
-rw-r--r--app/graphql/resolvers/bulk_labels_resolver.rb27
-rw-r--r--app/graphql/resolvers/concerns/resolves_merge_requests.rb1
-rw-r--r--app/graphql/types/issue_type.rb6
-rw-r--r--app/graphql/types/merge_request_type.rb7
-rw-r--r--app/models/ci/secure_file.rb32
-rw-r--r--app/models/group_label.rb1
-rw-r--r--app/models/label.rb9
-rw-r--r--app/models/preloaders/labels_preloader.rb2
-rw-r--r--app/models/project_label.rb1
-rw-r--r--app/models/user.rb1
-rw-r--r--app/policies/group_label_policy.rb2
-rw-r--r--app/policies/project_label_policy.rb2
-rw-r--r--app/services/labels/promote_service.rb4
-rw-r--r--app/services/merge_requests/merge_service.rb24
-rw-r--r--app/services/pages_domains/create_acme_order_service.rb10
-rw-r--r--app/views/projects/releases/index.html.haml1
-rw-r--r--app/views/projects/releases/show.html.haml1
-rw-r--r--app/workers/all_queues.yml9
-rw-r--r--app/workers/ci/parse_secure_file_metadata_worker.rb15
-rw-r--r--config/application.rb2
-rw-r--r--config/feature_flags/development/approval_rules_eligible_filter.yml (renamed from config/feature_flags/development/pages_lets_encrypt_ecdsa.yml)10
-rw-r--r--config/feature_flags/development/secure_files_metadata_parsers.yml8
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--doc/development/documentation/styleguide/word_list.md2
-rw-r--r--doc/development/gitlab_flavored_markdown/specification_guide/index.md19
-rw-r--r--glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.md (renamed from glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.txt)3
-rw-r--r--glfm_specification/input/gitlab_flavored_markdown/glfm_intro.md (renamed from glfm_specification/input/gitlab_flavored_markdown/glfm_intro.txt)0
-rw-r--r--glfm_specification/output/spec.txt3
-rw-r--r--lib/api/ci/secure_files.rb4
-rw-r--r--lib/gitlab/ci/secure_files/cer.rb56
-rw-r--r--lib/gitlab/ci/secure_files/x509_name.rb15
-rw-r--r--lib/gitlab/ci/templates/PHP.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Python.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Rust.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Scala.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Swift.gitlab-ci.yml5
-rw-r--r--lib/gitlab/jira_import/handle_labels_service.rb2
-rw-r--r--scripts/lib/glfm/constants.rb6
-rw-r--r--scripts/lib/glfm/update_specification.rb50
-rw-r--r--spec/features/profiles/password_spec.rb23
-rw-r--r--spec/fixtures/ci_secure_files/sample.cerbin0 -> 1479 bytes
-rw-r--r--spec/frontend/alert_management/components/alert_management_table_spec.js13
-rw-r--r--spec/lib/gitlab/ci/secure_files/cer_spec.rb70
-rw-r--r--spec/lib/gitlab/ci/secure_files/x509_name_spec.rb30
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/models/ci/secure_file_spec.rb56
-rw-r--r--spec/models/user_spec.rb7
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb16
-rw-r--r--spec/requests/api/graphql/project/merge_requests_spec.rb23
-rw-r--r--spec/requests/git_http_spec.rb3
-rw-r--r--spec/scripts/lib/glfm/update_specification_spec.rb24
-rw-r--r--spec/services/merge_requests/ff_merge_service_spec.rb6
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb76
-rw-r--r--spec/services/pages_domains/create_acme_order_service_spec.rb10
-rw-r--r--spec/support/helpers/ldap_helpers.rb26
-rw-r--r--spec/support/helpers/login_helpers.rb10
-rw-r--r--spec/support/shared_examples/graphql/n_plus_one_query_examples.rb15
-rw-r--r--spec/workers/ci/parse_secure_file_metadata_worker_spec.rb31
71 files changed, 1112 insertions, 285 deletions
diff --git a/.rubocop_todo/style/class_and_module_children.yml b/.rubocop_todo/style/class_and_module_children.yml
index 2674902c7ee..bff827ba5d3 100644
--- a/.rubocop_todo/style/class_and_module_children.yml
+++ b/.rubocop_todo/style/class_and_module_children.yml
@@ -521,6 +521,7 @@ Style/ClassAndModuleChildren:
- 'ee/db/fixtures/development/21_dast_profiles.rb'
- 'ee/db/fixtures/development/30_customizable_cycle_analytics.rb'
- 'ee/db/fixtures/development/32_compliance_report_violations.rb'
+ - 'ee/db/fixtures/development/35_merge_request_predictions.rb'
- 'ee/db/fixtures/development/90_productivity_analytics.rb'
- 'ee/lib/ee/gitlab/analytics/cycle_analytics/aggregated/base_query_builder.rb'
- 'ee/lib/ee/gitlab/analytics/cycle_analytics/base_query_builder.rb'
diff --git a/app/assets/javascripts/alert_management/components/alert_management_table.vue b/app/assets/javascripts/alert_management/components/alert_management_table.vue
index 37a6ea16018..c0cac958a42 100644
--- a/app/assets/javascripts/alert_management/components/alert_management_table.vue
+++ b/app/assets/javascripts/alert_management/components/alert_management_table.vue
@@ -216,8 +216,11 @@ export default {
this.pagination = initialPaginationState;
this.sort = sortObjectToString({ sortBy, sortDesc });
},
+ showAlertLink({ iid }) {
+ return joinPaths(window.location.pathname, iid, 'details');
+ },
navigateToAlertDetails({ iid }, index, { metaKey }) {
- return visitUrl(joinPaths(window.location.pathname, iid, 'details'), metaKey);
+ return visitUrl(this.showAlertLink({ iid }), metaKey);
},
hasAssignees(assignees) {
return Boolean(assignees.nodes?.length);
@@ -357,7 +360,7 @@ export default {
:title="`${item.iid} - ${item.title}`"
data-testid="idField"
>
- #{{ item.iid }} {{ item.title }}
+ <gl-link :href="showAlertLink(item)"> #{{ item.iid }} {{ item.title }} </gl-link>
</div>
</template>
diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json
index afdf6b9eee5..52bf9d25e6b 100644
--- a/app/assets/javascripts/editor/schema/ci.json
+++ b/app/assets/javascripts/editor/schema/ci.json
@@ -8,34 +8,74 @@
"type": "string",
"format": "uri"
},
- "image": { "$ref": "#/definitions/image" },
- "services": { "$ref": "#/definitions/services" },
- "before_script": { "$ref": "#/definitions/before_script" },
- "after_script": { "$ref": "#/definitions/after_script" },
- "variables": { "$ref": "#/definitions/globalVariables" },
- "cache": { "$ref": "#/definitions/cache" },
- "!reference": {"$ref" : "#/definitions/!reference"},
+ "image": {
+ "$ref": "#/definitions/image"
+ },
+ "services": {
+ "$ref": "#/definitions/services"
+ },
+ "before_script": {
+ "$ref": "#/definitions/before_script"
+ },
+ "after_script": {
+ "$ref": "#/definitions/after_script"
+ },
+ "variables": {
+ "$ref": "#/definitions/globalVariables"
+ },
+ "cache": {
+ "$ref": "#/definitions/cache"
+ },
+ "!reference": {
+ "$ref": "#/definitions/!reference"
+ },
"default": {
"type": "object",
"properties": {
- "after_script": { "$ref": "#/definitions/after_script" },
- "artifacts": { "$ref": "#/definitions/artifacts" },
- "before_script": { "$ref": "#/definitions/before_script" },
- "cache": { "$ref": "#/definitions/cache" },
- "image": { "$ref": "#/definitions/image" },
- "interruptible": { "$ref": "#/definitions/interruptible" },
- "retry": { "$ref": "#/definitions/retry" },
- "services": { "$ref": "#/definitions/services" },
- "tags": { "$ref": "#/definitions/tags" },
- "timeout": { "$ref": "#/definitions/timeout" },
- "!reference": {"$ref" : "#/definitions/!reference"}
+ "after_script": {
+ "$ref": "#/definitions/after_script"
+ },
+ "artifacts": {
+ "$ref": "#/definitions/artifacts"
+ },
+ "before_script": {
+ "$ref": "#/definitions/before_script"
+ },
+ "cache": {
+ "$ref": "#/definitions/cache"
+ },
+ "image": {
+ "$ref": "#/definitions/image"
+ },
+ "interruptible": {
+ "$ref": "#/definitions/interruptible"
+ },
+ "retry": {
+ "$ref": "#/definitions/retry"
+ },
+ "services": {
+ "$ref": "#/definitions/services"
+ },
+ "tags": {
+ "$ref": "#/definitions/tags"
+ },
+ "timeout": {
+ "$ref": "#/definitions/timeout"
+ },
+ "!reference": {
+ "$ref": "#/definitions/!reference"
+ }
},
"additionalProperties": false
},
"stages": {
"type": "array",
"markdownDescription": "Groups jobs into stages. All jobs in one stage must complete before next stage is executed. Defaults to ['build', 'test', 'deploy']. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#stages).",
- "default": ["build", "test", "deploy"],
+ "default": [
+ "build",
+ "test",
+ "deploy"
+ ],
"items": {
"type": "string"
},
@@ -45,10 +85,14 @@
"include": {
"markdownDescription": "Can be `IncludeItem` or `IncludeItem[]`. Each `IncludeItem` will be a string, or an object with properties for the method if including external YAML file. The external content will be fetched, included and evaluated along the `.gitlab-ci.yml`. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#include).",
"oneOf": [
- { "$ref": "#/definitions/include_item" },
+ {
+ "$ref": "#/definitions/include_item"
+ },
{
"type": "array",
- "items": { "$ref": "#/definitions/include_item" }
+ "items": {
+ "$ref": "#/definitions/include_item"
+ }
}
]
},
@@ -63,17 +107,36 @@
"type": "array",
"items": {
"anyOf": [
- {"type": "object"},
- {"type": "array", "minLength": 1, "items": { "type": "string" }}
+ {
+ "type": "object"
+ },
+ {
+ "type": "array",
+ "minLength": 1,
+ "items": {
+ "type": "string"
+ }
+ }
],
"properties": {
- "if": { "$ref": "#/definitions/if" },
- "changes": { "$ref": "#/definitions/changes" },
- "exists": { "$ref": "#/definitions/exists" },
- "variables": { "$ref": "#/definitions/variables" },
+ "if": {
+ "$ref": "#/definitions/if"
+ },
+ "changes": {
+ "$ref": "#/definitions/changes"
+ },
+ "exists": {
+ "$ref": "#/definitions/exists"
+ },
+ "variables": {
+ "$ref": "#/definitions/variables"
+ },
"when": {
"type": "string",
- "enum": ["always", "never"]
+ "enum": [
+ "always",
+ "never"
+ ]
}
},
"additionalProperties": false
@@ -86,8 +149,12 @@
"^[.]": {
"description": "Hidden keys.",
"anyOf": [
- { "$ref": "#/definitions/job_template" },
- { "description": "Arbitrary YAML anchor." }
+ {
+ "$ref": "#/definitions/job_template"
+ },
+ {
+ "description": "Arbitrary YAML anchor."
+ }
]
}
},
@@ -134,15 +201,21 @@
"default": "on_success",
"oneOf": [
{
- "enum": ["on_success"],
+ "enum": [
+ "on_success"
+ ],
"description": "Upload artifacts only when the job succeeds (this is the default)."
},
{
- "enum": ["on_failure"],
+ "enum": [
+ "on_failure"
+ ],
"description": "Upload artifacts only when the job fails."
},
{
- "enum": ["always"],
+ "enum": [
+ "always"
+ ],
"description": "Upload artifacts regardless of job status."
}
]
@@ -180,7 +253,9 @@
"properties": {
"coverage_format": {
"description": "Code coverage format used by the test framework.",
- "enum": ["cobertura"]
+ "enum": [
+ "cobertura"
+ ]
},
"path": {
"description": "Path to the coverage report file that should be parsed.",
@@ -284,9 +359,13 @@
"format": "uri-reference",
"pattern": "\\.ya?ml$"
},
- "rules": { "$ref": "#/definitions/rules" }
+ "rules": {
+ "$ref": "#/definitions/rules"
+ }
},
- "required": ["local"]
+ "required": [
+ "local"
+ ]
},
{
"type": "object",
@@ -319,7 +398,10 @@
]
}
},
- "required": ["project", "file"]
+ "required": [
+ "project",
+ "file"
+ ]
},
{
"type": "object",
@@ -332,7 +414,9 @@
"pattern": "\\.ya?ml$"
}
},
- "required": ["template"]
+ "required": [
+ "template"
+ ]
},
{
"type": "object",
@@ -345,7 +429,9 @@
"pattern": "^https?://.+\\.ya?ml$"
}
},
- "required": ["remote"]
+ "required": [
+ "remote"
+ ]
}
]
},
@@ -406,7 +492,9 @@
]
}
},
- "required": ["name"]
+ "required": [
+ "name"
+ ]
},
{
"type": "array",
@@ -487,7 +575,9 @@
"minLength": 1
}
},
- "required": ["name"]
+ "required": [
+ "name"
+ ]
}
]
}
@@ -511,20 +601,37 @@
"engine": {
"type": "object",
"properties": {
- "name": { "type": "string" },
- "path": { "type": "string" }
+ "name": {
+ "type": "string"
+ },
+ "path": {
+ "type": "string"
+ }
},
- "required": ["name", "path"]
+ "required": [
+ "name",
+ "path"
+ ]
},
- "path": { "type": "string" },
- "field": { "type": "string" }
+ "path": {
+ "type": "string"
+ },
+ "field": {
+ "type": "string"
+ }
},
- "required": ["engine", "path", "field"]
+ "required": [
+ "engine",
+ "path",
+ "field"
+ ]
}
]
}
},
- "required": ["vault"]
+ "required": [
+ "vault"
+ ]
}
},
"before_script": {
@@ -570,17 +677,40 @@
"type": "object",
"additionalProperties": false,
"properties": {
- "if": { "$ref": "#/definitions/if" },
- "changes": { "$ref": "#/definitions/changes" },
- "exists": { "$ref": "#/definitions/exists" },
- "variables": { "$ref": "#/definitions/variables" },
- "when": { "$ref": "#/definitions/when" },
- "start_in": { "$ref": "#/definitions/start_in" },
- "allow_failure": { "$ref": "#/definitions/allow_failure" }
+ "if": {
+ "$ref": "#/definitions/if"
+ },
+ "changes": {
+ "$ref": "#/definitions/changes"
+ },
+ "exists": {
+ "$ref": "#/definitions/exists"
+ },
+ "variables": {
+ "$ref": "#/definitions/variables"
+ },
+ "when": {
+ "$ref": "#/definitions/when"
+ },
+ "start_in": {
+ "$ref": "#/definitions/start_in"
+ },
+ "allow_failure": {
+ "$ref": "#/definitions/allow_failure"
+ }
}
},
- {"type": "string", "minLength": 1},
- {"type": "array", "minLength": 1, "items": { "type": "string" }}
+ {
+ "type": "string",
+ "minLength": 1
+ },
+ {
+ "type": "array",
+ "minLength": 1,
+ "items": {
+ "type": "string"
+ }
+ }
]
}
},
@@ -591,7 +721,10 @@
".*": {
"oneOf": [
{
- "type": ["string", "number"]
+ "type": [
+ "string",
+ "number"
+ ]
},
{
"type": "object",
@@ -621,7 +754,9 @@
{
"type": "object",
"additionalProperties": false,
- "required": ["paths"],
+ "required": [
+ "paths"
+ ],
"properties": {
"paths": {
"type": "array",
@@ -656,7 +791,10 @@
"type": "object",
"patternProperties": {
".*": {
- "type": ["string", "number"]
+ "type": [
+ "string",
+ "number"
+ ]
},
"additionalProperties": false
}
@@ -683,7 +821,9 @@
"description": "Exit code that are not considered failure. The job fails for any other exit code.",
"type": "object",
"additionalProperties": false,
- "required": ["exit_codes"],
+ "required": [
+ "exit_codes"
+ ],
"properties": {
"exit_codes": {
"type": "integer"
@@ -694,7 +834,9 @@
"description": "You can list which exit codes are not considered failures. The job fails for any other exit code.",
"type": "object",
"additionalProperties": false,
- "required": ["exit_codes"],
+ "required": [
+ "exit_codes"
+ ],
"properties": {
"exit_codes": {
"type": "array",
@@ -713,27 +855,39 @@
"default": "on_success",
"oneOf": [
{
- "enum": ["on_success"],
+ "enum": [
+ "on_success"
+ ],
"description": "Execute job only when all jobs from prior stages succeed."
},
{
- "enum": ["on_failure"],
+ "enum": [
+ "on_failure"
+ ],
"description": "Execute job when at least one job from prior stages fails."
},
{
- "enum": ["always"],
+ "enum": [
+ "always"
+ ],
"description": "Execute job regardless of the status from prior stages."
},
{
- "enum": ["manual"],
+ "enum": [
+ "manual"
+ ],
"markdownDescription": "Execute the job manually from Gitlab UI or API. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#when)."
},
{
- "enum": ["delayed"],
+ "enum": [
+ "delayed"
+ ],
"markdownDescription": "Execute a job after the time limit in 'start_in' expires. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#when)."
},
{
- "enum": ["never"],
+ "enum": [
+ "never"
+ ],
"description": "Never execute the job."
}
]
@@ -745,15 +899,21 @@
"default": "on_success",
"oneOf": [
{
- "enum": ["on_success"],
+ "enum": [
+ "on_success"
+ ],
"description": "Save the cache only when the job succeeds."
},
{
- "enum": ["on_failure"],
+ "enum": [
+ "on_failure"
+ ],
"description": "Save the cache only when the job fails. "
},
{
- "enum": ["always"],
+ "enum": [
+ "always"
+ ],
"description": "Always save the cache. "
}
]
@@ -805,15 +965,21 @@
"default": "pull-push",
"oneOf": [
{
- "enum": ["pull"],
+ "enum": [
+ "pull"
+ ],
"description": "Pull will download cache but skip uploading after job completes."
},
{
- "enum": ["push"],
+ "enum": [
+ "push"
+ ],
"description": "Push will skip downloading cache and always recreate cache after job completes."
},
{
- "enum": ["pull-push"],
+ "enum": [
+ "pull-push"
+ ],
"description": "Pull-push will both download cache at job start and upload cache on job success."
}
]
@@ -828,39 +994,57 @@
{
"oneOf": [
{
- "enum": ["branches"],
+ "enum": [
+ "branches"
+ ],
"description": "When a branch is pushed."
},
{
- "enum": ["tags"],
+ "enum": [
+ "tags"
+ ],
"description": "When a tag is pushed."
},
{
- "enum": ["api"],
+ "enum": [
+ "api"
+ ],
"description": "When a pipeline has been triggered by a second pipelines API (not triggers API)."
},
{
- "enum": ["external"],
+ "enum": [
+ "external"
+ ],
"description": "When using CI services other than Gitlab"
},
{
- "enum": ["pipelines"],
+ "enum": [
+ "pipelines"
+ ],
"description": "For multi-project triggers, created using the API with 'CI_JOB_TOKEN'."
},
{
- "enum": ["pushes"],
+ "enum": [
+ "pushes"
+ ],
"description": "Pipeline is triggered by a `git push` by the user"
},
{
- "enum": ["schedules"],
+ "enum": [
+ "schedules"
+ ],
"description": "For scheduled pipelines."
},
{
- "enum": ["triggers"],
+ "enum": [
+ "triggers"
+ ],
"description": "For pipelines created using a trigger token."
},
{
- "enum": ["web"],
+ "enum": [
+ "web"
+ ],
"description": "For pipelines created using *Run pipeline* button in Gitlab UI (under your project's *Pipelines*)."
}
]
@@ -888,7 +1072,9 @@
"$ref": "#/definitions/filter_refs"
},
"kubernetes": {
- "enum": ["active"],
+ "enum": [
+ "active"
+ ],
"description": "Filter job based on if Kubernetes integration is active."
},
"variables": {
@@ -912,16 +1098,22 @@
"retry": {
"markdownDescription": "Retry a job if it fails. Can be a simple integer or object definition. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#retry).",
"oneOf": [
- { "$ref": "#/definitions/retry_max" },
+ {
+ "$ref": "#/definitions/retry_max"
+ },
{
"type": "object",
"additionalProperties": false,
"properties": {
- "max": { "$ref": "#/definitions/retry_max" },
+ "max": {
+ "$ref": "#/definitions/retry_max"
+ },
"when": {
"markdownDescription": "Either a single or array of error types to trigger job retry. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#retrywhen).",
"oneOf": [
- { "$ref": "#/definitions/retry_errors" },
+ {
+ "$ref": "#/definitions/retry_errors"
+ },
{
"type": "array",
"items": {
@@ -1004,21 +1196,39 @@
},
"job": {
"allOf": [
- { "$ref": "#/definitions/job_template" }
+ {
+ "$ref": "#/definitions/job_template"
+ }
]
},
"job_template": {
"type": "object",
"additionalProperties": false,
"properties": {
- "image": { "$ref": "#/definitions/image" },
- "services": { "$ref": "#/definitions/services" },
- "before_script": { "$ref": "#/definitions/before_script" },
- "after_script": { "$ref": "#/definitions/after_script" },
- "rules": { "$ref": "#/definitions/rules" },
- "variables": { "$ref": "#/definitions/variables" },
- "cache": { "$ref": "#/definitions/cache" },
- "secrets": { "$ref": "#/definitions/secrets" },
+ "image": {
+ "$ref": "#/definitions/image"
+ },
+ "services": {
+ "$ref": "#/definitions/services"
+ },
+ "before_script": {
+ "$ref": "#/definitions/before_script"
+ },
+ "after_script": {
+ "$ref": "#/definitions/after_script"
+ },
+ "rules": {
+ "$ref": "#/definitions/rules"
+ },
+ "variables": {
+ "$ref": "#/definitions/variables"
+ },
+ "cache": {
+ "$ref": "#/definitions/cache"
+ },
+ "secrets": {
+ "$ref": "#/definitions/secrets"
+ },
"script": {
"markdownDescription": "Shell scripts executed by the Runner. The only required property of jobs. Be careful with special characters (e.g. `:`, `{`, `}`, `&`) and use single or double quotes to avoid issues. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#script)",
"oneOf": [
@@ -1060,7 +1270,7 @@
}
}
]
- },
+ },
"only": {
"$ref": "#/definitions/filter",
"description": "Job will run *only* when these filtering options match."
@@ -1102,7 +1312,9 @@
"type": "boolean"
}
},
- "required": ["job"]
+ "required": [
+ "job"
+ ]
},
{
"type": "object",
@@ -1118,7 +1330,10 @@
"type": "boolean"
}
},
- "required": ["job", "pipeline"]
+ "required": [
+ "job",
+ "pipeline"
+ ]
},
{
"type": "object",
@@ -1137,7 +1352,11 @@
"type": "boolean"
}
},
- "required": ["job", "project", "ref"]
+ "required": [
+ "job",
+ "project",
+ "ref"
+ ]
}
]
}
@@ -1174,7 +1393,9 @@
"environment": {
"description": "Used to associate environment metadata with a deploy. Environment can have a name and URL attached to it, and will be displayed under /environments under the project.",
"oneOf": [
- { "type": "string" },
+ {
+ "type": "string"
+ },
{
"type": "object",
"additionalProperties": false,
@@ -1195,7 +1416,13 @@
"description": "The name of a job to execute when the environment is about to be stopped."
},
"action": {
- "enum": ["start", "prepare", "stop", "verify", "access"],
+ "enum": [
+ "start",
+ "prepare",
+ "stop",
+ "verify",
+ "access"
+ ],
"description": "Specifies what this job will do. 'start' (default) indicates the job will start the deployment. 'prepare'/'verify'/'access' indicates this will not affect the deployment. 'stop' indicates this will stop the deployment.",
"default": "start"
},
@@ -1226,7 +1453,9 @@
]
}
},
- "required": ["name"]
+ "required": [
+ "name"
+ ]
}
]
},
@@ -1306,15 +1535,23 @@
]
}
},
- "required": ["name", "url"]
+ "required": [
+ "name",
+ "url"
+ ]
},
"minItems": 1
}
},
- "required": ["links"]
+ "required": [
+ "links"
+ ]
}
},
- "required": ["tag_name", "description"]
+ "required": [
+ "tag_name",
+ "description"
+ ]
},
"coverage": {
"type": "string",
@@ -1345,14 +1582,20 @@
"type": "object",
"description": "Defines environment variables for specific job.",
"additionalProperties": {
- "type": ["string", "number", "array"]
+ "type": [
+ "string",
+ "number",
+ "array"
+ ]
}
},
"maxItems": 50
}
},
"additionalProperties": false,
- "required": ["matrix"]
+ "required": [
+ "matrix"
+ ]
}
]
},
@@ -1383,7 +1626,9 @@
"strategy": {
"description": "You can mirror the pipeline status from the triggered pipeline to the source bridge job by using strategy: depend",
"type": "string",
- "enum": ["depend"]
+ "enum": [
+ "depend"
+ ]
},
"forward": {
"description": "Specify what to forward to the downstream pipeline.",
@@ -1403,9 +1648,13 @@
}
}
},
- "required": ["project"],
+ "required": [
+ "project"
+ ],
"dependencies": {
- "branch": ["project"]
+ "branch": [
+ "project"
+ ]
}
},
{
@@ -1466,7 +1715,10 @@
"type": "string"
}
},
- "required": ["artifact", "job"]
+ "required": [
+ "artifact",
+ "job"
+ ]
},
{
"type": "object",
@@ -1489,7 +1741,10 @@
"pattern": "\\.ya?ml$"
}
},
- "required": ["project", "file"]
+ "required": [
+ "project",
+ "file"
+ ]
}
]
}
@@ -1499,7 +1754,9 @@
"strategy": {
"description": "You can mirror the pipeline status from the triggered pipeline to the source bridge job by using strategy: depend",
"type": "string",
- "enum": ["depend"]
+ "enum": [
+ "depend"
+ ]
},
"forward": {
"description": "Specify what to forward to the downstream pipeline.",
@@ -1560,7 +1817,9 @@
"variables": {
"markdownDescription": "Whether to inherit all globally-defined variables or not. Or subset of inherited variables. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#inheritvariables).",
"oneOf": [
- { "type": "boolean" },
+ {
+ "type": "boolean"
+ },
{
"type": "array",
"items": {
@@ -1576,15 +1835,24 @@
"oneOf": [
{
"properties": {
- "when": { "enum": ["delayed"] }
+ "when": {
+ "enum": [
+ "delayed"
+ ]
+ }
},
- "required": ["when", "start_in"]
+ "required": [
+ "when",
+ "start_in"
+ ]
},
{
"properties": {
"when": {
"not": {
- "enum": ["delayed"]
+ "enum": [
+ "delayed"
+ ]
}
}
}
@@ -1612,4 +1880,4 @@
}
}
}
-}
+} \ No newline at end of file
diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue
index 3c07dee025f..4b7d606b52c 100644
--- a/app/assets/javascripts/integrations/edit/components/integration_form.vue
+++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue
@@ -230,10 +230,8 @@ export default {
@change="setOverride"
/>
- <div v-if="!hasSections" class="row">
- <div class="col-lg-4"></div>
-
- <div class="col-lg-8">
+ <section v-if="!hasSections" class="gl-lg-display-flex gl-justify-content-end">
+ <div class="gl-flex-basis-two-thirds">
<!-- helpHtml is trusted input -->
<div v-if="helpHtml" v-safe-html:[$options.helpHtmlConfig]="helpHtml"></div>
@@ -249,7 +247,7 @@ export default {
:type="propsSource.type"
/>
</div>
- </div>
+ </section>
<template v-if="hasSections">
<div
@@ -258,8 +256,8 @@ export default {
:class="{ 'gl-border-b gl-pb-3 gl-mb-6': index !== customState.sections.length - 1 }"
data-testid="integration-section"
>
- <div class="row">
- <div class="col-lg-4">
+ <section class="gl-lg-display-flex">
+ <div class="gl-flex-basis-third">
<h4 class="gl-mt-0">
{{ section.title
}}<gl-badge
@@ -277,7 +275,7 @@ export default {
<p v-safe-html="section.description"></p>
</div>
- <div class="col-lg-8">
+ <div class="gl-flex-basis-two-thirds">
<component
:is="$options.integrationFormSectionComponents[section.type]"
:fields="fieldsForSection(section)"
@@ -286,14 +284,12 @@ export default {
@request-jira-issue-types="onRequestJiraIssueTypes"
/>
</div>
- </div>
+ </section>
</div>
</template>
- <div v-if="hasFieldsWithoutSection" class="row">
- <div class="col-lg-4"></div>
-
- <div class="col-lg-8">
+ <section v-if="hasFieldsWithoutSection" class="gl-lg-display-flex gl-justify-content-end">
+ <div class="gl-flex-basis-two-thirds">
<dynamic-field
v-for="field in fieldsWithoutSection"
:key="`${currentKey}-${field.name}`"
@@ -302,12 +298,12 @@ export default {
:data-qa-selector="`${field.name}_div`"
/>
</div>
- </div>
+ </section>
- <div v-if="isEditable" class="row">
- <div :class="hasSections ? 'col' : 'col-lg-8 offset-lg-4'">
+ <section v-if="isEditable" :class="!hasSections && 'gl-lg-display-flex gl-justify-content-end'">
+ <div :class="!hasSections && 'gl-flex-basis-two-thirds'">
<div
- class="footer-block row-content-block gl-display-flex gl-justify-content-space-between"
+ class="footer-block row-content-block gl-lg-display-flex gl-justify-content-space-between"
>
<div>
<template v-if="isInstanceOrGroupLevel">
@@ -369,6 +365,6 @@ export default {
</template>
</div>
</div>
- </div>
+ </section>
</gl-form>
</template>
diff --git a/app/assets/javascripts/notes/components/discussion_filter_note.vue b/app/assets/javascripts/notes/components/discussion_filter_note.vue
index 61af0b06535..39b3df899a5 100644
--- a/app/assets/javascripts/notes/components/discussion_filter_note.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter_note.vue
@@ -31,7 +31,7 @@ export default {
<div class="timeline-icon d-none d-lg-flex">
<gl-icon name="comment" />
</div>
- <div class="timeline-content">
+ <div class="timeline-content gl-pl-8">
<div data-testid="discussion-filter-timeline-content">
<gl-sprintf :message="$options.i18n.information">
<template #bold="{ content }">
diff --git a/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/constants.js b/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/constants.js
index b7768cfa5b9..df1188d365b 100644
--- a/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/constants.js
+++ b/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/constants.js
@@ -4,7 +4,7 @@ export const tdClass =
'table-col gl-display-flex d-md-table-cell gl-align-items-center gl-white-space-nowrap';
export const thClass = 'gl-hover-bg-blue-50';
export const bodyTrClass =
- 'gl-border-1 gl-border-t-solid gl-border-gray-100 gl-hover-cursor-pointer gl-hover-bg-blue-50 gl-hover-border-b-solid gl-hover-border-blue-200';
+ 'gl-border-1 gl-border-t-solid gl-border-gray-100 gl-hover-cursor-pointer gl-hover-bg-gray-50 gl-hover-border-b-solid';
export const defaultPageSize = 20;
diff --git a/app/assets/stylesheets/components/milestone_combobox.scss b/app/assets/stylesheets/components/milestone_combobox.scss
deleted file mode 100644
index 94d295c324b..00000000000
--- a/app/assets/stylesheets/components/milestone_combobox.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-.milestone-combobox {
- .dropdown-menu.show {
- overflow: hidden;
- }
-}
diff --git a/app/assets/stylesheets/components/release_block.scss b/app/assets/stylesheets/components/release_block.scss
deleted file mode 100644
index 7e82d0960d7..00000000000
--- a/app/assets/stylesheets/components/release_block.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.release-block {
- transition: background-color 1s linear;
-}
diff --git a/app/assets/stylesheets/components/dashboard_skeleton.scss b/app/assets/stylesheets/page_bundles/operations.scss
index 1dcaa47470b..497cb63033c 100644
--- a/app/assets/stylesheets/components/dashboard_skeleton.scss
+++ b/app/assets/stylesheets/page_bundles/operations.scss
@@ -1,3 +1,5 @@
+@import 'mixins_and_variables_and_functions';
+
.dashboard-cards {
margin-right: -$gl-padding-8;
margin-left: -$gl-padding-8;
@@ -8,7 +10,7 @@
&-header {
&-warning {
- background-color: $orange-100;
+ background-color: var(--orange-100, $orange-100);
}
}
@@ -16,16 +18,16 @@
min-height: 120px;
&-warning {
- background-color: $orange-50;
+ background-color: var(--orange-50, $orange-50);
}
&-failed {
- background-color: $red-50;
+ background-color: var(--red-50, $red-50);
}
}
&-icon {
- color: $gray-300;
+ color: var(--gray-300, $gray-300);
}
&-footer {
@@ -33,7 +35,7 @@
height: $gl-padding-32;
&-arrow {
- color: $gray-200;
+ color: var(--gray-200, $gray-200);
}
&-downstream {
@@ -41,7 +43,7 @@
}
&-extra {
- background-color: $gray-200;
+ background-color: var(--gray-200, $gray-200);
font-size: 10px;
line-height: $gl-line-height;
width: $gl-padding;
@@ -50,7 +52,7 @@
&-header {
&-failed {
- background-color: $red-100;
+ background-color: var(--red-100, $red-100);
}
}
@@ -66,10 +68,10 @@
background-repeat: no-repeat;
background-size: cover;
background-image: linear-gradient(to right,
- $gray-50 0%,
- $gray-10 20%,
- $gray-50 40%,
- $gray-50 100%);
+ var(--gray-50, $gray-50) 0%,
+ var(--gray-10, $gray-10) 20%,
+ var(--gray-50, $gray-50) 40%,
+ var(--gray-50, $gray-50) 100%);
border-radius: $gl-padding;
height: $gl-padding;
margin-top: -$gl-padding-8;
diff --git a/app/assets/stylesheets/components/release_block_milestone_info.scss b/app/assets/stylesheets/page_bundles/releases.scss
index b6a85ae965a..24ffbf9b90c 100644
--- a/app/assets/stylesheets/components/release_block_milestone_info.scss
+++ b/app/assets/stylesheets/page_bundles/releases.scss
@@ -1,3 +1,9 @@
+@import 'mixins_and_variables_and_functions';
+
+.release-block {
+ transition: background-color 1s linear;
+}
+
.release-block-milestone-info {
.milestone-progress-bar-container {
width: 300px;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index bd61e8df699..cfad34760ad 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -435,8 +435,8 @@ $system-note-svg-size: 1rem;
.discussion-filter-note {
.timeline-icon {
- width: $system-note-icon-size + 6;
- height: $system-note-icon-size + 6;
+ width: $system-note-icon-size;
+ height: $system-note-icon-size;
margin-top: -8px;
}
}
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index ecd6270ed47..9f9d0da6efd 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -19,7 +19,7 @@ class LabelsFinder < UnionFinder
items = with_title(items)
items = by_subscription(items)
items = by_search(items)
- sort(items)
+ sort(items.with_preloaded_container)
end
private
diff --git a/app/graphql/resolvers/base_issues_resolver.rb b/app/graphql/resolvers/base_issues_resolver.rb
index 479b1df6876..6357132705e 100644
--- a/app/graphql/resolvers/base_issues_resolver.rb
+++ b/app/graphql/resolvers/base_issues_resolver.rb
@@ -47,7 +47,6 @@ module Resolvers
def preloads
{
alert_management_alert: [:alert_management_alert],
- labels: [:labels],
assignees: [:assignees],
participants: Issue.participant_includes,
timelogs: [:timelogs],
diff --git a/app/graphql/resolvers/bulk_labels_resolver.rb b/app/graphql/resolvers/bulk_labels_resolver.rb
new file mode 100644
index 00000000000..a758ef70f93
--- /dev/null
+++ b/app/graphql/resolvers/bulk_labels_resolver.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class BulkLabelsResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ type Types::LabelType.connection_type, null: true
+
+ def resolve
+ authorize!(object)
+
+ BatchLoader::GraphQL.for(object.id).batch(cache: false) do |ids, loader, args|
+ labels = Label.for_targets(ids, object.class.name).group_by(&:target_id)
+
+ ids.each do |id|
+ loader.call(id, labels[id] || [])
+ end
+ end
+ end
+
+ private
+
+ def authorized_resource?(object)
+ Ability.allowed?(current_user, :read_label, object.issuing_parent)
+ end
+ end
+end
diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
index 697cc6f5b03..b6510e7c5fa 100644
--- a/app/graphql/resolvers/concerns/resolves_merge_requests.rb
+++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
@@ -42,7 +42,6 @@ module ResolvesMergeRequests
assignees: [:assignees],
reviewers: [:reviewers],
participants: MergeRequest.participant_includes,
- labels: [:labels],
author: [:author],
merged_at: [:metrics],
commit_count: [:metrics],
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index ca4eb044bab..76fac831199 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -43,8 +43,10 @@ module Types
field :updated_by, Types::UserType, null: true,
description: 'User that last updated the issue.'
- field :labels, Types::LabelType.connection_type, null: true,
- description: 'Labels of the issue.'
+ field :labels, Types::LabelType.connection_type,
+ null: true,
+ description: 'Labels of the issue.',
+ resolver: Resolvers::BulkLabelsResolver
field :milestone, Types::MilestoneType, null: true,
description: 'Milestone of the issue.'
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 3a39236bafa..8cc600fc68e 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -158,8 +158,11 @@ module Types
description: 'Human-readable time estimate of the merge request.'
field :human_total_time_spent, GraphQL::Types::String, null: true,
description: 'Human-readable total time reported as spent on the merge request.'
- field :labels, Types::LabelType.connection_type, null: true, complexity: 5,
- description: 'Labels of the merge request.'
+ field :labels, Types::LabelType.connection_type,
+ null: true, complexity: 5,
+ description: 'Labels of the merge request.',
+ resolver: Resolvers::BulkLabelsResolver
+
field :milestone, Types::MilestoneType, null: true,
description: 'Milestone of the merge request.'
field :participants, Types::MergeRequests::ParticipantType.connection_type, null: true, complexity: 15,
diff --git a/app/models/ci/secure_file.rb b/app/models/ci/secure_file.rb
index 0644c9f3235..3cc57e8f907 100644
--- a/app/models/ci/secure_file.rb
+++ b/app/models/ci/secure_file.rb
@@ -7,6 +7,7 @@ module Ci
FILE_SIZE_LIMIT = 5.megabytes.freeze
CHECKSUM_ALGORITHM = 'sha256'
+ PARSABLE_EXTENSIONS = ['cer'].freeze
self.limit_scope = :project
self.limit_name = 'project_ci_secure_files'
@@ -34,6 +35,37 @@ module Ci
CHECKSUM_ALGORITHM
end
+ def file_extension
+ File.extname(name).delete_prefix('.')
+ end
+
+ def metadata_parsable?
+ PARSABLE_EXTENSIONS.include?(file_extension)
+ end
+
+ def metadata_parser
+ return unless metadata_parsable?
+
+ case file_extension
+ when 'cer'
+ Gitlab::Ci::SecureFiles::Cer.new(file.read)
+ end
+ end
+
+ def update_metadata!
+ return unless metadata_parser
+
+ begin
+ parser = metadata_parser
+ self.metadata = parser.metadata
+ self.expires_at = parser.expires_at if parser.respond_to?(:expires_at)
+ save!
+ rescue StandardError => err
+ Gitlab::AppLogger.error("Secure File Parser Failure (#{id}): #{err.message} - #{parser.error}.")
+ nil
+ end
+ end
+
private
def assign_checksum
diff --git a/app/models/group_label.rb b/app/models/group_label.rb
index ff14529c6e6..0d2eb524929 100644
--- a/app/models/group_label.rb
+++ b/app/models/group_label.rb
@@ -2,6 +2,7 @@
class GroupLabel < Label
belongs_to :group
+ belongs_to :parent_container, foreign_key: :group_id, class_name: 'Group'
validates :group, presence: true
diff --git a/app/models/label.rb b/app/models/label.rb
index 6608a0573cb..35daca92089 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -42,6 +42,7 @@ class Label < ApplicationRecord
scope :order_name_asc, -> { reorder(title: :asc) }
scope :order_name_desc, -> { reorder(title: :desc) }
scope :subscribed_by, ->(user_id) { joins(:subscriptions).where(subscriptions: { user_id: user_id, subscribed: true }) }
+ scope :with_preloaded_container, -> { preload(parent_container: :route) }
scope :top_labels_by_target, -> (target_relation) {
label_id_column = arel_table[:id]
@@ -59,6 +60,14 @@ class Label < ApplicationRecord
.distinct
}
+ scope :for_targets, ->(target_ids, targets_type) do
+ joins(:label_links)
+ .where(label_links: { target_id: target_ids })
+ .where(label_links: { target_type: targets_type })
+ .select("labels.*, target_id")
+ .with_preloaded_container
+ end
+
def self.prioritized(project)
joins(:priorities)
.where(label_priorities: { project_id: project })
diff --git a/app/models/preloaders/labels_preloader.rb b/app/models/preloaders/labels_preloader.rb
index 722d588d8bc..b6e73c1cd02 100644
--- a/app/models/preloaders/labels_preloader.rb
+++ b/app/models/preloaders/labels_preloader.rb
@@ -21,8 +21,10 @@ module Preloaders
def preload_all
preloader = ActiveRecord::Associations::Preloader.new
+ preloader.preload(labels, parent_container: :route)
preloader.preload(labels.select { |l| l.is_a? ProjectLabel }, { project: [:project_feature, namespace: :route] })
preloader.preload(labels.select { |l| l.is_a? GroupLabel }, { group: :route })
+
labels.each do |label|
label.lazy_subscription(user)
label.lazy_subscription(user, project) if project.present?
diff --git a/app/models/project_label.rb b/app/models/project_label.rb
index d0b16cc98b4..dc647901b46 100644
--- a/app/models/project_label.rb
+++ b/app/models/project_label.rb
@@ -4,6 +4,7 @@ class ProjectLabel < Label
MAX_NUMBER_OF_PRIORITIES = 1
belongs_to :project
+ belongs_to :parent_container, foreign_key: :project_id, class_name: 'Project'
validates :project, presence: true
diff --git a/app/models/user.rb b/app/models/user.rb
index d64a52ff7b9..be3bec0d142 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -935,6 +935,7 @@ class User < ApplicationRecord
# that the password is the user's password
def valid_password?(password)
return false unless password_allowed?(password)
+ return false if password_automatically_set?
return super if Feature.enabled?(:pbkdf2_password_encryption)
Devise::Encryptor.compare(self.class, encrypted_password, password)
diff --git a/app/policies/group_label_policy.rb b/app/policies/group_label_policy.rb
index 9f3acd44b23..4a848e44fec 100644
--- a/app/policies/group_label_policy.rb
+++ b/app/policies/group_label_policy.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
class GroupLabelPolicy < BasePolicy
- delegate { @subject.group }
+ delegate { @subject.parent_container }
end
diff --git a/app/policies/project_label_policy.rb b/app/policies/project_label_policy.rb
index 5ce896ecaf2..6656d5990a5 100644
--- a/app/policies/project_label_policy.rb
+++ b/app/policies/project_label_policy.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
class ProjectLabelPolicy < BasePolicy
- delegate { @subject.project }
+ delegate { @subject.parent_container }
end
diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb
index e3b110f8f26..2786a2e357e 100644
--- a/app/services/labels/promote_service.rb
+++ b/app/services/labels/promote_service.rb
@@ -9,7 +9,7 @@ module Labels
return unless project.group &&
label.is_a?(ProjectLabel)
- Label.transaction do
+ ProjectLabel.transaction do
# use the existing group label if it exists
group_label = find_or_create_group_label(label)
@@ -50,7 +50,7 @@ module Labels
.new(current_user, title: group_label.title, group_id: project.group.id)
.execute(skip_authorization: true)
.where.not(id: group_label)
- .select(:id) # Can't use pluck() to avoid object-creation because of the batching
+ .select(:id, :project_id, :group_id, :type) # Can't use pluck() to avoid object-creation because of the batching
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 6d31a29f5a7..6b4f9dbe509 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -26,6 +26,7 @@ module MergeRequests
@merge_request = merge_request
@options = options
+ jid = merge_jid
validate!
@@ -37,7 +38,7 @@ module MergeRequests
end
end
- log_info("Merge process finished on JID #{merge_jid} with state #{state}")
+ log_info("Merge process finished on JID #{jid} with state #{state}")
rescue MergeError => e
handle_merge_error(log_message: e.message, save_message_on_model: true)
ensure
@@ -159,17 +160,32 @@ module MergeRequests
end
def handle_merge_error(log_message:, save_message_on_model: false)
- Gitlab::AppLogger.error("MergeService ERROR: #{merge_request_info} - #{log_message}")
+ log_error("MergeService ERROR: #{merge_request_info} - #{log_message}")
@merge_request.update(merge_error: log_message) if save_message_on_model
end
def log_info(message)
+ payload = log_payload("#{merge_request_info} - #{message}")
+ logger.info(**payload)
+ end
+
+ def log_error(message)
+ payload = log_payload(message)
+ logger.error(**payload)
+ end
+
+ def logger
@logger ||= Gitlab::AppLogger
- @logger.info("#{merge_request_info} - #{message}")
+ end
+
+ def log_payload(message)
+ Gitlab::ApplicationContext.current
+ .merge(merge_request_info: merge_request_info,
+ message: message)
end
def merge_request_info
- merge_request.to_reference(full: true)
+ @merge_request_info ||= merge_request.to_reference(full: true)
end
def source_matches?
diff --git a/app/services/pages_domains/create_acme_order_service.rb b/app/services/pages_domains/create_acme_order_service.rb
index e289a78091b..c600f497fa5 100644
--- a/app/services/pages_domains/create_acme_order_service.rb
+++ b/app/services/pages_domains/create_acme_order_service.rb
@@ -2,9 +2,6 @@
module PagesDomains
class CreateAcmeOrderService
- # elliptic curve algorithm to generate the private key
- ECDSA_CURVE = "prime256v1"
-
attr_reader :pages_domain
def initialize(pages_domain)
@@ -17,12 +14,7 @@ module PagesDomains
challenge = order.new_challenge
- private_key = if Feature.enabled?(:pages_lets_encrypt_ecdsa, pages_domain.project)
- OpenSSL::PKey::EC.generate(ECDSA_CURVE)
- else
- OpenSSL::PKey::RSA.new(4096)
- end
-
+ private_key = OpenSSL::PKey::RSA.new(4096)
saved_order = pages_domain.acme_orders.create!(
url: order.url,
expires_at: order.expires,
diff --git a/app/views/projects/releases/index.html.haml b/app/views/projects/releases/index.html.haml
index 9ddf2201fad..975abaefc6c 100644
--- a/app/views/projects/releases/index.html.haml
+++ b/app/views/projects/releases/index.html.haml
@@ -1,4 +1,5 @@
- page_title _('Releases')
+- add_page_specific_style 'page_bundles/releases'
- if use_startup_query_for_index_page?
- add_page_startup_graphql_call('releases/all_releases', index_page_startup_query_variables)
diff --git a/app/views/projects/releases/show.html.haml b/app/views/projects/releases/show.html.haml
index 91ee9ad70a3..66b187c8c72 100644
--- a/app/views/projects/releases/show.html.haml
+++ b/app/views/projects/releases/show.html.haml
@@ -1,5 +1,6 @@
- add_to_breadcrumbs _("Releases"), project_releases_path(@project)
- page_title @release.name
- page_description @release.description_html
+- add_page_specific_style 'page_bundles/releases'
#js-show-release-page{ data: data_for_show_page }
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 26f9f45e4bd..e02285e7f34 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -2181,6 +2181,15 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: ci_parse_secure_file_metadata
+ :worker_name: Ci::ParseSecureFileMetadataWorker
+ :feature_category: :mobile_signing_deployment
+ :has_external_dependencies: false
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: ci_runners_process_runner_version_update
:worker_name: Ci::Runners::ProcessRunnerVersionUpdateWorker
:feature_category: :runner_fleet
diff --git a/app/workers/ci/parse_secure_file_metadata_worker.rb b/app/workers/ci/parse_secure_file_metadata_worker.rb
new file mode 100644
index 00000000000..0d2495d3155
--- /dev/null
+++ b/app/workers/ci/parse_secure_file_metadata_worker.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Ci
+ class ParseSecureFileMetadataWorker
+ include ::ApplicationWorker
+
+ feature_category :mobile_signing_deployment
+ urgency :low
+ idempotent!
+
+ def perform(secure_file_id)
+ ::Ci::SecureFile.find_by_id(secure_file_id).try(&:update_metadata!)
+ end
+ end
+end
diff --git a/config/application.rb b/config/application.rb
index 0ed3e6fbd2d..d45e0bab54d 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -294,6 +294,7 @@ module Gitlab
config.assets.precompile << "page_bundles/milestone.css"
config.assets.precompile << "page_bundles/new_namespace.css"
config.assets.precompile << "page_bundles/oncall_schedules.css"
+ config.assets.precompile << "page_bundles/operations.css"
config.assets.precompile << "page_bundles/escalation_policies.css"
config.assets.precompile << "page_bundles/pipeline.css"
config.assets.precompile << "page_bundles/pipeline_schedules.css"
@@ -306,6 +307,7 @@ module Gitlab
config.assets.precompile << "page_bundles/project.css"
config.assets.precompile << "page_bundles/projects_edit.css"
config.assets.precompile << "page_bundles/prometheus.css"
+ config.assets.precompile << "page_bundles/releases.css"
config.assets.precompile << "page_bundles/reports.css"
config.assets.precompile << "page_bundles/roadmap.css"
config.assets.precompile << "page_bundles/requirements.css"
diff --git a/config/feature_flags/development/pages_lets_encrypt_ecdsa.yml b/config/feature_flags/development/approval_rules_eligible_filter.yml
index 866c2438e9f..e8d925d08a7 100644
--- a/config/feature_flags/development/pages_lets_encrypt_ecdsa.yml
+++ b/config/feature_flags/development/approval_rules_eligible_filter.yml
@@ -1,8 +1,8 @@
---
-name: pages_lets_encrypt_ecdsa
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88125
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/363026
-milestone: '15.1'
+name: approval_rules_eligible_filter
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/100192
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/376331
+milestone: '15.5'
type: development
-group: group::editor
+group: group::source code
default_enabled: false
diff --git a/config/feature_flags/development/secure_files_metadata_parsers.yml b/config/feature_flags/development/secure_files_metadata_parsers.yml
new file mode 100644
index 00000000000..2d6eed27f4b
--- /dev/null
+++ b/config/feature_flags/development/secure_files_metadata_parsers.yml
@@ -0,0 +1,8 @@
+---
+name: secure_files_metadata_parsers
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/99046
+rollout_issue_url:
+milestone: '15.5'
+type: development
+group: group::incubation
+default_enabled: false
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 0e8f5d4a7d6..aff05cb62b9 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -93,6 +93,8 @@
- 1
- - ci_job_artifacts_expire_project_build_artifacts
- 1
+- - ci_parse_secure_file_metadata
+ - 1
- - ci_runners_process_runner_version_update
- 1
- - ci_upstream_projects_subscriptions_cleanup
diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md
index 1a13343d3e1..0b1d355aba2 100644
--- a/doc/development/documentation/styleguide/word_list.md
+++ b/doc/development/documentation/styleguide/word_list.md
@@ -218,7 +218,7 @@ Instead of:
## cannot, can not
-Use **cannot** instead of **can not**. You can also use **can't**.
+Use **cannot** instead of **can not**.
See also [contractions](index.md#contractions).
diff --git a/doc/development/gitlab_flavored_markdown/specification_guide/index.md b/doc/development/gitlab_flavored_markdown/specification_guide/index.md
index 4a6a1d8f64f..b1ab39b0321 100644
--- a/doc/development/gitlab_flavored_markdown/specification_guide/index.md
+++ b/doc/development/gitlab_flavored_markdown/specification_guide/index.md
@@ -86,7 +86,7 @@ Some places in the code refer to both the GitLab and GitHub specifications
simultaneous in the same areas of logic. In these situations,
_GitHub_ Flavored Markdown may be referred to with variable or constant names like
`ghfm_` to avoid confusion. For example, we use the `ghfm` acronym for the
-[`ghfm_spec_v_0.29.txt` GitHub Flavored Markdown specification file](#github-flavored-markdown-specification)
+[`ghfm_spec_v_0.29.md` GitHub Flavored Markdown specification file](#github-flavored-markdown-specification),
which is committed to the `gitlab` repository and used as input to the
[`update_specification.rb` script](#update-specificationrb-script).
@@ -618,8 +618,8 @@ subgraph script:
A --> B{Backend Markdown API}
end
subgraph input:<br/>input specification files
- C[ghfm_spec_v_0.29.txt] --> A
- D[glfm_intro.txt] --> A
+ C[ghfm_spec_v_0.29.md] --> A
+ D[glfm_intro.md] --> A
E[glfm_official_specification_examples.md] --> A
F[glfm_internal_extension_examples.md] --> A
end
@@ -749,7 +749,7 @@ subcategories based on their usage and purpose:
These are the original input to drive all other automated GLFM
specification scripts, processes, or tests.
- `github_flavored_markdown`: Contains only the downloaded and committed
- [`ghfm_spec_v_0.29.txt`](#github-flavored-markdown-specification) specification.
+ [`ghfm_spec_v_0.29.md`](#github-flavored-markdown-specification) specification.
- `gitlab_flavored_markdown`: Contains all `glfm_*` files.
- `*.md` [input specification files](#input-specification-files),
which represent the GLFM specification itself.
@@ -769,19 +769,20 @@ See the main [specification files](#specification-files) section for more contex
##### GitHub Flavored Markdown specification
-[`glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.txt`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.txt)
-is the official latest [GFM `spec.txt`](https://github.com/github/cmark-gfm/blob/master/test/spec.txt).
+[`glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.md`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.md)
+is a copy of the official latest [GFM `spec.txt`](https://github.com/github/cmark-gfm/blob/master/test/spec.txt).
-- It is automatically downloaded and updated by `update-specification.rb` script.
+- It is automatically downloaded and updated by the `update-specification.rb` script.
- When it is downloaded, the version number is added to the filename.
+- The extension is changed from `*.txt` to `*.md` so that it can be handled better by Markdown editors.
NOTE:
For extra clarity, this file uses the `ghfm` acronym in its name instead of `gfm`, as
explained in the [Acronyms section](#acronyms-glfm-ghfm-gfm-commonmark).
-##### `glfm_intro.txt`
+##### `glfm_intro.md`
-[`glfm_specification/input/gitlab_flavored_markdown/glfm_intro.txt`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/input/gitlab_flavored_markdown/glfm_intro.txt)
+[`glfm_specification/input/gitlab_flavored_markdown/glfm_intro.md`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/input/gitlab_flavored_markdown/glfm_intro.md)
is the GitLab-specific version of the prose in the introduction section of the GLFM specification.
- It is manually updated.
diff --git a/glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.txt b/glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.md
index 582131d700a..1702761563e 100644
--- a/glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.txt
+++ b/glfm_specification/input/github_flavored_markdown/ghfm_spec_v_0.29.md
@@ -2077,7 +2077,7 @@ followed by one of the strings (case-insensitive) `address`,
`h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `head`, `header`, `hr`,
`html`, `iframe`, `legend`, `li`, `link`, `main`, `menu`, `menuitem`,
`nav`, `noframes`, `ol`, `optgroup`, `option`, `p`, `param`,
-`section`, `source`, `summary`, `table`, `tbody`, `td`,
+`section`, `summary`, `table`, `tbody`, `td`,
`tfoot`, `th`, `thead`, `title`, `tr`, `track`, `ul`, followed
by [whitespace], the end of the line, the string `>`, or
the string `/>`.\
@@ -10224,4 +10224,3 @@ closers:
After we're done, we remove all delimiters above `stack_bottom` from the
delimiter stack.
-
diff --git a/glfm_specification/input/gitlab_flavored_markdown/glfm_intro.txt b/glfm_specification/input/gitlab_flavored_markdown/glfm_intro.md
index b5351bf37de..b5351bf37de 100644
--- a/glfm_specification/input/gitlab_flavored_markdown/glfm_intro.txt
+++ b/glfm_specification/input/gitlab_flavored_markdown/glfm_intro.md
diff --git a/glfm_specification/output/spec.txt b/glfm_specification/output/spec.txt
index af4eba06758..9fa6c4c291e 100644
--- a/glfm_specification/output/spec.txt
+++ b/glfm_specification/output/spec.txt
@@ -1791,7 +1791,7 @@ followed by one of the strings (case-insensitive) `address`,
`h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `head`, `header`, `hr`,
`html`, `iframe`, `legend`, `li`, `link`, `main`, `menu`, `menuitem`,
`nav`, `noframes`, `ol`, `optgroup`, `option`, `p`, `param`,
-`section`, `source`, `summary`, `table`, `tbody`, `td`,
+`section`, `summary`, `table`, `tbody`, `td`,
`tfoot`, `th`, `thead`, `title`, `tr`, `track`, `ul`, followed
by [whitespace], the end of the line, the string `>`, or
the string `/>`.\
@@ -10328,4 +10328,3 @@ closers:
After we're done, we remove all delimiters above `stack_bottom` from the
delimiter stack.
-
diff --git a/lib/api/ci/secure_files.rb b/lib/api/ci/secure_files.rb
index c1f47dd67ce..68431df203b 100644
--- a/lib/api/ci/secure_files.rb
+++ b/lib/api/ci/secure_files.rb
@@ -74,6 +74,10 @@ module API
file_too_large! unless secure_file.file.size < ::Ci::SecureFile::FILE_SIZE_LIMIT.to_i
if secure_file.save
+ if Feature.enabled?(:secure_files_metadata_parsers, user_project)
+ ::Ci::ParseSecureFileMetadataWorker.perform_async(secure_file.id) # rubocop:disable CodeReuse/Worker
+ end
+
present secure_file, with: Entities::Ci::SecureFile
else
render_validation_error!(secure_file)
diff --git a/lib/gitlab/ci/secure_files/cer.rb b/lib/gitlab/ci/secure_files/cer.rb
new file mode 100644
index 00000000000..7a55d9ca095
--- /dev/null
+++ b/lib/gitlab/ci/secure_files/cer.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module SecureFiles
+ class Cer
+ attr_reader :error
+
+ def initialize(filedata)
+ @filedata = filedata
+ @error = nil
+ end
+
+ def certificate_data
+ @certificate_data ||= begin
+ OpenSSL::X509::Certificate.new(@filedata)
+ rescue StandardError => err
+ @error = err.to_s
+ nil
+ end
+ end
+
+ def metadata
+ return {} unless certificate_data
+
+ {
+ issuer: issuer,
+ subject: subject,
+ id: id,
+ expires_at: expires_at
+ }
+ end
+
+ def expires_at
+ return unless certificate_data
+
+ certificate_data.not_before
+ end
+
+ private
+
+ def id
+ certificate_data.serial.to_s
+ end
+
+ def issuer
+ X509Name.parse(certificate_data.issuer)
+ end
+
+ def subject
+ X509Name.parse(certificate_data.subject)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/secure_files/x509_name.rb b/lib/gitlab/ci/secure_files/x509_name.rb
new file mode 100644
index 00000000000..659959b8ae5
--- /dev/null
+++ b/lib/gitlab/ci/secure_files/x509_name.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module SecureFiles
+ class X509Name
+ def self.parse(x509_name)
+ x509_name.to_utf8.split(',').to_h { |a| a.split('=') }
+ rescue StandardError
+ {}
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/templates/PHP.gitlab-ci.yml b/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
index 12640d28d29..0604438e0aa 100644
--- a/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
@@ -42,3 +42,8 @@ variables:
test:
script:
- vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never
+
+deploy:
+ stage: deploy
+ script: echo "Define your deployment script!"
+ environment: production
diff --git a/lib/gitlab/ci/templates/Python.gitlab-ci.yml b/lib/gitlab/ci/templates/Python.gitlab-ci.yml
index 191d5b6b11c..febbb36d834 100644
--- a/lib/gitlab/ci/templates/Python.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Python.gitlab-ci.yml
@@ -55,3 +55,8 @@ pages:
- public
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+
+deploy:
+ stage: deploy
+ script: echo "Define your deployment script!"
+ environment: production
diff --git a/lib/gitlab/ci/templates/Rust.gitlab-ci.yml b/lib/gitlab/ci/templates/Rust.gitlab-ci.yml
index a83f84da818..b9823444db2 100644
--- a/lib/gitlab/ci/templates/Rust.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Rust.gitlab-ci.yml
@@ -40,3 +40,8 @@ test:cargo:
# when: always
# reports:
# junit: $CI_PROJECT_DIR/tests/*.xml
+
+deploy:
+ stage: deploy
+ script: echo "Define your deployment script!"
+ environment: production
diff --git a/lib/gitlab/ci/templates/Scala.gitlab-ci.yml b/lib/gitlab/ci/templates/Scala.gitlab-ci.yml
index 26efe7a8908..ce5d5937896 100644
--- a/lib/gitlab/ci/templates/Scala.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Scala.gitlab-ci.yml
@@ -30,3 +30,8 @@ test:
script:
# Execute your project's tests
- sbt clean test
+
+deploy:
+ stage: deploy
+ script: echo "Define your deployment script!"
+ environment: production
diff --git a/lib/gitlab/ci/templates/Swift.gitlab-ci.yml b/lib/gitlab/ci/templates/Swift.gitlab-ci.yml
index 3c4533d603e..2a5ac539a42 100644
--- a/lib/gitlab/ci/templates/Swift.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Swift.gitlab-ci.yml
@@ -38,3 +38,8 @@ archive_project:
- ios_11-3
- xcode_9-3
- macos_10-13
+
+deploy:
+ stage: deploy
+ script: echo "Define your deployment script!"
+ environment: production
diff --git a/lib/gitlab/jira_import/handle_labels_service.rb b/lib/gitlab/jira_import/handle_labels_service.rb
index 1b00515cced..60d7f9e93d9 100644
--- a/lib/gitlab/jira_import/handle_labels_service.rb
+++ b/lib/gitlab/jira_import/handle_labels_service.rb
@@ -12,7 +12,7 @@ module Gitlab
return if jira_labels.blank?
existing_labels = LabelsFinder.new(nil, project: project, title: jira_labels)
- .execute(skip_authorization: true).select(:id, :name)
+ .execute(skip_authorization: true).select(:id, :project_id, :group_id, :type, :name)
new_labels = create_missing_labels(existing_labels)
label_ids = existing_labels.map(&:id)
diff --git a/scripts/lib/glfm/constants.rb b/scripts/lib/glfm/constants.rb
index e5790bbdd88..27b959cd135 100644
--- a/scripts/lib/glfm/constants.rb
+++ b/scripts/lib/glfm/constants.rb
@@ -10,12 +10,12 @@ module Glfm
# GitHub Flavored Markdown specification file
GHFM_SPEC_TXT_URI = 'https://raw.githubusercontent.com/github/cmark-gfm/master/test/spec.txt'
GHFM_SPEC_VERSION = '0.29'
- GHFM_SPEC_TXT_FILENAME = "ghfm_spec_v_#{GHFM_SPEC_VERSION}.txt"
- GHFM_SPEC_TXT_PATH = specification_path.join('input/github_flavored_markdown', GHFM_SPEC_TXT_FILENAME)
+ GHFM_SPEC_MD_FILENAME = "ghfm_spec_v_#{GHFM_SPEC_VERSION}.md"
+ GHFM_SPEC_MD_PATH = specification_path.join('input/github_flavored_markdown', GHFM_SPEC_MD_FILENAME)
# GitLab Flavored Markdown specification files
specification_input_glfm_path = specification_path.join('input/gitlab_flavored_markdown')
- GLFM_INTRO_TXT_PATH = specification_input_glfm_path.join('glfm_intro.txt')
+ GLFM_INTRO_MD_PATH = specification_input_glfm_path.join('glfm_intro.md')
GLFM_EXAMPLES_TXT_PATH = specification_input_glfm_path.join('glfm_canonical_examples.txt')
GLFM_EXAMPLE_STATUS_YML_PATH = specification_input_glfm_path.join('glfm_example_status.yml')
GLFM_EXAMPLE_METADATA_YML_PATH =
diff --git a/scripts/lib/glfm/update_specification.rb b/scripts/lib/glfm/update_specification.rb
index 73c23d40de5..7a89797123b 100644
--- a/scripts/lib/glfm/update_specification.rb
+++ b/scripts/lib/glfm/update_specification.rb
@@ -12,16 +12,16 @@ module Glfm
def process
output('Updating specification...')
- ghfm_spec_txt_lines = load_ghfm_spec_txt
- glfm_spec_txt_string = build_glfm_spec_txt(ghfm_spec_txt_lines)
+ ghfm_spec_lines = load_ghfm_spec
+ glfm_spec_txt_string = build_glfm_spec_txt(ghfm_spec_lines)
write_glfm_spec_txt(glfm_spec_txt_string)
end
private
- def load_ghfm_spec_txt
+ def load_ghfm_spec
# We only re-download the GitHub Flavored Markdown specification if the
- # UPDATE_GHFM_SPEC_TXT environment variable is set to true, which should only
+ # UPDATE_GHFM_SPEC_MD environment variable is set to true, which should only
# ever be done manually and locally, never in CI. This provides some security
# protection against a possible injection attack vector, if the GitHub-hosted
# version of the spec is ever temporarily compromised with an injection attack.
@@ -29,40 +29,40 @@ module Glfm
# This also avoids doing external network access to download the file
# in CI jobs, which can avoid potentially flaky builds if the GitHub-hosted
# version of the file is temporarily unavailable.
- if ENV['UPDATE_GHFM_SPEC_TXT'] == 'true'
- download_and_write_ghfm_spec_txt
+ if ENV['UPDATE_GHFM_SPEC_MD'] == 'true'
+ update_ghfm_spec_md
else
- read_existing_ghfm_spec_txt
+ read_existing_ghfm_spec_md
end
end
- def read_existing_ghfm_spec_txt
- output("Reading existing #{GHFM_SPEC_TXT_PATH}...")
- File.open(GHFM_SPEC_TXT_PATH).readlines
+ def read_existing_ghfm_spec_md
+ output("Reading existing #{GHFM_SPEC_MD_PATH}...")
+ File.open(GHFM_SPEC_MD_PATH).readlines
end
- def download_and_write_ghfm_spec_txt
+ def update_ghfm_spec_md
output("Downloading #{GHFM_SPEC_TXT_URI}...")
ghfm_spec_txt_uri_io = URI.open(GHFM_SPEC_TXT_URI)
# Read IO stream into an array of lines for easy processing later
- ghfm_spec_txt_lines = ghfm_spec_txt_uri_io.readlines
- raise "Unable to read lines from #{GHFM_SPEC_TXT_URI}" if ghfm_spec_txt_lines.empty?
+ ghfm_spec_lines = ghfm_spec_txt_uri_io.readlines
+ raise "Unable to read lines from #{GHFM_SPEC_TXT_URI}" if ghfm_spec_lines.empty?
# Make sure the GHFM spec version has not changed
- validate_expected_spec_version!(ghfm_spec_txt_lines[2])
+ validate_expected_spec_version!(ghfm_spec_lines[2])
# Reset IO stream and re-read into a single string for easy writing
# noinspection RubyNilAnalysis
ghfm_spec_txt_uri_io.seek(0)
- ghfm_spec_txt_string = ghfm_spec_txt_uri_io.read
- raise "Unable to read string from #{GHFM_SPEC_TXT_URI}" unless ghfm_spec_txt_string
+ ghfm_spec_string = ghfm_spec_txt_uri_io.read
+ raise "Unable to read string from #{GHFM_SPEC_TXT_URI}" unless ghfm_spec_string
- output("Writing #{GHFM_SPEC_TXT_PATH}...")
- GHFM_SPEC_TXT_PATH.dirname.mkpath
- write_file(GHFM_SPEC_TXT_PATH, ghfm_spec_txt_string)
+ output("Writing #{GHFM_SPEC_MD_PATH}...")
+ GHFM_SPEC_MD_PATH.dirname.mkpath
+ write_file(GHFM_SPEC_MD_PATH, ghfm_spec_string)
- ghfm_spec_txt_lines
+ ghfm_spec_lines
end
def validate_expected_spec_version!(version_line)
@@ -85,13 +85,13 @@ module Glfm
end
def replace_intro_section(spec_txt_lines)
- glfm_intro_txt_lines = File.open(GLFM_INTRO_TXT_PATH).readlines
- raise "Unable to read lines from #{GLFM_INTRO_TXT_PATH}" if glfm_intro_txt_lines.empty?
+ glfm_intro_md_lines = File.open(GLFM_INTRO_MD_PATH).readlines
+ raise "Unable to read lines from #{GLFM_INTRO_MD_PATH}" if glfm_intro_md_lines.empty?
ghfm_intro_header_begin_index = spec_txt_lines.index do |line|
line =~ INTRODUCTION_HEADER_LINE_TEXT
end
- raise "Unable to locate introduction header line in #{GHFM_SPEC_TXT_PATH}" if ghfm_intro_header_begin_index.nil?
+ raise "Unable to locate introduction header line in #{GHFM_SPEC_MD_PATH}" if ghfm_intro_header_begin_index.nil?
# Find the index of the next header after the introduction header, starting from the index
# of the introduction header this is the length of the intro section
@@ -100,7 +100,7 @@ module Glfm
end
# Replace the intro section with the GitLab flavored Markdown intro section
- spec_txt_lines[ghfm_intro_header_begin_index, ghfm_intro_section_length] = glfm_intro_txt_lines
+ spec_txt_lines[ghfm_intro_header_begin_index, ghfm_intro_section_length] = glfm_intro_md_lines
end
def insert_examples_txt(spec_txt_lines)
@@ -110,7 +110,7 @@ module Glfm
ghfm_end_tests_comment_index = spec_txt_lines.index do |line|
line =~ END_TESTS_COMMENT_LINE_TEXT
end
- raise "Unable to locate 'END TESTS' comment line in #{GHFM_SPEC_TXT_PATH}" if ghfm_end_tests_comment_index.nil?
+ raise "Unable to locate 'END TESTS' comment line in #{GHFM_SPEC_MD_PATH}" if ghfm_end_tests_comment_index.nil?
# Insert the GLFM examples before the 'END TESTS' comment line
spec_txt_lines[ghfm_end_tests_comment_index - 1] = ["\n", glfm_examples_txt_lines, "\n"].flatten
diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb
index 1d0db488751..6c860740354 100644
--- a/spec/features/profiles/password_spec.rb
+++ b/spec/features/profiles/password_spec.rb
@@ -51,11 +51,11 @@ RSpec.describe 'Profile > Password' do
end
context 'Password authentication unavailable' do
- before do
- gitlab_sign_in(user)
- end
-
context 'Regular user' do
+ before do
+ gitlab_sign_in(user)
+ end
+
let(:user) { create(:user) }
it 'renders 404 when password authentication is disabled for the web interface and Git' do
@@ -69,7 +69,22 @@ RSpec.describe 'Profile > Password' do
end
context 'LDAP user' do
+ include LdapHelpers
+
+ let(:ldap_settings) { { enabled: true } }
let(:user) { create(:omniauth_user, provider: 'ldapmain') }
+ let(:provider) { 'ldapmain' }
+ let(:provider_label) { 'Main LDAP' }
+
+ before do
+ stub_ldap_setting(ldap_settings)
+ stub_ldap_access(user, provider, provider_label)
+ sign_in_using_ldap!(user, provider_label, provider)
+ end
+
+ after(:all) do
+ Rails.application.reload_routes!
+ end
it 'renders 404' do
visit edit_profile_password_path
diff --git a/spec/fixtures/ci_secure_files/sample.cer b/spec/fixtures/ci_secure_files/sample.cer
new file mode 100644
index 00000000000..9cca06d53c9
--- /dev/null
+++ b/spec/fixtures/ci_secure_files/sample.cer
Binary files differ
diff --git a/spec/frontend/alert_management/components/alert_management_table_spec.js b/spec/frontend/alert_management/components/alert_management_table_spec.js
index 3e1438c37d6..7fb4f2d2463 100644
--- a/spec/frontend/alert_management/components/alert_management_table_spec.js
+++ b/spec/frontend/alert_management/components/alert_management_table_spec.js
@@ -1,4 +1,4 @@
-import { GlTable, GlAlert, GlLoadingIcon, GlDropdown, GlIcon, GlAvatar } from '@gitlab/ui';
+import { GlTable, GlAlert, GlLoadingIcon, GlDropdown, GlIcon, GlAvatar, GlLink } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
@@ -31,6 +31,7 @@ describe('AlertManagementTable', () => {
const findSearch = () => wrapper.findComponent(FilteredSearchBar);
const findSeverityColumnHeader = () => wrapper.findByTestId('alert-management-severity-sort');
const findFirstIDField = () => wrapper.findAllByTestId('idField').at(0);
+ const findFirstIDLink = () => wrapper.findAllByTestId('idField').at(0).findComponent(GlLink);
const findAssignees = () => wrapper.findAllByTestId('assigneesField');
const findSeverityFields = () => wrapper.findAllByTestId('severityField');
const findIssueFields = () => wrapper.findAllByTestId('issueField');
@@ -135,10 +136,11 @@ describe('AlertManagementTable', () => {
expect(findLoader().exists()).toBe(false);
expect(findAlertsTable().exists()).toBe(true);
expect(findAlerts()).toHaveLength(mockAlerts.length);
- expect(findAlerts().at(0).classes()).toContain('gl-hover-bg-blue-50');
+ expect(findAlerts().at(0).classes()).toContain('gl-hover-bg-gray-50');
+ expect(findAlerts().at(0).classes()).not.toContain('gl-hover-border-blue-200');
});
- it('displays the alert ID and title formatted correctly', () => {
+ it('displays the alert ID and title as a link', () => {
mountComponent({
data: { alerts: { list: mockAlerts }, alertsCount, errored: false },
loading: false,
@@ -146,6 +148,8 @@ describe('AlertManagementTable', () => {
expect(findFirstIDField().exists()).toBe(true);
expect(findFirstIDField().text()).toBe(`#${mockAlerts[0].iid} ${mockAlerts[0].title}`);
+ expect(findFirstIDLink().text()).toBe(`#${mockAlerts[0].iid} ${mockAlerts[0].title}`);
+ expect(findFirstIDLink().attributes('href')).toBe('/1527542/details');
});
it('displays status dropdown', () => {
@@ -266,7 +270,8 @@ describe('AlertManagementTable', () => {
alerts: {
list: [
{
- iid: 1,
+ iid: '1',
+ title: 'SyntaxError: Invalid or unexpected token',
status: 'acknowledged',
startedAt: '2020-03-17T23:18:14.996Z',
severity: 'high',
diff --git a/spec/lib/gitlab/ci/secure_files/cer_spec.rb b/spec/lib/gitlab/ci/secure_files/cer_spec.rb
new file mode 100644
index 00000000000..50c8494524a
--- /dev/null
+++ b/spec/lib/gitlab/ci/secure_files/cer_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::SecureFiles::Cer do
+ context 'when the supplied certificate cannot be parsed' do
+ let(:invalid_certificate) { described_class.new('xyzabc') }
+ let(:subject) { described_class.new('xyzabc') }
+
+ describe '#certificate_data' do
+ it 'assigns the error message and returns nil' do
+ expect(invalid_certificate.certificate_data).to be nil
+ expect(invalid_certificate.error).to eq('not enough data')
+ end
+ end
+
+ describe '#metadata' do
+ it 'returns an empty hash' do
+ expect(invalid_certificate.metadata).to eq({})
+ end
+ end
+
+ describe '#expires_at' do
+ it 'returns nil' do
+ expect(invalid_certificate.expires_at).to be_nil
+ end
+ end
+ end
+
+ context 'when the supplied certificate can be parsed' do
+ let(:sample_file) { fixture_file('ci_secure_files/sample.cer') }
+ let(:subject) { described_class.new(sample_file) }
+
+ describe '#certificate_data' do
+ it 'returns an OpenSSL::X509::Certificate object' do
+ expect(subject.certificate_data.class).to be(OpenSSL::X509::Certificate)
+ end
+ end
+
+ describe '#metadata' do
+ it 'returns a hash with the expected keys' do
+ expect(subject.metadata.keys).to match_array([:issuer, :subject, :id, :expires_at])
+ end
+ end
+
+ describe '#id' do
+ it 'returns the certificate serial number' do
+ expect(subject.metadata[:id]).to eq('33669367788748363528491290218354043267')
+ end
+ end
+
+ describe '#expires_at' do
+ it 'returns the certificate expiration timestamp' do
+ expect(subject.expires_at).to eq('2022-04-26 19:20:40 UTC')
+ end
+ end
+
+ describe '#issuer' do
+ it 'calls parse on X509Name' do
+ expect(subject.metadata[:issuer]["O"]).to eq('Apple Inc.')
+ end
+ end
+
+ describe '#subject' do
+ it 'calls parse on X509Name' do
+ expect(subject.metadata[:subject]["OU"]).to eq('N7SYAN8PX8')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/secure_files/x509_name_spec.rb b/spec/lib/gitlab/ci/secure_files/x509_name_spec.rb
new file mode 100644
index 00000000000..3a523924c5b
--- /dev/null
+++ b/spec/lib/gitlab/ci/secure_files/x509_name_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::SecureFiles::X509Name do
+ describe '.parse' do
+ it 'parses an X509Name object into a hash format' do
+ sample = OpenSSL::X509::Name.new([
+ ['C', 'Test Country'],
+ ['O', 'Test Org Name'],
+ ['OU', 'Test Org Unit'],
+ ['CN', 'Test Common Name'],
+ ['UID', 'Test UID']
+ ])
+
+ parsed_sample = described_class.parse(sample)
+
+ expect(parsed_sample["C"]).to eq('Test Country')
+ expect(parsed_sample["O"]).to eq('Test Org Name')
+ expect(parsed_sample["OU"]).to eq('Test Org Unit')
+ expect(parsed_sample["CN"]).to eq('Test Common Name')
+ expect(parsed_sample["UID"]).to eq('Test UID')
+ end
+
+ it 'returns an empty hash when an error occurs' do
+ parsed_sample = described_class.parse('unexpectedinput')
+ expect(parsed_sample).to eq({})
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 34c062d3096..b3d5a0d57c1 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -95,6 +95,7 @@ label_links:
label:
- subscriptions
- project
+- parent_container
- lists
- label_links
- issues
diff --git a/spec/models/ci/secure_file_spec.rb b/spec/models/ci/secure_file_spec.rb
index e47efff5dfd..153912da4e8 100644
--- a/spec/models/ci/secure_file_spec.rb
+++ b/spec/models/ci/secure_file_spec.rb
@@ -81,4 +81,60 @@ RSpec.describe Ci::SecureFile do
expect(Base64.encode64(subject.file.read)).to eq(Base64.encode64(sample_file))
end
end
+
+ describe '#file_extension' do
+ it 'returns the extension for the file name' do
+ file = build(:ci_secure_file, name: 'file1.cer')
+ expect(file.file_extension).to eq('cer')
+ end
+
+ it 'returns only the last part of the extension for the file name' do
+ file = build(:ci_secure_file, name: 'file1.tar.gz')
+ expect(file.file_extension).to eq('gz')
+ end
+ end
+
+ describe '#metadata_parsable?' do
+ it 'returns true when the file extension has a supported parser' do
+ file = build(:ci_secure_file, name: 'file1.cer')
+ expect(file.metadata_parsable?).to be true
+ end
+
+ it 'returns false when the file extension does not have a supported parser' do
+ file = build(:ci_secure_file, name: 'file1.foo')
+ expect(file.metadata_parsable?).to be false
+ end
+ end
+
+ describe '#metadata_parser' do
+ it 'returns an instance of Gitlab::Ci::SecureFiles::Cer when a .cer file is supplied' do
+ file = build(:ci_secure_file, name: 'file1.cer')
+ expect(file.metadata_parser).to be_an_instance_of(Gitlab::Ci::SecureFiles::Cer)
+ end
+
+ it 'returns nil when the file type is not supported by any parsers' do
+ file = build(:ci_secure_file, name: 'file1.foo')
+ expect(file.metadata_parser).to be nil
+ end
+ end
+
+ describe '#update_metadata!' do
+ it 'assigns the expected metadata when a parsable file is supplied' do
+ file = create(:ci_secure_file, name: 'file1.cer',
+ file: CarrierWaveStringFile.new(fixture_file('ci_secure_files/sample.cer') ))
+ file.update_metadata!
+
+ expect(file.expires_at).to eq(DateTime.parse('2022-04-26 19:20:40'))
+ expect(file.metadata['id']).to eq('33669367788748363528491290218354043267')
+ expect(file.metadata['issuer']['CN']).to eq('Apple Worldwide Developer Relations Certification Authority')
+ expect(file.metadata['subject']['OU']).to eq('N7SYAN8PX8')
+ end
+
+ it 'logs an error when something goes wrong with the file parsing' do
+ corrupt_file = create(:ci_secure_file, name: 'file1.cer', file: CarrierWaveStringFile.new('11111111'))
+ message = 'Validation failed: Metadata must be a valid json schema - not enough data.'
+ expect(Gitlab::AppLogger).to receive(:error).with("Secure File Parser Failure (#{corrupt_file.id}): #{message}")
+ corrupt_file.update_metadata!
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 3b949bcfe65..d789533f76b 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -6235,6 +6235,13 @@ RSpec.describe User do
it { is_expected.to be_falsey }
end
end
+
+ context 'user with autogenerated_password' do
+ let(:user) { build_stubbed(:user, password_automatically_set: true) }
+ let(:password) { user.password }
+
+ it { is_expected.to be_falsey }
+ end
end
# These entire test section can be removed once the :pbkdf2_password_encryption feature flag is removed.
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index 163f9d02a3b..3b8beb4f798 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -708,7 +708,21 @@ RSpec.describe 'getting an issue list for a project' do
end
# Executes 3 extra queries to fetch participant_attrs
- include_examples 'N+1 query check', 3
+ include_examples 'N+1 query check', threshold: 3
+ end
+
+ context 'when requesting labels' do
+ let(:requested_fields) { ['labels { nodes { id } }'] }
+
+ before do
+ project_labels = create_list(:label, 2, project: project)
+ group_labels = create_list(:group_label, 2, group: group)
+
+ issue_a.update!(labels: [project_labels.first, group_labels.first].flatten)
+ issue_b.update!(labels: [project_labels, group_labels].flatten)
+ end
+
+ include_examples 'N+1 query check', skip_cached: false
end
end
diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb
index ef83f9be8ac..2895737ae6f 100644
--- a/spec/requests/api/graphql/project/merge_requests_spec.rb
+++ b/spec/requests/api/graphql/project/merge_requests_spec.rb
@@ -5,12 +5,14 @@ require 'spec_helper'
RSpec.describe 'getting merge request listings nested in a project' do
include GraphqlHelpers
- let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, :repository, :public, group: group) }
let_it_be(:current_user) { create(:user) }
let_it_be(:label) { create(:label, project: project) }
+ let_it_be(:group_label) { create(:group_label, group: group) }
let_it_be_with_reload(:merge_request_a) do
- create(:labeled_merge_request, :unique_branches, source_project: project, labels: [label])
+ create(:labeled_merge_request, :unique_branches, source_project: project, labels: [label, group_label])
end
let_it_be(:merge_request_b) do
@@ -18,7 +20,7 @@ RSpec.describe 'getting merge request listings nested in a project' do
end
let_it_be(:merge_request_c) do
- create(:labeled_merge_request, :closed, :unique_branches, source_project: project, labels: [label])
+ create(:labeled_merge_request, :closed, :unique_branches, source_project: project, labels: [label, group_label])
end
let_it_be(:merge_request_d) do
@@ -357,7 +359,20 @@ RSpec.describe 'getting merge request listings nested in a project' do
end
# Executes 3 extra queries to fetch participant_attrs
- include_examples 'N+1 query check', 3
+ include_examples 'N+1 query check', threshold: 3
+ end
+
+ context 'when requesting labels' do
+ let(:requested_fields) { ['labels { nodes { id } }'] }
+
+ before do
+ project_labels = create_list(:label, 2, project: project)
+ group_labels = create_list(:group_label, 2, group: group)
+
+ merge_request_c.update!(labels: [project_labels, group_labels].flatten)
+ end
+
+ include_examples 'N+1 query check', skip_cached: false
end
end
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 5a774619e75..20d298edfe5 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -1759,8 +1759,7 @@ RSpec.describe 'Git HTTP requests' do
end
describe "User with LDAP identity" do
- let(:user) { create(:omniauth_user, extern_uid: dn) }
- let(:dn) { 'uid=john,ou=people,dc=example,dc=com' }
+ let(:user) { create(:omniauth_user, :ldap) }
let(:path) { 'doesnt/exist.git' }
before do
diff --git a/spec/scripts/lib/glfm/update_specification_spec.rb b/spec/scripts/lib/glfm/update_specification_spec.rb
index 9fb671e0016..1cdc90876de 100644
--- a/spec/scripts/lib/glfm/update_specification_spec.rb
+++ b/spec/scripts/lib/glfm/update_specification_spec.rb
@@ -7,11 +7,11 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do
let(:ghfm_spec_txt_uri) { described_class::GHFM_SPEC_TXT_URI }
let(:ghfm_spec_txt_uri_io) { StringIO.new(ghfm_spec_txt_contents) }
- let(:ghfm_spec_txt_path) { described_class::GHFM_SPEC_TXT_PATH }
+ let(:ghfm_spec_md_path) { described_class::GHFM_SPEC_MD_PATH }
let(:ghfm_spec_txt_local_io) { StringIO.new(ghfm_spec_txt_contents) }
- let(:glfm_intro_txt_path) { described_class::GLFM_INTRO_TXT_PATH }
- let(:glfm_intro_txt_io) { StringIO.new(glfm_intro_txt_contents) }
+ let(:glfm_intro_md_path) { described_class::GLFM_INTRO_MD_PATH }
+ let(:glfm_intro_md_io) { StringIO.new(glfm_intro_md_contents) }
let(:glfm_examples_txt_path) { described_class::GLFM_EXAMPLES_TXT_PATH }
let(:glfm_examples_txt_io) { StringIO.new(glfm_examples_txt_contents) }
let(:glfm_spec_txt_path) { described_class::GLFM_SPEC_TXT_PATH }
@@ -52,7 +52,7 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do
MARKDOWN
end
- let(:glfm_intro_txt_contents) do
+ let(:glfm_intro_md_contents) do
# language=Markdown
<<~MARKDOWN
# Introduction
@@ -73,15 +73,15 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do
before do
# Mock default ENV var values
- allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_TXT').and_return(nil)
+ allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_MD').and_return(nil)
allow(ENV).to receive(:[]).and_call_original
# We mock out the URI and local file IO objects with real StringIO, instead of just mock
# objects. This gives better and more realistic coverage, while still avoiding
# actual network and filesystem I/O during the spec run.
allow(URI).to receive(:open).with(ghfm_spec_txt_uri) { ghfm_spec_txt_uri_io }
- allow(File).to receive(:open).with(ghfm_spec_txt_path) { ghfm_spec_txt_local_io }
- allow(File).to receive(:open).with(glfm_intro_txt_path) { glfm_intro_txt_io }
+ allow(File).to receive(:open).with(ghfm_spec_md_path) { ghfm_spec_txt_local_io }
+ allow(File).to receive(:open).with(glfm_intro_md_path) { glfm_intro_md_io }
allow(File).to receive(:open).with(glfm_examples_txt_path) { glfm_examples_txt_io }
allow(File).to receive(:open).with(glfm_spec_txt_path, 'w') { glfm_spec_txt_io }
@@ -90,7 +90,7 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do
end
describe 'retrieving latest GHFM spec.txt' do
- context 'when UPDATE_GHFM_SPEC_TXT is not true (default)' do
+ context 'when UPDATE_GHFM_SPEC_MD is not true (default)' do
it 'does not download' do
expect(URI).not_to receive(:open).with(ghfm_spec_txt_uri)
@@ -100,12 +100,12 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do
end
end
- context 'when UPDATE_GHFM_SPEC_TXT is true' do
+ context 'when UPDATE_GHFM_SPEC_MD is true' do
let(:ghfm_spec_txt_local_io) { StringIO.new }
before do
- allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_TXT').and_return('true')
- allow(File).to receive(:open).with(ghfm_spec_txt_path, 'w') { ghfm_spec_txt_local_io }
+ allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_MD').and_return('true')
+ allow(File).to receive(:open).with(ghfm_spec_md_path, 'w') { ghfm_spec_txt_local_io }
end
context 'with success' do
@@ -170,7 +170,7 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do
it 'replaces the intro section with the GitLab version' do
expect(glfm_contents).not_to match(/What is GitHub Flavored Markdown/m)
- expect(glfm_contents).to match(/#{Regexp.escape(glfm_intro_txt_contents)}/m)
+ expect(glfm_contents).to match(/#{Regexp.escape(glfm_intro_md_contents)}/m)
end
it 'inserts the GitLab examples sections before the appendix section' do
diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb
index aa5d6dcd1fb..5027acbba0a 100644
--- a/spec/services/merge_requests/ff_merge_service_spec.rb
+++ b/spec/services/merge_requests/ff_merge_service_spec.rb
@@ -108,7 +108,8 @@ RSpec.describe MergeRequests::FfMergeService do
service.execute(merge_request)
- expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
+ expect(Gitlab::AppLogger).to have_received(:error)
+ .with(hash_including(message: a_string_matching(error_message)))
end
it 'logs and saves error if there is an PreReceiveError exception' do
@@ -122,7 +123,8 @@ RSpec.describe MergeRequests::FfMergeService do
service.execute(merge_request)
expect(merge_request.merge_error).to include(error_message)
- expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
+ expect(Gitlab::AppLogger).to have_received(:error)
+ .with(hash_including(message: a_string_matching(error_message)))
end
it 'does not update squash_commit_sha if squash merge is not successful' do
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index a2d73d8c9b1..d3bf203d6bb 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -95,6 +95,42 @@ RSpec.describe MergeRequests::MergeService do
end
end
+ context 'running the service once' do
+ let(:ref) { merge_request.to_reference(full: true) }
+ let(:jid) { SecureRandom.hex }
+
+ let(:messages) do
+ [
+ /#{ref} - Git merge started on JID #{jid}/,
+ /#{ref} - Git merge finished on JID #{jid}/,
+ /#{ref} - Post merge started on JID #{jid}/,
+ /#{ref} - Post merge finished on JID #{jid}/,
+ /#{ref} - Merge process finished on JID #{jid}/
+ ]
+ end
+
+ before do
+ merge_request.update!(merge_jid: jid)
+ ::Gitlab::ApplicationContext.push(caller_id: 'MergeWorker')
+ end
+
+ it 'logs status messages' do
+ allow(Gitlab::AppLogger).to receive(:info).and_call_original
+
+ messages.each do |message|
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ hash_including(
+ 'meta.caller_id' => 'MergeWorker',
+ message: message,
+ merge_request_info: ref
+ )
+ ).and_call_original
+ end
+
+ service.execute(merge_request)
+ end
+ end
+
context 'running the service multiple time' do
it 'is idempotent' do
2.times { service.execute(merge_request) }
@@ -315,7 +351,9 @@ RSpec.describe MergeRequests::MergeService do
service.execute(merge_request)
expect(merge_request.merge_error).to eq(error_message)
- expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
+ expect(Gitlab::AppLogger).to have_received(:error)
+ .with(hash_including(merge_request_info: merge_request.to_reference(full: true),
+ message: a_string_matching(error_message)))
end
end
@@ -328,7 +366,9 @@ RSpec.describe MergeRequests::MergeService do
service.execute(merge_request)
expect(merge_request.merge_error).to eq(described_class::GENERIC_ERROR_MESSAGE)
- expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
+ expect(Gitlab::AppLogger).to have_received(:error)
+ .with(hash_including(merge_request_info: merge_request.to_reference(full: true),
+ message: a_string_matching(error_message)))
end
it 'logs and saves error if user is not authorized' do
@@ -354,7 +394,9 @@ RSpec.describe MergeRequests::MergeService do
service.execute(merge_request)
expect(merge_request.merge_error).to include('Something went wrong during merge pre-receive hook')
- expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
+ expect(Gitlab::AppLogger).to have_received(:error)
+ .with(hash_including(merge_request_info: merge_request.to_reference(full: true),
+ message: a_string_matching(error_message)))
end
it 'logs and saves error if commit is not created' do
@@ -366,7 +408,9 @@ RSpec.describe MergeRequests::MergeService do
expect(merge_request).to be_open
expect(merge_request.merge_commit_sha).to be_nil
expect(merge_request.merge_error).to include(described_class::GENERIC_ERROR_MESSAGE)
- expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(described_class::GENERIC_ERROR_MESSAGE))
+ expect(Gitlab::AppLogger).to have_received(:error)
+ .with(hash_including(merge_request_info: merge_request.to_reference(full: true),
+ message: a_string_matching(described_class::GENERIC_ERROR_MESSAGE)))
end
context 'when squashing is required' do
@@ -385,7 +429,9 @@ RSpec.describe MergeRequests::MergeService do
expect(merge_request.merge_commit_sha).to be_nil
expect(merge_request.squash_commit_sha).to be_nil
expect(merge_request.merge_error).to include(error_message)
- expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
+ expect(Gitlab::AppLogger).to have_received(:error)
+ .with(hash_including(merge_request_info: merge_request.to_reference(full: true),
+ message: a_string_matching(error_message)))
end
end
@@ -406,7 +452,9 @@ RSpec.describe MergeRequests::MergeService do
expect(merge_request.merge_commit_sha).to be_nil
expect(merge_request.squash_commit_sha).to be_nil
expect(merge_request.merge_error).to include(error_message)
- expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
+ expect(Gitlab::AppLogger).to have_received(:error)
+ .with(hash_including(merge_request_info: merge_request.to_reference(full: true),
+ message: a_string_matching(error_message)))
end
it 'logs and saves error if there is an PreReceiveError exception' do
@@ -422,7 +470,9 @@ RSpec.describe MergeRequests::MergeService do
expect(merge_request.merge_commit_sha).to be_nil
expect(merge_request.squash_commit_sha).to be_nil
expect(merge_request.merge_error).to include('Something went wrong during merge pre-receive hook')
- expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
+ expect(Gitlab::AppLogger).to have_received(:error)
+ .with(hash_including(merge_request_info: merge_request.to_reference(full: true),
+ message: a_string_matching(error_message)))
end
context 'when fast-forward merge is not allowed' do
@@ -444,7 +494,9 @@ RSpec.describe MergeRequests::MergeService do
expect(merge_request.merge_commit_sha).to be_nil
expect(merge_request.squash_commit_sha).to be_nil
expect(merge_request.merge_error).to include(error_message)
- expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
+ expect(Gitlab::AppLogger).to have_received(:error)
+ .with(hash_including(merge_request_info: merge_request.to_reference(full: true),
+ message: a_string_matching(error_message)))
end
end
end
@@ -461,7 +513,9 @@ RSpec.describe MergeRequests::MergeService do
it 'logs and saves error' do
service.execute(merge_request)
- expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
+ expect(Gitlab::AppLogger).to have_received(:error)
+ .with(hash_including(merge_request_info: merge_request.to_reference(full: true),
+ message: a_string_matching(error_message)))
end
end
@@ -473,7 +527,9 @@ RSpec.describe MergeRequests::MergeService do
it 'logs and saves error' do
service.execute(merge_request)
- expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
+ expect(Gitlab::AppLogger).to have_received(:error)
+ .with(hash_including(merge_request_info: merge_request.to_reference(full: true),
+ message: a_string_matching(error_message)))
end
context 'when passing `skip_discussions_check: true` as `options` parameter' do
diff --git a/spec/services/pages_domains/create_acme_order_service_spec.rb b/spec/services/pages_domains/create_acme_order_service_spec.rb
index b882c253613..35b2cc56973 100644
--- a/spec/services/pages_domains/create_acme_order_service_spec.rb
+++ b/spec/services/pages_domains/create_acme_order_service_spec.rb
@@ -38,21 +38,13 @@ RSpec.describe PagesDomains::CreateAcmeOrderService do
expect(challenge).to have_received(:request_validation).ordered
end
- it 'generates and saves private key: rsa' do
- stub_feature_flags(pages_lets_encrypt_ecdsa: false)
+ it 'generates and saves private key' do
service.execute
saved_order = PagesDomainAcmeOrder.last
expect { OpenSSL::PKey::RSA.new(saved_order.private_key) }.not_to raise_error
end
- it 'generates and saves private key: ec' do
- service.execute
-
- saved_order = PagesDomainAcmeOrder.last
- expect { OpenSSL::PKey::EC.new(saved_order.private_key) }.not_to raise_error
- end
-
it 'properly saves order attributes' do
service.execute
diff --git a/spec/support/helpers/ldap_helpers.rb b/spec/support/helpers/ldap_helpers.rb
index 2f5f8be518c..48b593fb3d1 100644
--- a/spec/support/helpers/ldap_helpers.rb
+++ b/spec/support/helpers/ldap_helpers.rb
@@ -69,6 +69,32 @@ module LdapHelpers
allow_any_instance_of(Gitlab::Auth::Ldap::Adapter)
.to receive(:ldap_search).and_raise(Gitlab::Auth::Ldap::LdapConnectionError)
end
+
+ def stub_ldap_access(user, provider, provider_label)
+ ldap_server_config =
+ {
+ 'label' => provider_label,
+ 'provider_name' => provider,
+ 'attributes' => {},
+ 'encryption' => 'plain',
+ 'uid' => 'uid',
+ 'base' => 'dc=example,dc=com'
+ }
+ uid = 'my-uid'
+ allow(::Gitlab::Auth::Ldap::Config).to receive_messages(enabled: true, servers: [ldap_server_config])
+ allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [provider.to_sym])
+
+ Ldap::OmniauthCallbacksController.define_providers!
+ Rails.application.reload_routes!
+
+ mock_auth_hash(provider, uid, user.email)
+ allow(Gitlab::Auth::Ldap::Access).to receive(:allowed?).with(user).and_return(true)
+
+ allow_next_instance_of(ActionDispatch::Routing::RoutesProxy) do |instance|
+ allow(instance).to receive(:"user_#{provider}_omniauth_callback_path")
+ .and_return("/users/auth/#{provider}/callback")
+ end
+ end
end
LdapHelpers.include_mod_with('LdapHelpers')
diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb
index 87a1f5459ec..44237b821c3 100644
--- a/spec/support/helpers/login_helpers.rb
+++ b/spec/support/helpers/login_helpers.rb
@@ -119,6 +119,16 @@ module LoginHelpers
click_button "oauth-login-#{provider}"
end
+ def sign_in_using_ldap!(user, ldap_tab, ldap_name)
+ visit new_user_session_path
+ click_link ldap_tab
+ fill_in 'username', with: user.username
+ fill_in 'password', with: user.password
+ within("##{ldap_name}") do
+ click_button 'Sign in'
+ end
+ end
+
def register_via(provider, uid, email, additional_info: {})
mock_auth_hash(provider, uid, email, additional_info: additional_info)
visit new_user_registration_path
diff --git a/spec/support/shared_examples/graphql/n_plus_one_query_examples.rb b/spec/support/shared_examples/graphql/n_plus_one_query_examples.rb
index ea14c465872..b4afde311ba 100644
--- a/spec/support/shared_examples/graphql/n_plus_one_query_examples.rb
+++ b/spec/support/shared_examples/graphql/n_plus_one_query_examples.rb
@@ -1,14 +1,23 @@
# frozen_string_literal: true
-RSpec.shared_examples 'N+1 query check' do |threshold = 0|
+
+RSpec.shared_examples 'N+1 query check' do |threshold: 0, skip_cached: true|
it 'prevents N+1 queries' do
execute_query # "warm up" to prevent undeterministic counts
expect(graphql_errors).to be_blank # Sanity check - ex falso quodlibet!
- control = ActiveRecord::QueryRecorder.new { execute_query }
+ control = ActiveRecord::QueryRecorder.new(skip_cached: skip_cached) { execute_query }
expect(control.count).to be > 0
search_params[:iids] << extra_iid_for_second_query
- expect { execute_query }.not_to exceed_query_limit(control).with_threshold(threshold)
+ expect { execute_query }.not_to exceed_query_count_limit(control, skip_cached: skip_cached, threshold: threshold)
+ end
+
+ def exceed_query_count_limit(control, skip_cached: true, threshold: 0)
+ if skip_cached
+ exceed_query_limit(control).with_threshold(threshold)
+ else
+ exceed_all_query_limit(control).with_threshold(threshold)
+ end
end
end
diff --git a/spec/workers/ci/parse_secure_file_metadata_worker_spec.rb b/spec/workers/ci/parse_secure_file_metadata_worker_spec.rb
new file mode 100644
index 00000000000..57bbd8a6ff0
--- /dev/null
+++ b/spec/workers/ci/parse_secure_file_metadata_worker_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::ParseSecureFileMetadataWorker do
+ describe '#perform' do
+ include_examples 'an idempotent worker' do
+ let(:secure_file) { create(:ci_secure_file) }
+ subject { described_class.new.perform(secure_file&.id) }
+
+ context 'when the file is found' do
+ it 'calls update_metadata!' do
+ allow(::Ci::SecureFile).to receive(:find_by_id).and_return(secure_file)
+ expect(secure_file).to receive(:update_metadata!)
+
+ subject
+ end
+ end
+ end
+
+ context 'when file is not found' do
+ let(:secure_file) { nil }
+
+ it 'does not call update_metadata!' do
+ expect(secure_file).not_to receive(:update_metadata!)
+
+ subject
+ end
+ end
+ end
+end