summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.prettierignore4
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_mermaid.js2
-rw-r--r--app/assets/javascripts/jobs/components/job_container_item.vue66
-rw-r--r--app/assets/javascripts/jobs/components/jobs_container.vue53
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss11
-rw-r--r--app/assets/stylesheets/framework/panels.scss5
-rw-r--r--app/assets/stylesheets/framework/terms.scss15
-rw-r--r--app/models/ci/pipeline.rb10
-rw-r--r--app/models/clusters/cluster.rb3
-rw-r--r--app/models/clusters/kubernetes_namespace.rb49
-rw-r--r--app/models/clusters/platforms/kubernetes.rb16
-rw-r--r--app/models/clusters/project.rb3
-rw-r--r--app/models/project.rb81
-rw-r--r--app/services/projects/after_rename_service.rb135
-rw-r--r--app/services/projects/update_service.rb2
-rw-r--r--app/views/admin/groups/show.html.haml7
-rw-r--r--app/views/layouts/terms.html.haml29
-rw-r--r--app/views/projects/branches/_panel.html.haml3
-rw-r--r--app/views/projects/pipelines/_info.html.haml6
-rw-r--r--app/views/projects/protected_branches/shared/_branches_list.html.haml3
-rw-r--r--app/views/projects/protected_branches/shared/_create_protected_branch.html.haml3
-rw-r--r--app/views/projects/protected_tags/shared/_create_protected_tag.html.haml3
-rw-r--r--app/views/projects/protected_tags/shared/_tags_list.html.haml3
-rw-r--r--app/views/projects/registry/repositories/index.html.haml3
-rw-r--r--app/views/projects/services/prometheus/_metrics.html.haml12
-rw-r--r--app/views/projects/tags/new.html.haml3
-rw-r--r--app/views/projects/triggers/_index.html.haml3
-rw-r--r--app/views/shared/runners/show.html.haml2
-rw-r--r--changelogs/unreleased/42611-removed-branch-link.yml5
-rw-r--r--changelogs/unreleased/46884-remove-card-title.yml5
-rw-r--r--changelogs/unreleased/51716-add-kubernetes-namespace-model.yml5
-rw-r--r--changelogs/unreleased/52840-fix-runners-details-page.yml5
-rw-r--r--changelogs/unreleased/add-new-kubernetes-spec-helpers.yml5
-rw-r--r--changelogs/unreleased/blackst0ne-bump-mermaid.yml5
-rw-r--r--changelogs/unreleased/change-branch-font-type-in-tag-creation.yml5
-rw-r--r--changelogs/unreleased/feature-gb-improve-include-config-errors-reporting.yml5
-rw-r--r--changelogs/unreleased/gt-remove-empty-spec-describe-blocks.yml2
-rw-r--r--changelogs/unreleased/sh-pages-eof-error.yml5
-rw-r--r--db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb24
-rw-r--r--db/post_migrate/20161221153951_rename_reserved_project_names.rb6
-rw-r--r--db/post_migrate/20170313133418_rename_more_reserved_project_names.rb6
-rw-r--r--db/schema.rb20
-rw-r--r--doc/development/contributing/index.md16
-rw-r--r--doc/development/contributing/issue_workflow.md20
-rw-r--r--doc/development/contributing/merge_request_workflow.md9
-rw-r--r--doc/integration/saml.md17
-rw-r--r--lib/gitlab/ci/build/artifacts/metadata.rb7
-rw-r--r--lib/gitlab/ci/config.rb8
-rw-r--r--lib/gitlab/ci/config/external/file/base.rb75
-rw-r--r--lib/gitlab/ci/config/external/file/local.rb42
-rw-r--r--lib/gitlab/ci/config/external/file/remote.rb49
-rw-r--r--lib/gitlab/ci/config/external/mapper.rb33
-rw-r--r--lib/gitlab/ci/config/external/processor.rb50
-rw-r--r--lib/gitlab/ci/external/file/base.rb29
-rw-r--r--lib/gitlab/ci/external/file/local.rb34
-rw-r--r--lib/gitlab/ci/external/file/remote.rb30
-rw-r--r--lib/gitlab/ci/external/mapper.rb32
-rw-r--r--lib/gitlab/ci/external/processor.rb52
-rw-r--r--lib/gitlab/namespace_sanitizer.rb9
-rw-r--r--package.json5
-rw-r--r--scripts/frontend/frontend_script_utils.js3
-rw-r--r--scripts/frontend/prettier.js206
-rw-r--r--spec/factories/ci/runners.rb10
-rw-r--r--spec/factories/clusters/kubernetes_namespaces.rb9
-rw-r--r--spec/factories/clusters/projects.rb8
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb18
-rw-r--r--spec/javascripts/jobs/components/job_container_item_spec.js73
-rw-r--r--spec/javascripts/jobs/mock_data.js4
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb30
-rw-r--r--spec/lib/gitlab/ci/config/external/file/base_spec.rb52
-rw-r--r--spec/lib/gitlab/ci/config/external/file/local_spec.rb (renamed from spec/lib/gitlab/ci/external/file/local_spec.rb)4
-rw-r--r--spec/lib/gitlab/ci/config/external/file/remote_spec.rb (renamed from spec/lib/gitlab/ci/external/file/remote_spec.rb)51
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb (renamed from spec/lib/gitlab/ci/external/mapper_spec.rb)8
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb (renamed from spec/lib/gitlab/ci/external/processor_spec.rb)26
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb8
-rw-r--r--spec/migrations/rename_more_reserved_project_names_spec.rb11
-rw-r--r--spec/migrations/rename_reserved_project_names_spec.rb11
-rw-r--r--spec/models/ci/pipeline_spec.rb35
-rw-r--r--spec/models/clusters/cluster_spec.rb4
-rw-r--r--spec/models/clusters/kubernetes_namespace_spec.rb84
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb75
-rw-r--r--spec/models/clusters/project_spec.rb2
-rw-r--r--spec/models/project_spec.rb167
-rw-r--r--spec/services/clusters/create_service_spec.rb103
-rw-r--r--spec/services/projects/after_rename_service_spec.rb198
-rw-r--r--spec/support/helpers/ci_artifact_metadata_generator.rb48
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb23
-rw-r--r--spec/support/services/clusters/create_service_shared.rb59
-rw-r--r--spec/views/shared/runners/show.html.haml_spec.rb155
-rw-r--r--yarn.lock158
90 files changed, 1934 insertions, 864 deletions
diff --git a/.prettierignore b/.prettierignore
index b674ccd50cf..dc9e572ab54 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -3,3 +3,7 @@
/public/
/vendor/
/tmp/
+
+# ignore stylesheets for now as this clashes with our linter
+*.css
+*.scss
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
index 56f64f934a1..720f30e18e6 100644
--- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -17,7 +17,7 @@ import flash from '~/flash';
export default function renderMermaid($els) {
if (!$els.length) return;
- import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid')
+ import(/* webpackChunkName: 'mermaid' */ 'mermaid')
.then(mermaid => {
mermaid.initialize({
// mermaid core options
diff --git a/app/assets/javascripts/jobs/components/job_container_item.vue b/app/assets/javascripts/jobs/components/job_container_item.vue
new file mode 100644
index 00000000000..81cc0823792
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/job_container_item.vue
@@ -0,0 +1,66 @@
+<script>
+import _ from 'underscore';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+ components: {
+ CiIcon,
+ Icon,
+ },
+
+ directives: {
+ tooltip,
+ },
+
+ props: {
+ job: {
+ type: Object,
+ required: true,
+ },
+ isActive: {
+ type: Boolean,
+ required: true,
+ },
+ },
+
+ computed: {
+ tooltipText() {
+ return `${_.escape(this.job.name)} - ${this.job.status.tooltip}`;
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="build-job"
+ :class="{ retried: job.retried, active: isActive }"
+ >
+ <a
+ v-tooltip
+ :href="job.status.details_path"
+ :title="tooltipText"
+ data-container="body"
+ data-boundary="viewport"
+ class="js-job-link"
+ >
+ <icon
+ v-if="isActive"
+ name="arrow-right"
+ class="js-arrow-right icon-arrow-right"
+ />
+
+ <ci-icon :status="job.status" />
+
+ <span>{{ job.name ? job.name : job.id }}</span>
+
+ <icon
+ v-if="job.retried"
+ name="retry"
+ class="js-retry-icon"
+ />
+ </a>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/jobs_container.vue b/app/assets/javascripts/jobs/components/jobs_container.vue
index 03f36ec5c8b..951bcb36600 100644
--- a/app/assets/javascripts/jobs/components/jobs_container.vue
+++ b/app/assets/javascripts/jobs/components/jobs_container.vue
@@ -1,17 +1,11 @@
<script>
-import _ from 'underscore';
-import CiIcon from '~/vue_shared/components/ci_icon.vue';
-import Icon from '~/vue_shared/components/icon.vue';
-import tooltip from '~/vue_shared/directives/tooltip';
+import JobContainerItem from './job_container_item.vue';
export default {
components: {
- CiIcon,
- Icon,
- },
- directives: {
- tooltip,
+ JobContainerItem,
},
+
props: {
jobs: {
type: Array,
@@ -26,49 +20,16 @@ export default {
isJobActive(currentJobId) {
return this.jobId === currentJobId;
},
- tooltipText(job) {
- return `${_.escape(job.name)} - ${job.status.tooltip}`;
- },
},
};
</script>
<template>
<div class="js-jobs-container builds-container">
- <div
+ <job-container-item
v-for="job in jobs"
:key="job.id"
- class="build-job"
- :class="{ retried: job.retried, active: isJobActive(job.id) }"
- >
- <a
- v-tooltip
- :href="job.status.details_path"
- :title="tooltipText(job)"
- data-container="body"
- >
- <icon
- v-if="isJobActive(job.id)"
- name="arrow-right"
- class="js-arrow-right icon-arrow-right"
- />
-
- <ci-icon :status="job.status" />
-
- <span>
- <template v-if="job.name">
- {{ job.name }}
- </template>
- <template v-else>
- {{ job.id }}
- </template>
- </span>
-
- <icon
- v-if="job.retried"
- name="retry"
- class="js-retry-icon"
- />
- </a>
- </div>
+ :job="job"
+ :is-active="isJobActive(job.id)"
+ />
</div>
</template>
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index af73954bd2e..1e00aa4ff7e 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -238,10 +238,6 @@ h3.popover-header {
}
.card {
- .card-title {
- margin-bottom: 0;
- }
-
&.card-without-border {
@extend .border-0;
}
@@ -255,13 +251,6 @@ h3.popover-header {
}
}
-.card-header {
- h3.card-title,
- h4.card-title {
- margin-top: 0;
- }
-}
-
.nav-tabs {
// Override bootstrap's default border
border-bottom: 0;
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index 5ca4d944d73..3a117106cff 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -53,8 +53,3 @@
margin-top: $gl-padding;
}
}
-
-.card-title {
- font-size: inherit;
- line-height: inherit;
-}
diff --git a/app/assets/stylesheets/framework/terms.scss b/app/assets/stylesheets/framework/terms.scss
index 7cda674e5c8..3f4be8829d7 100644
--- a/app/assets/stylesheets/framework/terms.scss
+++ b/app/assets/stylesheets/framework/terms.scss
@@ -19,17 +19,12 @@
justify-content: space-between;
line-height: $line-height-base;
- .card-title {
+ .logo-text {
+ width: 55px;
+ height: 24px;
display: flex;
- align-items: center;
-
- .logo-text {
- width: 55px;
- height: 24px;
- display: flex;
- flex-direction: column;
- justify-content: center;
- }
+ flex-direction: column;
+ justify-content: center;
}
.navbar-collapse {
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 17024e8a0af..aeee7f0a5d2 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -268,6 +268,12 @@ module Ci
stage unless stage.statuses_count.zero?
end
+ def ref_exists?
+ project.repository.ref_exists?(git_ref)
+ rescue Gitlab::Git::Repository::NoRepository
+ false
+ end
+
##
# TODO We do not completely switch to persisted stages because of
# race conditions with setting statuses gitlab-ce#23257.
@@ -674,11 +680,11 @@ module Ci
def push_details
strong_memoize(:push_details) do
- Gitlab::Git::Push.new(project, before_sha, sha, push_ref)
+ Gitlab::Git::Push.new(project, before_sha, sha, git_ref)
end
end
- def push_ref
+ def git_ref
if branch?
Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s
elsif tag?
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 20d53b8e620..95efecfc41d 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -31,6 +31,9 @@ module Clusters
has_one :application_runner, class_name: 'Clusters::Applications::Runner'
has_one :application_jupyter, class_name: 'Clusters::Applications::Jupyter'
+ has_many :kubernetes_namespaces
+ has_one :kubernetes_namespace, -> { order(id: :desc) }, class_name: 'Clusters::KubernetesNamespace'
+
accepts_nested_attributes_for :provider_gcp, update_only: true
accepts_nested_attributes_for :platform_kubernetes, update_only: true
diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb
new file mode 100644
index 00000000000..fb5f6b65d9d
--- /dev/null
+++ b/app/models/clusters/kubernetes_namespace.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Clusters
+ class KubernetesNamespace < ActiveRecord::Base
+ self.table_name = 'clusters_kubernetes_namespaces'
+
+ belongs_to :cluster_project, class_name: 'Clusters::Project'
+ belongs_to :cluster, class_name: 'Clusters::Cluster'
+ belongs_to :project, class_name: '::Project'
+ has_one :platform_kubernetes, through: :cluster
+
+ validates :namespace, presence: true
+ validates :namespace, uniqueness: { scope: :cluster_id }
+
+ before_validation :set_namespace_and_service_account_to_default, on: :create
+
+ attr_encrypted :service_account_token,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-cbc'
+
+ def token_name
+ "#{namespace}-token"
+ end
+
+ private
+
+ def set_namespace_and_service_account_to_default
+ self.namespace ||= default_namespace
+ self.service_account_name ||= default_service_account_name
+ end
+
+ def default_namespace
+ platform_kubernetes&.namespace.presence || project_namespace
+ end
+
+ def default_service_account_name
+ "#{namespace}-service-account"
+ end
+
+ def project_namespace
+ Gitlab::NamespaceSanitizer.sanitize(project_slug)
+ end
+
+ def project_slug
+ "#{project.path}-#{project.id}".downcase
+ end
+ end
+end
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 3a335909101..e8e943872de 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -7,6 +7,8 @@ module Clusters
include ReactiveCaching
include EnumWithNil
+ RESERVED_NAMESPACES = %w(gitlab-managed-apps).freeze
+
self.table_name = 'cluster_platforms_kubernetes'
self.reactive_cache_key = ->(kubernetes) { [kubernetes.class.model_name.singular, kubernetes.id] }
@@ -32,6 +34,8 @@ module Clusters
message: Gitlab::Regex.kubernetes_namespace_regex_message
}
+ validates :namespace, exclusion: { in: RESERVED_NAMESPACES }
+
# We expect to be `active?` only when enabled and cluster is created (the api_url is assigned)
validates :api_url, url: true, presence: true
validates :token, presence: true
@@ -45,6 +49,7 @@ module Clusters
delegate :project, to: :cluster, allow_nil: true
delegate :enabled?, to: :cluster, allow_nil: true
delegate :managed?, to: :cluster, allow_nil: true
+ delegate :kubernetes_namespace, to: :cluster
alias_method :active?, :enabled?
@@ -116,10 +121,19 @@ module Clusters
end
def default_namespace
+ kubernetes_namespace&.namespace.presence || fallback_default_namespace
+ end
+
+ # DEPRECATED
+ #
+ # On 11.4 Clusters::KubernetesNamespace was introduced, this model will allow to
+ # have multiple namespaces per project. This method will be removed after migration
+ # has been completed.
+ def fallback_default_namespace
return unless project
slug = "#{project.path}-#{project.id}".downcase
- slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '')
+ Gitlab::NamespaceSanitizer.sanitize(slug)
end
def build_kube_client!(api_groups: ['api'], api_version: 'v1')
diff --git a/app/models/clusters/project.rb b/app/models/clusters/project.rb
index 839ce796081..15092b1c9d2 100644
--- a/app/models/clusters/project.rb
+++ b/app/models/clusters/project.rb
@@ -6,5 +6,8 @@ module Clusters
belongs_to :cluster, class_name: 'Clusters::Cluster'
belongs_to :project, class_name: '::Project'
+
+ has_many :kubernetes_namespaces, class_name: 'Clusters::KubernetesNamespace', foreign_key: :cluster_project_id
+ has_one :kubernetes_namespace, -> { order(id: :desc) }, class_name: 'Clusters::KubernetesNamespace', foreign_key: :cluster_project_id
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 086f256174f..be99408fcea 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -688,6 +688,8 @@ class Project < ActiveRecord::Base
else
super
end
+ rescue
+ super
end
def valid_import_url?
@@ -1628,34 +1630,6 @@ class Project < ActiveRecord::Base
end
# rubocop: enable CodeReuse/ServiceClass
- def rename_repo
- path_before = previous_changes['path'].first
- full_path_before = full_path_was
- full_path_after = build_full_path
-
- Gitlab::AppLogger.info("Attempting to rename #{full_path_was} -> #{full_path_after}")
-
- if has_container_registry_tags?
- Gitlab::AppLogger.info("Project #{full_path_was} cannot be renamed because container registry tags are present!")
-
- # we currently don't support renaming repository if it contains images in container registry
- raise StandardError.new('Project cannot be renamed, because images are present in its container registry')
- end
-
- expire_caches_before_rename(full_path_before)
-
- if rename_or_migrate_repository!
- Gitlab::AppLogger.info("Project was renamed: #{full_path_before} -> #{full_path_after}")
- after_rename_repository(full_path_before, path_before)
- else
- Gitlab::AppLogger.info("Repository could not be renamed: #{full_path_before} -> #{full_path_after}")
-
- # if we cannot move namespace directory we should rollback
- # db changes in order to prevent out of sync between db and fs
- raise StandardError.new('Repository cannot be renamed')
- end
- end
-
def write_repository_config(gl_full_path: full_path)
# We'd need to keep track of project full path otherwise directory tree
# created with hashed storage enabled cannot be usefully imported using
@@ -2084,51 +2058,6 @@ class Project < ActiveRecord::Base
auto_cancel_pending_pipelines == 'enabled'
end
- private
-
- # rubocop: disable CodeReuse/ServiceClass
- def rename_or_migrate_repository!
- if Gitlab::CurrentSettings.hashed_storage_enabled? &&
- storage_upgradable? &&
- Feature.disabled?(:skip_hashed_storage_upgrade) # kill switch in case we need to disable upgrade behavior
- ::Projects::HashedStorageMigrationService.new(self, full_path_was).execute
- else
- storage.rename_repo
- end
- end
- # rubocop: enable CodeReuse/ServiceClass
-
- def storage_upgradable?
- storage_version != LATEST_STORAGE_VERSION
- end
-
- def after_rename_repository(full_path_before, path_before)
- execute_rename_repository_hooks!(full_path_before)
-
- write_repository_config
-
- # We need to check if project had been rolled out to move resource to hashed storage or not and decide
- # if we need execute any take action or no-op.
- unless hashed_storage?(:attachments)
- Gitlab::UploadsTransfer.new.rename_project(path_before, self.path, namespace.full_path)
- end
-
- Gitlab::PagesTransfer.new.rename_project(path_before, self.path, namespace.full_path)
- end
-
- # rubocop: disable CodeReuse/ServiceClass
- def execute_rename_repository_hooks!(full_path_before)
- # When we import a project overwriting the original project, there
- # is a move operation. In that case we don't want to send the instructions.
- send_move_instructions(full_path_before) unless import_started?
-
- self.old_path_with_namespace = full_path_before
- SystemHooksService.new.execute_hooks_for(self, :rename)
-
- reload_repository!
- end
- # rubocop: enable CodeReuse/ServiceClass
-
def storage
@storage ||=
if hashed_storage?(:repository)
@@ -2138,6 +2067,12 @@ class Project < ActiveRecord::Base
end
end
+ def storage_upgradable?
+ storage_version != LATEST_STORAGE_VERSION
+ end
+
+ private
+
def use_hashed_storage
if self.new_record? && Gitlab::CurrentSettings.hashed_storage_enabled
self.storage_version = LATEST_STORAGE_VERSION
diff --git a/app/services/projects/after_rename_service.rb b/app/services/projects/after_rename_service.rb
new file mode 100644
index 00000000000..4131da44f5a
--- /dev/null
+++ b/app/services/projects/after_rename_service.rb
@@ -0,0 +1,135 @@
+# frozen_string_literal: true
+
+module Projects
+ # Service class for performing operations that should take place after a
+ # project has been renamed.
+ #
+ # Example usage:
+ #
+ # project = Project.find(42)
+ #
+ # project.update(...)
+ #
+ # Projects::AfterRenameService.new(project).execute
+ class AfterRenameService
+ attr_reader :project, :full_path_before, :full_path_after, :path_before
+
+ RenameFailedError = Class.new(StandardError)
+
+ # @param [Project] project The Project of the repository to rename.
+ def initialize(project)
+ @project = project
+
+ # The full path of the namespace + project, before the rename took place.
+ @full_path_before = project.full_path_was
+
+ # The full path of the namespace + project, after the rename took place.
+ @full_path_after = project.build_full_path
+
+ # The path of just the project, before the rename took place.
+ @path_before = project.path_was
+ end
+
+ def execute
+ first_ensure_no_registry_tags_are_present
+ expire_caches_before_rename
+ rename_or_migrate_repository!
+ send_move_instructions
+ execute_system_hooks
+ update_repository_configuration
+ rename_transferred_documents
+ log_completion
+ end
+
+ def first_ensure_no_registry_tags_are_present
+ return unless project.has_container_registry_tags?
+
+ raise RenameFailedError.new(
+ "Project #{full_path_before} cannot be renamed because images are " \
+ "present in its container registry"
+ )
+ end
+
+ def expire_caches_before_rename
+ project.expire_caches_before_rename(full_path_before)
+ end
+
+ def rename_or_migrate_repository!
+ success =
+ if migrate_to_hashed_storage?
+ ::Projects::HashedStorageMigrationService
+ .new(project, full_path_before)
+ .execute
+ else
+ project.storage.rename_repo
+ end
+
+ rename_failed! unless success
+ end
+
+ def send_move_instructions
+ return unless send_move_instructions?
+
+ project.send_move_instructions(full_path_before)
+ end
+
+ def execute_system_hooks
+ project.old_path_with_namespace = full_path_before
+ SystemHooksService.new.execute_hooks_for(project, :rename)
+ end
+
+ def update_repository_configuration
+ project.reload_repository!
+ project.write_repository_config
+ end
+
+ def rename_transferred_documents
+ if rename_uploads?
+ Gitlab::UploadsTransfer
+ .new
+ .rename_project(path_before, project_path, namespace_full_path)
+ end
+
+ Gitlab::PagesTransfer
+ .new
+ .rename_project(path_before, project_path, namespace_full_path)
+ end
+
+ def log_completion
+ Gitlab::AppLogger.info(
+ "Project #{project.id} has been renamed from " \
+ "#{full_path_before} to #{full_path_after}"
+ )
+ end
+
+ def migrate_to_hashed_storage?
+ Gitlab::CurrentSettings.hashed_storage_enabled? &&
+ project.storage_upgradable? &&
+ Feature.disabled?(:skip_hashed_storage_upgrade)
+ end
+
+ def send_move_instructions?
+ !project.import_started?
+ end
+
+ def rename_uploads?
+ !project.hashed_storage?(:attachments)
+ end
+
+ def project_path
+ project.path
+ end
+
+ def namespace_full_path
+ project.namespace.full_path
+ end
+
+ def rename_failed!
+ error = "Repository #{full_path_before} could not be renamed to #{full_path_after}"
+
+ Gitlab::AppLogger.error(error)
+
+ raise RenameFailedError.new(error)
+ end
+ end
+end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index f25a4e30938..93e48fc0199 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -67,7 +67,7 @@ module Projects
end
if project.previous_changes.include?('path')
- project.rename_repo
+ AfterRenameService.new(project).execute
else
system_hook_service.execute_hooks_for(project, :update)
end
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 21c1260e982..5f205d1bcbc 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -63,10 +63,9 @@
.card
.card-header
- %h3.card-title
- = _('Projects')
- %span.badge.badge-pill
- #{@group.projects.count}
+ = _('Projects')
+ %span.badge.badge-pill
+ #{@group.projects.count}
%ul.content-list
- @projects.each do |project|
%li
diff --git a/app/views/layouts/terms.html.haml b/app/views/layouts/terms.html.haml
index 977eb350365..cdad617f006 100644
--- a/app/views/layouts/terms.html.haml
+++ b/app/views/layouts/terms.html.haml
@@ -16,19 +16,18 @@
.content{ id: "content-body" }
.card
.card-header
- .card-title
- = brand_header_logo
- - logo_text = brand_header_logo_type
- - if logo_text.present?
- %span.logo-text.prepend-left-8
- = logo_text
- - if header_link?(:user_dropdown)
- .navbar-collapse
- %ul.nav.navbar-nav
- %li.header-user.dropdown
- = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
- = image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar"
- = sprite_icon('angle-down', css_class: 'caret-down')
- .dropdown-menu.dropdown-menu-right
- = render 'layouts/header/current_user_dropdown'
+ = brand_header_logo
+ - logo_text = brand_header_logo_type
+ - if logo_text.present?
+ %span.logo-text.prepend-left-8
+ = logo_text
+ - if header_link?(:user_dropdown)
+ .navbar-collapse
+ %ul.nav.navbar-nav
+ %li.header-user.dropdown
+ = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
+ = image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar"
+ = sprite_icon('angle-down', css_class: 'caret-down')
+ .dropdown-menu.dropdown-menu-right
+ = render 'layouts/header/current_user_dropdown'
= yield
diff --git a/app/views/projects/branches/_panel.html.haml b/app/views/projects/branches/_panel.html.haml
index 398f76d379a..0e4b119bb54 100644
--- a/app/views/projects/branches/_panel.html.haml
+++ b/app/views/projects/branches/_panel.html.haml
@@ -9,8 +9,7 @@
.card.prepend-top-10
.card-header
- %h4.card-title
- = panel_title
+ = panel_title
%ul.content-list.all-branches
- branches.first(overview_max_branches).each do |branch|
= render "projects/branches/branch", branch: branch, merged: project.repository.merged_to_root_ref?(branch)
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index dbb563f51ea..2575efc0981 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -13,7 +13,11 @@
= pluralize @pipeline.total_size, "job"
- if @pipeline.ref
from
- = link_to @pipeline.ref, project_ref_path(@project, @pipeline.ref), class: "ref-name"
+ - if @pipeline.ref_exists?
+ = link_to @pipeline.ref, project_ref_path(@project, @pipeline.ref), class: "ref-name"
+ - else
+ %span.ref-name
+ = @pipeline.ref
- if @pipeline.duration
in
= time_interval_in_words(@pipeline.duration)
diff --git a/app/views/projects/protected_branches/shared/_branches_list.html.haml b/app/views/projects/protected_branches/shared/_branches_list.html.haml
index 9a06eca89bb..1913d06a6f8 100644
--- a/app/views/projects/protected_branches/shared/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml
@@ -1,8 +1,7 @@
.protected-branches-list.js-protected-branches-list.qa-protected-branches-list
- if @protected_branches.empty?
.card-header.bg-white
- %h3.card-title.mb-0
- Protected branch (#{@protected_branches_count})
+ Protected branch (#{@protected_branches_count})
%p.settings-message.text-center
There are currently no protected branches, protect a branch with the form above.
- else
diff --git a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
index c3b8f2f8964..d617d85afc2 100644
--- a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
@@ -2,8 +2,7 @@
%input{ type: 'hidden', name: 'update_section', value: 'js-protected-branches-settings' }
.card
.card-header
- %h3.card-title
- Protect a branch
+ Protect a branch
.card-body
= form_errors(@protected_branch)
.form-group.row
diff --git a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
index b274c73d035..cbf1938664c 100644
--- a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
@@ -2,8 +2,7 @@
%input{ type: 'hidden', name: 'update_section', value: 'js-protected-tags-settings' }
.card
.card-header
- %h3.card-title
- Protect a tag
+ Protect a tag
.card-body
= form_errors(@protected_tag)
.form-group.row
diff --git a/app/views/projects/protected_tags/shared/_tags_list.html.haml b/app/views/projects/protected_tags/shared/_tags_list.html.haml
index c3081d75fb4..382ea848243 100644
--- a/app/views/projects/protected_tags/shared/_tags_list.html.haml
+++ b/app/views/projects/protected_tags/shared/_tags_list.html.haml
@@ -1,8 +1,7 @@
.protected-tags-list.js-protected-tags-list
- if @protected_tags.empty?
.card-header
- %h3.card-title
- Protected tag (#{@protected_tags_count})
+ Protected tag (#{@protected_tags_count})
%p.settings-message.text-center
There are currently no protected tags, protect a tag with the form above.
- else
diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml
index 0426f2215ad..db1f15f96b8 100644
--- a/app/views/projects/registry/repositories/index.html.haml
+++ b/app/views/projects/registry/repositories/index.html.haml
@@ -18,8 +18,7 @@
.col-lg-12
.card
.card-header
- %h4.card-title
- = s_('ContainerRegistry|How to use the Container Registry')
+ = s_('ContainerRegistry|How to use the Container Registry')
.card-body
%p
- link_token = link_to(_('personal access token'), help_page_path('user/profile/account/two_factor_authentication', anchor: 'personal-access-tokens'), target: '_blank')
diff --git a/app/views/projects/services/prometheus/_metrics.html.haml b/app/views/projects/services/prometheus/_metrics.html.haml
index 98d64fafe86..597c029f755 100644
--- a/app/views/projects/services/prometheus/_metrics.html.haml
+++ b/app/views/projects/services/prometheus/_metrics.html.haml
@@ -2,9 +2,8 @@
.card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/metrics') } }
.card-header
- %h3.card-title
- = s_('PrometheusService|Common metrics')
- %span.badge.badge-pill.js-monitored-count 0
+ = s_('PrometheusService|Common metrics')
+ %span.badge.badge-pill.js-monitored-count 0
.card-body
.loading-metrics.js-loading-metrics
%p.prepend-top-10.prepend-left-10
@@ -17,10 +16,9 @@
.card.hidden.js-panel-missing-env-vars
.card-header
- %h3.card-title
- = icon('caret-right lg fw', class: 'panel-toggle js-panel-toggle', 'aria-label' => 'Toggle panel')
- = s_('PrometheusService|Missing environment variable')
- %span.badge.badge-pill.js-env-var-count 0
+ = icon('caret-right lg fw', class: 'panel-toggle js-panel-toggle', 'aria-label' => 'Toggle panel')
+ = s_('PrometheusService|Missing environment variable')
+ %span.badge.badge-pill.js-env-var-count 0
.card-body.hidden
.flash-container
.flash-notice
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 24724394259..5e6d06d980e 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -20,8 +20,9 @@
.col-sm-10.create-from
.dropdown
= hidden_field_tag :ref, default_ref
- = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
+ = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select monospace', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
.text-left.dropdown-toggle-text= default_ref
+ = icon('chevron-down')
= render 'shared/ref_dropdown', dropdown_class: 'wide'
.form-text.text-muted
= s_('TagsPage|Existing branch name, tag, or commit SHA')
diff --git a/app/views/projects/triggers/_index.html.haml b/app/views/projects/triggers/_index.html.haml
index a15bb4c4f3f..a559ce41e57 100644
--- a/app/views/projects/triggers/_index.html.haml
+++ b/app/views/projects/triggers/_index.html.haml
@@ -3,8 +3,7 @@
= render "projects/triggers/content"
.card
.card-header
- %h4.card-title
- Manage your project's triggers
+ Manage your project's triggers
.card-body
= render "projects/triggers/form", btn_text: "Add trigger"
%hr
diff --git a/app/views/shared/runners/show.html.haml b/app/views/shared/runners/show.html.haml
index 362569bfbaf..f62eed694d2 100644
--- a/app/views/shared/runners/show.html.haml
+++ b/app/views/shared/runners/show.html.haml
@@ -24,7 +24,7 @@
%td= @runner.active? ? 'Yes' : 'No'
%tr
%td Protected
- %td= @runner.active? ? _('Yes') : _('No')
+ %td= @runner.ref_protected? ? 'Yes' : 'No'
%tr
%td Can run untagged jobs
%td= @runner.run_untagged? ? 'Yes' : 'No'
diff --git a/changelogs/unreleased/42611-removed-branch-link.yml b/changelogs/unreleased/42611-removed-branch-link.yml
new file mode 100644
index 00000000000..03a206871b4
--- /dev/null
+++ b/changelogs/unreleased/42611-removed-branch-link.yml
@@ -0,0 +1,5 @@
+---
+title: Only render link to branch when branch still exists in pipeline page
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/46884-remove-card-title.yml b/changelogs/unreleased/46884-remove-card-title.yml
new file mode 100644
index 00000000000..95f08a67638
--- /dev/null
+++ b/changelogs/unreleased/46884-remove-card-title.yml
@@ -0,0 +1,5 @@
+---
+title: Remove .card-title from .card-header for BS4 migration
+merge_request: 19335
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/51716-add-kubernetes-namespace-model.yml b/changelogs/unreleased/51716-add-kubernetes-namespace-model.yml
new file mode 100644
index 00000000000..ad43c512ba3
--- /dev/null
+++ b/changelogs/unreleased/51716-add-kubernetes-namespace-model.yml
@@ -0,0 +1,5 @@
+---
+title: Introduce new model to persist specific cluster information
+merge_request: 22404
+author:
+type: added
diff --git a/changelogs/unreleased/52840-fix-runners-details-page.yml b/changelogs/unreleased/52840-fix-runners-details-page.yml
new file mode 100644
index 00000000000..b061390fcf0
--- /dev/null
+++ b/changelogs/unreleased/52840-fix-runners-details-page.yml
@@ -0,0 +1,5 @@
+---
+title: Fix rendering of 'Protected' value on Runner details page
+merge_request: 22459
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-new-kubernetes-spec-helpers.yml b/changelogs/unreleased/add-new-kubernetes-spec-helpers.yml
new file mode 100644
index 00000000000..87023ede020
--- /dev/null
+++ b/changelogs/unreleased/add-new-kubernetes-spec-helpers.yml
@@ -0,0 +1,5 @@
+---
+title: Introduce new kubernetes helpers
+merge_request: 22525
+author:
+type: other
diff --git a/changelogs/unreleased/blackst0ne-bump-mermaid.yml b/changelogs/unreleased/blackst0ne-bump-mermaid.yml
new file mode 100644
index 00000000000..cb924ac8448
--- /dev/null
+++ b/changelogs/unreleased/blackst0ne-bump-mermaid.yml
@@ -0,0 +1,5 @@
+---
+title: Bump mermaid to 8.0.0-rc.8
+merge_request: 22509
+author: "@blackst0ne"
+type: changed
diff --git a/changelogs/unreleased/change-branch-font-type-in-tag-creation.yml b/changelogs/unreleased/change-branch-font-type-in-tag-creation.yml
new file mode 100644
index 00000000000..0f46efb693f
--- /dev/null
+++ b/changelogs/unreleased/change-branch-font-type-in-tag-creation.yml
@@ -0,0 +1,5 @@
+---
+title: Change branch font type in tag creation
+merge_request: 22454
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/feature-gb-improve-include-config-errors-reporting.yml b/changelogs/unreleased/feature-gb-improve-include-config-errors-reporting.yml
new file mode 100644
index 00000000000..67eb6b78096
--- /dev/null
+++ b/changelogs/unreleased/feature-gb-improve-include-config-errors-reporting.yml
@@ -0,0 +1,5 @@
+---
+title: Improve validation errors for external CI/CD configuration
+merge_request: 22394
+author:
+type: added
diff --git a/changelogs/unreleased/gt-remove-empty-spec-describe-blocks.yml b/changelogs/unreleased/gt-remove-empty-spec-describe-blocks.yml
index c1f7a2a40c7..d2a65d48d8d 100644
--- a/changelogs/unreleased/gt-remove-empty-spec-describe-blocks.yml
+++ b/changelogs/unreleased/gt-remove-empty-spec-describe-blocks.yml
@@ -1,5 +1,5 @@
---
title: Remove empty spec describe blocks
merge_request: 22451
-author: george Tsiolis
+author: George Tsiolis
type: other
diff --git a/changelogs/unreleased/sh-pages-eof-error.yml b/changelogs/unreleased/sh-pages-eof-error.yml
new file mode 100644
index 00000000000..497a74c1458
--- /dev/null
+++ b/changelogs/unreleased/sh-pages-eof-error.yml
@@ -0,0 +1,5 @@
+---
+title: Fix EOF detection with CI artifacts metadata
+merge_request: 22479
+author:
+type: fixed
diff --git a/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb b/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb
new file mode 100644
index 00000000000..a58c190e1d6
--- /dev/null
+++ b/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class CreateClustersKubernetesNamespaces < ActiveRecord::Migration
+ DOWNTIME = false
+ INDEX_NAME = 'kubernetes_namespaces_cluster_and_namespace'
+
+ def change
+ create_table :clusters_kubernetes_namespaces, id: :bigserial do |t|
+ t.references :cluster, null: false, index: true, foreign_key: { on_delete: :cascade }
+ t.references :project, index: true, foreign_key: { on_delete: :nullify }
+ t.references :cluster_project, index: true, foreign_key: { on_delete: :nullify }
+
+ t.timestamps_with_timezone null: false
+
+ t.string :encrypted_service_account_token_iv
+ t.string :namespace, null: false
+ t.string :service_account_name
+
+ t.text :encrypted_service_account_token
+
+ t.index [:cluster_id, :namespace], name: INDEX_NAME, unique: true
+ end
+ end
+end
diff --git a/db/post_migrate/20161221153951_rename_reserved_project_names.rb b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
index 08d7f499eec..678876e886c 100644
--- a/db/post_migrate/20161221153951_rename_reserved_project_names.rb
+++ b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
@@ -113,7 +113,9 @@ class RenameReservedProjectNames < ActiveRecord::Migration
begin
# Because project path update is quite complex operation we can't safely
# copy-paste all code from GitLab. As exception we use Rails code here
- project.rename_repo if rename_project_row(project, path)
+ if rename_project_row(project, path)
+ Projects::AfterRenameService.new(project).execute
+ end
rescue Exception => e # rubocop: disable Lint/RescueException
Rails.logger.error "Exception when renaming project #{id}: #{e.message}"
end
@@ -123,6 +125,6 @@ class RenameReservedProjectNames < ActiveRecord::Migration
def rename_project_row(project, path)
project.respond_to?(:update_attributes) &&
project.update(path: path) &&
- project.respond_to?(:rename_repo)
+ defined?(Projects::AfterRenameService)
end
end
diff --git a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
index 43a37667250..26a67b0f814 100644
--- a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
+++ b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
@@ -55,7 +55,9 @@ class RenameMoreReservedProjectNames < ActiveRecord::Migration
begin
# Because project path update is quite complex operation we can't safely
# copy-paste all code from GitLab. As exception we use Rails code here
- project.rename_repo if rename_project_row(project, path)
+ if rename_project_row(project, path)
+ Projects::AfterRenameService.new(project).execute
+ end
rescue Exception => e # rubocop: disable Lint/RescueException
Rails.logger.error "Exception when renaming project #{id}: #{e.message}"
end
@@ -65,6 +67,6 @@ class RenameMoreReservedProjectNames < ActiveRecord::Migration
def rename_project_row(project, path)
project.respond_to?(:update_attributes) &&
project.update(path: path) &&
- project.respond_to?(:rename_repo)
+ defined?(Projects::AfterRenameService)
end
end
diff --git a/db/schema.rb b/db/schema.rb
index 3f3bec0ce04..50989960aa9 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -691,6 +691,23 @@ ActiveRecord::Schema.define(version: 20181013005024) do
add_index "clusters_applications_runners", ["cluster_id"], name: "index_clusters_applications_runners_on_cluster_id", unique: true, using: :btree
add_index "clusters_applications_runners", ["runner_id"], name: "index_clusters_applications_runners_on_runner_id", using: :btree
+ create_table "clusters_kubernetes_namespaces", id: :bigserial, force: :cascade do |t|
+ t.integer "cluster_id", null: false
+ t.integer "project_id"
+ t.integer "cluster_project_id"
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.text "encrypted_service_account_token"
+ t.string "encrypted_service_account_token_iv"
+ t.string "namespace", null: false
+ t.string "service_account_name"
+ end
+
+ add_index "clusters_kubernetes_namespaces", ["cluster_id", "namespace"], name: "kubernetes_namespaces_cluster_and_namespace", unique: true, using: :btree
+ add_index "clusters_kubernetes_namespaces", ["cluster_id"], name: "index_clusters_kubernetes_namespaces_on_cluster_id", using: :btree
+ add_index "clusters_kubernetes_namespaces", ["cluster_project_id"], name: "index_clusters_kubernetes_namespaces_on_cluster_project_id", using: :btree
+ add_index "clusters_kubernetes_namespaces", ["project_id"], name: "index_clusters_kubernetes_namespaces_on_project_id", using: :btree
+
create_table "container_repositories", force: :cascade do |t|
t.integer "project_id", null: false
t.string "name", null: false
@@ -2325,6 +2342,9 @@ ActiveRecord::Schema.define(version: 20181013005024) do
add_foreign_key "clusters_applications_prometheus", "clusters", name: "fk_557e773639", on_delete: :cascade
add_foreign_key "clusters_applications_runners", "ci_runners", column: "runner_id", name: "fk_02de2ded36", on_delete: :nullify
add_foreign_key "clusters_applications_runners", "clusters", on_delete: :cascade
+ add_foreign_key "clusters_kubernetes_namespaces", "cluster_projects", on_delete: :nullify
+ add_foreign_key "clusters_kubernetes_namespaces", "clusters", on_delete: :cascade
+ add_foreign_key "clusters_kubernetes_namespaces", "projects", on_delete: :nullify
add_foreign_key "container_repositories", "projects"
add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade
add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade
diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md
index ec850c53deb..9da4c66933c 100644
--- a/doc/development/contributing/index.md
+++ b/doc/development/contributing/index.md
@@ -8,7 +8,7 @@ We want to create a welcoming environment for everyone who is interested in cont
For a first-time step-by-step guide to the contribution process, please see
["Contributing to GitLab"](https://about.gitlab.com/contributing/).
-Looking for something to work on? Look for issues in the [Backlog (Accepting merge requests) milestone](#i-want-to-contribute).
+Looking for something to work on? Look for issues with the label [`Accepting merge requests`](#i-want-to-contribute).
GitLab comes in two flavors, GitLab Community Edition (CE) our free and open
source edition, and GitLab Enterprise Edition (EE) which is our commercial
@@ -30,6 +30,11 @@ Please report suspected security vulnerabilities in private to
Please do **NOT** create publicly viewable issues for suspected security
vulnerabilities.
+## Code of conduct
+
+Our code of conduct can be found on the
+["Contributing to GitLab"](https://about.gitlab.com/contributing/) page.
+
## Closing policy for issues and merge requests
GitLab is a popular open source project and the capacity to deal with issues
@@ -61,10 +66,10 @@ the remaining issues on the GitHub issue tracker.
## I want to contribute!
-If you want to contribute to GitLab, [issues in the `Backlog (Accepting merge requests)` milestone][accepting-mrs-weight]
-are a great place to start. Issues with a lower weight (1 or 2) are deemed
-suitable for beginners. These issues will be of reasonable size and challenge,
-for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to
+If you want to contribute to GitLab,
+[issues with the `Accepting merge requests` label](issue_workflow.md#label-for-community-contributors)
+are a great place to start.
+If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to
learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel
please consider we favor
[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution!
@@ -126,4 +131,3 @@ This [documentation](style_guides.md) outlines the current style guidelines.
[team]: https://about.gitlab.com/team/
[getting-help]: https://about.gitlab.com/getting-help/
[codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
-[accepting-mrs-weight]: https://gitlab.com/gitlab-org/gitlab-ce/issues?scope=all&utf8=✓&state=opened&assignee_id=0&milestone_title=Backlog%20&#40;Accepting%20merge%20requests&#41;
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index 47264bec571..4661d11b29e 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -181,10 +181,10 @@ Severity levels can be applied further depending on the facet of the impact; e.g
Issues that are beneficial to our users, 'nice to haves', that we currently do
not have the capacity for or want to give the priority to, are labeled as
-~"Accepting Merge Requests", so the community can make a contribution.
+~"Accepting merge requests", so the community can make a contribution.
Community contributors can submit merge requests for any issue they want, but
-the ~"Accepting Merge Requests" label has a special meaning. It points to
+the ~"Accepting merge requests" label has a special meaning. It points to
changes that:
1. We already agreed on,
@@ -192,26 +192,26 @@ changes that:
1. Are likely to get accepted by a maintainer.
We want to avoid a situation when a contributor picks an
-~"Accepting Merge Requests" issue and then their merge request gets closed,
+~"Accepting merge requests" issue and then their merge request gets closed,
because we realize that it does not fit our vision, or we want to solve it in a
different way.
-We add the ~"Accepting Merge Requests" label to:
+We add the ~"Accepting merge requests" label to:
- Low priority ~bug issues (i.e. we do not add it to the bugs that we want to
solve in the ~"Next Patch Release")
- Small ~"feature proposal"
- Small ~"technical debt" issues
-After adding the ~"Accepting Merge Requests" label, we try to estimate the
+After adding the ~"Accepting merge requests" label, we try to estimate the
[weight](#issue-weight) of the issue. We use issue weight to let contributors
know how difficult the issue is. Additionally:
-- We advertise ["Accepting Merge Requests" issues with weight < 5][up-for-grabs]
+- We advertise [`Accepting merge requests` issues with weight < 5][up-for-grabs]
as suitable for people that have never contributed to GitLab before on the
[Up For Grabs campaign](http://up-for-grabs.net)
- We encourage people that have never contributed to any open source project to
- look for ["Accepting Merge Requests" issues with a weight of 1][firt-timers]
+ look for [`Accepting merge requests` issues with a weight of 1][firt-timers]
If you've decided that you would like to work on an issue, please @-mention
the [appropriate product manager](https://about.gitlab.com/handbook/product/#who-to-talk-to-for-what)
@@ -220,12 +220,12 @@ members to further discuss scope, design, and technical considerations. This wil
ensure that your contribution is aligned with the GitLab product and minimize
any rework and delay in getting it merged into master.
-GitLab team members who apply the ~"Accepting Merge Requests" label to an issue
+GitLab team members who apply the ~"Accepting merge requests" label to an issue
should update the issue description with a responsible product manager, inviting
any potential community contributor to @-mention per above.
-[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests&scope=all&sort=weight_asc&state=opened
-[firt-timers]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=Accepting+Merge+Requests&scope=all&sort=upvotes_desc&state=opened&weight=1
+[up-for-grabs]: https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=0&sort=weight
+[firt-timers]: https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=0&sort=weight&weight=1
## Issue triaging
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index cc7d8a1e1db..1764e2d8b21 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -2,10 +2,9 @@
We welcome merge requests with fixes and improvements to GitLab code, tests,
and/or documentation. The issues that are specifically suitable for
-community contributions are listed with the
-[`Backlog (Accepting merge requests)` milestone in the CE issue tracker][accepting-mrs-ce]
-and [EE issue tracker][accepting-mrs-ee], but you are free to contribute to any other issue
-you want.
+community contributions are listed with
+[the `Accepting merge requests` label](issue_workflow.md#label-for-community-contributors),
+but you are free to contribute to any other issue you want.
Please note that if an issue is marked for the current milestone either before
or while you are working on it, a team member may take over the merge request
@@ -25,8 +24,6 @@ some potentially easy issues.
To start with GitLab development download the [GitLab Development Kit][gdk] and
see the [Development section](../../README.md) for some guidelines.
-[accepting-mrs-ce]: https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_title=Backlog%20&#40;Accepting%20merge%20requests&#41;
-[accepting-mrs-ee]: https://gitlab.com/gitlab-org/gitlab-ee/issues?milestone_title=Backlog%20&#40;Accepting%20merge%20requests&#41;
[gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests
[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index e2eea57d694..a7470d27b4b 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -339,6 +339,23 @@ args: {
}
```
+### `uid_attribute`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/43806) in GitLab 10.7.
+
+By default, the `uid` is set as the `name_id` in the SAML response. If you'd like to designate a unique attribute for the `uid`, you can set the `uid_attribute`. In the example below, the value of `uid` attribute in the SAML response is set as the `uid_attribute`.
+
+```yaml
+args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
+ uid_attribute: 'uid'
+}
+```
+
## Troubleshooting
### 500 error after login
diff --git a/lib/gitlab/ci/build/artifacts/metadata.rb b/lib/gitlab/ci/build/artifacts/metadata.rb
index 375d8bc1ff5..551d4f4473e 100644
--- a/lib/gitlab/ci/build/artifacts/metadata.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata.rb
@@ -59,9 +59,12 @@ module Gitlab
until gz.eof?
begin
- path = read_string(gz).force_encoding('UTF-8')
- meta = read_string(gz).force_encoding('UTF-8')
+ path = read_string(gz)&.force_encoding('UTF-8')
+ meta = read_string(gz)&.force_encoding('UTF-8')
+ # We might hit an EOF while reading either value, so we should
+ # abort if we don't get any data.
+ next unless path && meta
next unless path.valid_encoding? && meta.valid_encoding?
next unless path =~ match_pattern
next if path =~ INVALID_PATH_PATTERN
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index fe98d25af29..fedaf18ef30 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -13,10 +13,10 @@ module Gitlab
@global = Entry::Global.new(@config)
@global.compose!
- rescue Loader::FormatError, Extendable::ExtensionError => e
+ rescue Loader::FormatError,
+ Extendable::ExtensionError,
+ External::Processor::IncludeError => e
raise Config::ConfigError, e.message
- rescue ::Gitlab::Ci::External::Processor::FileError => e
- raise ::Gitlab::Ci::YamlProcessor::ValidationError, e.message
end
def valid?
@@ -81,7 +81,7 @@ module Gitlab
def process_external_files(config, project, opts)
sha = opts.fetch(:sha) { project.repository.root_ref_sha }
- ::Gitlab::Ci::External::Processor.new(config, project, sha).perform
+ Config::External::Processor.new(config, project, sha).perform
end
end
end
diff --git a/lib/gitlab/ci/config/external/file/base.rb b/lib/gitlab/ci/config/external/file/base.rb
new file mode 100644
index 00000000000..15ca47ef60e
--- /dev/null
+++ b/lib/gitlab/ci/config/external/file/base.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ module File
+ class Base
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :location, :opts, :errors
+
+ YAML_WHITELIST_EXTENSION = /.+\.(yml|yaml)$/i.freeze
+
+ def initialize(location, opts = {})
+ @location = location
+ @opts = opts
+ @errors = []
+
+ validate!
+ end
+
+ def invalid_extension?
+ !::File.basename(location).match(YAML_WHITELIST_EXTENSION)
+ end
+
+ def valid?
+ errors.none?
+ end
+
+ def error_message
+ errors.first
+ end
+
+ def content
+ raise NotImplementedError, 'subclass must implement fetching raw content'
+ end
+
+ def to_hash
+ @hash ||= Ci::Config::Loader.new(content).load!
+ rescue Ci::Config::Loader::FormatError
+ nil
+ end
+
+ protected
+
+ def validate!
+ validate_location!
+ validate_content! if errors.none?
+ validate_hash! if errors.none?
+ end
+
+ def validate_location!
+ if invalid_extension?
+ errors.push("Included file `#{location}` does not have YAML extension!")
+ end
+ end
+
+ def validate_content!
+ if content.blank?
+ errors.push("Included file `#{location}` is empty or does not exist!")
+ end
+ end
+
+ def validate_hash!
+ if to_hash.blank?
+ errors.push("Included file `#{location}` does not have valid YAML syntax!")
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/file/local.rb b/lib/gitlab/ci/config/external/file/local.rb
new file mode 100644
index 00000000000..2a256aff65c
--- /dev/null
+++ b/lib/gitlab/ci/config/external/file/local.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ module File
+ class Local < Base
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :project, :sha
+
+ def initialize(location, opts = {})
+ @project = opts.fetch(:project)
+ @sha = opts.fetch(:sha)
+
+ super
+ end
+
+ def content
+ strong_memoize(:content) { fetch_local_content }
+ end
+
+ private
+
+ def validate_content!
+ if content.nil?
+ errors.push("Local file `#{location}` does not exist!")
+ elsif content.blank?
+ errors.push("Local file `#{location}` is empty!")
+ end
+ end
+
+ def fetch_local_content
+ project.repository.blob_data_at(sha, location)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/file/remote.rb b/lib/gitlab/ci/config/external/file/remote.rb
new file mode 100644
index 00000000000..86fa5ad8800
--- /dev/null
+++ b/lib/gitlab/ci/config/external/file/remote.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ module File
+ class Remote < Base
+ include Gitlab::Utils::StrongMemoize
+
+ def content
+ strong_memoize(:content) { fetch_remote_content }
+ end
+
+ private
+
+ def validate_location!
+ super
+
+ unless ::Gitlab::UrlSanitizer.valid?(location)
+ errors.push("Remote file `#{location}` does not have a valid address!")
+ end
+ end
+
+ def fetch_remote_content
+ begin
+ response = Gitlab::HTTP.get(location)
+ rescue SocketError
+ errors.push("Remote file `#{location}` could not be fetched because of a socket error!")
+ rescue Timeout::Error
+ errors.push("Remote file `#{location}` could not be fetched because of a timeout error!")
+ rescue Gitlab::HTTP::Error
+ errors.push("Remote file `#{location}` could not be fetched because of HTTP error!")
+ rescue Gitlab::HTTP::BlockedUrlError => e
+ errors.push("Remote file could not be fetched because #{e}!")
+ end
+
+ if response&.code.to_i >= 400
+ errors.push("Remote file `#{location}` could not be fetched because of HTTP code `#{response.code}` error!")
+ end
+
+ response.to_s if errors.none?
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/mapper.rb b/lib/gitlab/ci/config/external/mapper.rb
new file mode 100644
index 00000000000..def3563e505
--- /dev/null
+++ b/lib/gitlab/ci/config/external/mapper.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ class Mapper
+ def initialize(values, project, sha)
+ @locations = Array(values.fetch(:include, []))
+ @project = project
+ @sha = sha
+ end
+
+ def process
+ locations.map { |location| build_external_file(location) }
+ end
+
+ private
+
+ attr_reader :locations, :project, :sha
+
+ def build_external_file(location)
+ if ::Gitlab::UrlSanitizer.valid?(location)
+ External::File::Remote.new(location)
+ else
+ External::File::Local.new(location, project: project, sha: sha)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/processor.rb b/lib/gitlab/ci/config/external/processor.rb
new file mode 100644
index 00000000000..eae0bdeb644
--- /dev/null
+++ b/lib/gitlab/ci/config/external/processor.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ class Processor
+ IncludeError = Class.new(StandardError)
+
+ def initialize(values, project, sha)
+ @values = values
+ @external_files = External::Mapper.new(values, project, sha).process
+ @content = {}
+ end
+
+ def perform
+ return @values if @external_files.empty?
+
+ validate_external_files!
+ merge_external_files!
+ append_inline_content!
+ remove_include_keyword!
+ end
+
+ private
+
+ def validate_external_files!
+ @external_files.each do |file|
+ raise IncludeError, file.error_message unless file.valid?
+ end
+ end
+
+ def merge_external_files!
+ @external_files.each do |file|
+ @content.deep_merge!(file.to_hash)
+ end
+ end
+
+ def append_inline_content!
+ @content.deep_merge!(@values)
+ end
+
+ def remove_include_keyword!
+ @content.tap { @content.delete(:include) }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/external/file/base.rb b/lib/gitlab/ci/external/file/base.rb
deleted file mode 100644
index f4da07b0b02..00000000000
--- a/lib/gitlab/ci/external/file/base.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module External
- module File
- class Base
- YAML_WHITELIST_EXTENSION = /(yml|yaml)$/i.freeze
-
- def initialize(location, opts = {})
- @location = location
- end
-
- def valid?
- location.match(YAML_WHITELIST_EXTENSION) && content
- end
-
- def content
- raise NotImplementedError, 'content must be implemented and return a string or nil'
- end
-
- def error_message
- raise NotImplementedError, 'error_message must be implemented and return a string'
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/external/file/local.rb b/lib/gitlab/ci/external/file/local.rb
deleted file mode 100644
index 1aa7f687507..00000000000
--- a/lib/gitlab/ci/external/file/local.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module External
- module File
- class Local < Base
- attr_reader :location, :project, :sha
-
- def initialize(location, opts = {})
- super
-
- @project = opts.fetch(:project)
- @sha = opts.fetch(:sha)
- end
-
- def content
- @content ||= fetch_local_content
- end
-
- def error_message
- "Local file '#{location}' is not valid."
- end
-
- private
-
- def fetch_local_content
- project.repository.blob_data_at(sha, location)
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/external/file/remote.rb b/lib/gitlab/ci/external/file/remote.rb
deleted file mode 100644
index 59bb3e8999e..00000000000
--- a/lib/gitlab/ci/external/file/remote.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module External
- module File
- class Remote < Base
- include Gitlab::Utils::StrongMemoize
- attr_reader :location
-
- def content
- return @content if defined?(@content)
-
- @content = strong_memoize(:content) do
- begin
- Gitlab::HTTP.get(location)
- rescue Gitlab::HTTP::Error, Timeout::Error, SocketError, Gitlab::HTTP::BlockedUrlError
- nil
- end
- end
- end
-
- def error_message
- "Remote file '#{location}' is not valid."
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/external/mapper.rb b/lib/gitlab/ci/external/mapper.rb
deleted file mode 100644
index 58bd6a19acf..00000000000
--- a/lib/gitlab/ci/external/mapper.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module External
- class Mapper
- def initialize(values, project, sha)
- @locations = Array(values.fetch(:include, []))
- @project = project
- @sha = sha
- end
-
- def process
- locations.map { |location| build_external_file(location) }
- end
-
- private
-
- attr_reader :locations, :project, :sha
-
- def build_external_file(location)
- if ::Gitlab::UrlSanitizer.valid?(location)
- Gitlab::Ci::External::File::Remote.new(location)
- else
- options = { project: project, sha: sha }
- Gitlab::Ci::External::File::Local.new(location, options)
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/external/processor.rb b/lib/gitlab/ci/external/processor.rb
deleted file mode 100644
index 76cf3ce89f9..00000000000
--- a/lib/gitlab/ci/external/processor.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module External
- class Processor
- FileError = Class.new(StandardError)
-
- def initialize(values, project, sha)
- @values = values
- @external_files = Gitlab::Ci::External::Mapper.new(values, project, sha).process
- @content = {}
- end
-
- def perform
- return values if external_files.empty?
-
- external_files.each do |external_file|
- validate_external_file(external_file)
- @content.deep_merge!(content_of(external_file))
- end
-
- append_inline_content
- remove_include_keyword
- end
-
- private
-
- attr_reader :values, :external_files, :content
-
- def validate_external_file(external_file)
- unless external_file.valid?
- raise FileError, external_file.error_message
- end
- end
-
- def content_of(external_file)
- Gitlab::Ci::Config::Loader.new(external_file.content).load!
- end
-
- def append_inline_content
- @content.deep_merge!(@values)
- end
-
- def remove_include_keyword
- content.delete(:include)
- content
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/namespace_sanitizer.rb b/lib/gitlab/namespace_sanitizer.rb
new file mode 100644
index 00000000000..d755bbbcaf9
--- /dev/null
+++ b/lib/gitlab/namespace_sanitizer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class NamespaceSanitizer
+ def self.sanitize(namespace)
+ namespace.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '')
+ end
+ end
+end
diff --git a/package.json b/package.json
index 8ec47bc2837..5b0a92ee7a1 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,7 @@
"karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js",
"karma-start": "BABEL_ENV=karma karma start config/karma.config.js",
"postinstall": "node ./scripts/frontend/postinstall.js",
- "prettier-staged": "node ./scripts/frontend/prettier.js",
+ "prettier-staged": "node ./scripts/frontend/prettier.js check",
"prettier-staged-save": "node ./scripts/frontend/prettier.js save",
"prettier-all": "node ./scripts/frontend/prettier.js check-all",
"prettier-all-save": "node ./scripts/frontend/prettier.js save-all",
@@ -29,7 +29,6 @@
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-loader": "^8.0.4",
- "blackst0ne-mermaid": "^7.1.0-fixed",
"bootstrap": "4.1.1",
"brace-expansion": "^1.1.8",
"cache-loader": "^1.2.2",
@@ -71,6 +70,7 @@
"jszip-utils": "^0.0.2",
"katex": "^0.9.0",
"marked": "^0.3.12",
+ "mermaid": "^8.0.0-rc.8",
"monaco-editor": "^0.14.3",
"monaco-editor-webpack-plugin": "^1.5.4",
"mousetrap": "^1.4.6",
@@ -126,7 +126,6 @@
"eslint-plugin-jasmine": "^2.10.1",
"gettext-extractor": "^3.3.2",
"gettext-extractor-vue": "^4.0.1",
- "ignore": "^3.3.7",
"istanbul": "^0.4.5",
"jasmine-core": "^2.9.0",
"jasmine-diff": "^0.1.3",
diff --git a/scripts/frontend/frontend_script_utils.js b/scripts/frontend/frontend_script_utils.js
index e42b912d359..e3d357b4a40 100644
--- a/scripts/frontend/frontend_script_utils.js
+++ b/scripts/frontend/frontend_script_utils.js
@@ -13,7 +13,8 @@ const execGitCmd = args =>
exec('git', args)
.trim()
.toString()
- .split('\n');
+ .split('\n')
+ .filter(Boolean);
module.exports = {
getStagedFiles: fileExtensionFilter => {
diff --git a/scripts/frontend/prettier.js b/scripts/frontend/prettier.js
index b66ba885701..ce86a9f4601 100644
--- a/scripts/frontend/prettier.js
+++ b/scripts/frontend/prettier.js
@@ -1,126 +1,116 @@
const glob = require('glob');
const prettier = require('prettier');
const fs = require('fs');
-const path = require('path');
-const prettierIgnore = require('ignore')();
+const { getStagedFiles } = require('./frontend_script_utils');
-const getStagedFiles = require('./frontend_script_utils').getStagedFiles;
+const matchExtensions = ['js', 'vue'];
+
+// This will improve glob performance by excluding certain directories.
+// The .prettierignore file will also be respected, but after the glob has executed.
+const globIgnore = ['**/node_modules/**', 'vendor/**', 'public/**'];
+
+const readFileAsync = (file, options) =>
+ new Promise((resolve, reject) => {
+ fs.readFile(file, options, function(err, data) {
+ if (err) reject(err);
+ else resolve(data);
+ });
+ });
+
+const writeFileAsync = (file, data, options) =>
+ new Promise((resolve, reject) => {
+ fs.writeFile(file, data, options, function(err) {
+ if (err) reject(err);
+ else resolve();
+ });
+ });
const mode = process.argv[2] || 'check';
const shouldSave = mode === 'save' || mode === 'save-all';
const allFiles = mode === 'check-all' || mode === 'save-all';
-let dirPath = process.argv[3] || '';
-if (dirPath && dirPath.charAt(dirPath.length - 1) !== '/') dirPath += '/';
-
-const config = {
- patterns: ['**/*.js', '**/*.vue', '**/*.scss'],
- /*
- * The ignore patterns below are just to reduce search time with glob, as it includes the
- * folders with the most ignored assets, the actual `.prettierignore` will be used later on
- */
- ignore: ['**/node_modules/**', '**/vendor/**', '**/public/**'],
- parsers: {
- js: 'babylon',
- vue: 'vue',
- scss: 'css',
- },
-};
+let globDir = process.argv[3] || '';
+if (globDir && globDir.charAt(globDir.length - 1) !== '/') globDir += '/';
-/*
- * Unfortunately the prettier API does not expose support for `.prettierignore` files, they however
- * use the ignore package, so we do the same. We simply cannot use the glob package, because
- * gitignore style is not compatible with globs ignore style.
- */
-prettierIgnore.add(
- fs
- .readFileSync(path.join(__dirname, '../../', '.prettierignore'))
- .toString()
- .trim()
- .split(/\r?\n/)
+console.log(
+ `Loading all ${allFiles ? '' : 'staged '}files ${globDir ? `within ${globDir} ` : ''}...`
);
-const availableExtensions = Object.keys(config.parsers);
-
-console.log(`Loading ${allFiles ? 'All' : 'Selected'} Files ...`);
+const globPatterns = matchExtensions.map(ext => `${globDir}**/*.${ext}`);
+const matchedFiles = allFiles
+ ? glob.sync(`{${globPatterns.join(',')}}`, { ignore: globIgnore })
+ : getStagedFiles(globPatterns);
+const matchedCount = matchedFiles.length;
-const stagedFiles =
- allFiles || dirPath ? null : getStagedFiles(availableExtensions.map(ext => `*.${ext}`));
-
-if (stagedFiles) {
- if (!stagedFiles.length || (stagedFiles.length === 1 && !stagedFiles[0])) {
- console.log('No matching staged files.');
- process.exit(1);
- }
- console.log(`Matching staged Files : ${stagedFiles.length}`);
+if (!matchedCount) {
+ console.log('No files found to process with prettier');
+ process.exit(0);
}
let didWarn = false;
-let didError = false;
-
-let files;
-if (allFiles) {
- const ignore = config.ignore;
- const patterns = config.patterns;
- const globPattern = patterns.length > 1 ? `{${patterns.join(',')}}` : `${patterns.join(',')}`;
- files = glob.sync(globPattern, { ignore }).filter(f => allFiles || stagedFiles.includes(f));
-} else if (dirPath) {
- const ignore = config.ignore;
- const patterns = config.patterns.map(item => {
- return dirPath + item;
- });
- const globPattern = patterns.length > 1 ? `{${patterns.join(',')}}` : `${patterns.join(',')}`;
- files = glob.sync(globPattern, { ignore });
-} else {
- files = stagedFiles.filter(f => availableExtensions.includes(f.split('.').pop()));
-}
-
-files = prettierIgnore.filter(files);
-
-if (!files.length) {
- console.log('No Files found to process with Prettier');
- process.exit(1);
-}
-
-console.log(`${shouldSave ? 'Updating' : 'Checking'} ${files.length} file(s)`);
-
-files.forEach(file => {
- try {
- prettier
- .resolveConfig(file)
- .then(options => {
- const fileExtension = file.split('.').pop();
- Object.assign(options, {
- parser: config.parsers[fileExtension],
+let passedCount = 0;
+let failedCount = 0;
+let ignoredCount = 0;
+
+console.log(`${shouldSave ? 'Updating' : 'Checking'} ${matchedCount} file(s)`);
+
+const fixCommand = `yarn prettier-${allFiles ? 'all' : 'staged'}-save`;
+const warningMessage = `
+===============================
+GitLab uses Prettier to format all JavaScript code.
+Please format each file listed below or run "${fixCommand}"
+===============================
+`;
+
+const checkFileWithOptions = (filePath, options) =>
+ readFileAsync(filePath, 'utf8').then(input => {
+ if (shouldSave) {
+ const output = prettier.format(input, options);
+ if (input === output) {
+ passedCount += 1;
+ } else {
+ return writeFileAsync(filePath, output, 'utf8').then(() => {
+ console.log(`Prettified : ${filePath}`);
+ failedCount += 1;
});
-
- const input = fs.readFileSync(file, 'utf8');
-
- if (shouldSave) {
- const output = prettier.format(input, options);
- if (output !== input) {
- fs.writeFileSync(file, output, 'utf8');
- console.log(`Prettified : ${file}`);
- }
- } else if (!prettier.check(input, options)) {
- if (!didWarn) {
- console.log(
- '\n===============================\nGitLab uses Prettier to format all JavaScript code.\nPlease format each file listed below or run "yarn prettier-staged-save"\n===============================\n'
- );
- didWarn = true;
- }
- console.log(`Prettify Manually : ${file}`);
+ }
+ } else {
+ if (prettier.check(input, options)) {
+ passedCount += 1;
+ } else {
+ if (!didWarn) {
+ console.log(warningMessage);
+ didWarn = true;
}
- })
- .catch(e => {
- console.log(`Error on loading the Config File: ${e.message}`);
- process.exit(1);
- });
- } catch (error) {
- didError = true;
- console.log(`\n\nError with ${file}: ${error.message}`);
- }
-});
+ console.log(`Prettify Manually : ${filePath}`);
+ failedCount += 1;
+ }
+ }
+ });
-if (didWarn || didError) {
- process.exit(1);
-}
+const checkFileWithPrettierConfig = filePath =>
+ prettier
+ .getFileInfo(filePath, { ignorePath: '.prettierignore' })
+ .then(({ ignored, inferredParser }) => {
+ if (ignored || !inferredParser) {
+ ignoredCount += 1;
+ return;
+ }
+ return prettier.resolveConfig(filePath).then(fileOptions => {
+ const options = { ...fileOptions, parser: inferredParser };
+ return checkFileWithOptions(filePath, options);
+ });
+ });
+
+Promise.all(matchedFiles.map(checkFileWithPrettierConfig))
+ .then(() => {
+ const failAction = shouldSave ? 'fixed' : 'failed';
+ console.log(
+ `\nSummary:\n ${matchedCount} files processed (${passedCount} passed, ${failedCount} ${failAction}, ${ignoredCount} ignored)\n`
+ );
+
+ if (didWarn) process.exit(1);
+ })
+ .catch(e => {
+ console.log(`\nAn error occured while processing files with prettier: ${e.message}\n`);
+ process.exit(1);
+ });
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb
index f564e7bee47..24e70913b87 100644
--- a/spec/factories/ci/runners.rb
+++ b/spec/factories/ci/runners.rb
@@ -47,5 +47,15 @@ FactoryBot.define do
trait :ref_protected do
access_level :ref_protected
end
+
+ trait :tagged_only do
+ run_untagged false
+
+ tag_list %w(tag1 tag2)
+ end
+
+ trait :locked do
+ locked true
+ end
end
end
diff --git a/spec/factories/clusters/kubernetes_namespaces.rb b/spec/factories/clusters/kubernetes_namespaces.rb
new file mode 100644
index 00000000000..6fdada75a3d
--- /dev/null
+++ b/spec/factories/clusters/kubernetes_namespaces.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :cluster_kubernetes_namespace, class: Clusters::KubernetesNamespace do
+ cluster
+ project
+ cluster_project
+ end
+end
diff --git a/spec/factories/clusters/projects.rb b/spec/factories/clusters/projects.rb
new file mode 100644
index 00000000000..6cda77c6f85
--- /dev/null
+++ b/spec/factories/clusters/projects.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :cluster_project, class: Clusters::Project do
+ cluster
+ project
+ end
+end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 491c64fc329..cd6c37bf54d 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -68,6 +68,10 @@ describe 'Pipeline', :js do
expect(page).to have_css('#js-tab-pipeline.active')
end
+ it 'shows link to the pipeline ref' do
+ expect(page).to have_link(pipeline.ref)
+ end
+
it_behaves_like 'showing user status' do
let(:user_with_status) { pipeline.user }
@@ -236,6 +240,20 @@ describe 'Pipeline', :js do
it { expect(page).not_to have_content('Cancel running') }
end
end
+
+ context 'when pipeline ref does not exist in repository anymore' do
+ let(:pipeline) do
+ create(:ci_empty_pipeline, project: project,
+ ref: 'non-existent',
+ sha: project.commit.id,
+ user: user)
+ end
+
+ it 'does not render link to the pipeline ref' do
+ expect(page).not_to have_link(pipeline.ref)
+ expect(page).to have_content(pipeline.ref)
+ end
+ end
end
context 'when user does not have access to read jobs' do
diff --git a/spec/javascripts/jobs/components/job_container_item_spec.js b/spec/javascripts/jobs/components/job_container_item_spec.js
new file mode 100644
index 00000000000..8588eda19c8
--- /dev/null
+++ b/spec/javascripts/jobs/components/job_container_item_spec.js
@@ -0,0 +1,73 @@
+import Vue from 'vue';
+import JobContainerItem from '~/jobs/components/job_container_item.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import job from '../mock_data';
+
+describe('JobContainerItem', () => {
+ const Component = Vue.extend(JobContainerItem);
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ const sharedTests = () => {
+ it('displays a status icon', () => {
+ expect(vm.$el).toHaveSpriteIcon(job.status.icon);
+ });
+
+ it('displays the job name', () => {
+ expect(vm.$el).toContainText(job.name);
+ });
+
+ it('displays a link to the job', () => {
+ const link = vm.$el.querySelector('.js-job-link');
+
+ expect(link.href).toBe(job.status.details_path);
+ });
+ };
+
+ describe('when a job is not active and not retied', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ job,
+ isActive: false,
+ });
+ });
+
+ sharedTests();
+ });
+
+ describe('when a job is active', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ job,
+ isActive: true,
+ });
+ });
+
+ sharedTests();
+
+ it('displays an arrow', () => {
+ expect(vm.$el).toHaveSpriteIcon('arrow-right');
+ });
+ });
+
+ describe('when a job is retried', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ job: {
+ ...job,
+ retried: true,
+ },
+ isActive: false,
+ });
+ });
+
+ sharedTests();
+
+ it('displays an icon', () => {
+ expect(vm.$el).toHaveSpriteIcon('retry');
+ });
+ });
+});
diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js
index ca6fbabeeb6..0398f184c0a 100644
--- a/spec/javascripts/jobs/mock_data.js
+++ b/spec/javascripts/jobs/mock_data.js
@@ -1,3 +1,5 @@
+import { TEST_HOST } from 'spec/test_constants';
+
const threeWeeksAgo = new Date();
threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);
@@ -19,7 +21,7 @@ export default {
label: 'passed',
group: 'success',
has_details: true,
- details_path: '/root/ci-mock/-/jobs/4757',
+ details_path: `${TEST_HOST}/root/ci-mock/-/jobs/4757`,
favicon:
'/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: {
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
index e327399d82d..a9a4af1f455 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
@@ -112,4 +112,34 @@ describe Gitlab::Ci::Build::Artifacts::Metadata do
end
end
end
+
+ context 'generated metadata' do
+ let(:tmpfile) { Tempfile.new('test-metadata') }
+ let(:generator) { CiArtifactMetadataGenerator.new(tmpfile) }
+ let(:entry_count) { 5 }
+
+ before do
+ tmpfile.binmode
+
+ (1..entry_count).each do |index|
+ generator.add_entry("public/test-#{index}.txt")
+ end
+
+ generator.write
+ end
+
+ after do
+ File.unlink(tmpfile.path)
+ end
+
+ describe '#find_entries!' do
+ it 'reads expected number of entries' do
+ stream = File.open(tmpfile.path)
+
+ metadata = described_class.new(stream, 'public', { recursive: true })
+
+ expect(metadata.find_entries!.count).to eq entry_count
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
new file mode 100644
index 00000000000..2e92d5204d6
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Ci::Config::External::File::Base do
+ subject { described_class.new(location) }
+
+ before do
+ allow_any_instance_of(described_class)
+ .to receive(:content).and_return('key: value')
+ end
+
+ describe '#valid?' do
+ context 'when location is not a YAML file' do
+ let(:location) { 'some/file.txt' }
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when location has not a valid naming scheme' do
+ let(:location) { 'some/file/.yml' }
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when location is a valid .yml extension' do
+ let(:location) { 'some/file/config.yml' }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when location is a valid .yaml extension' do
+ let(:location) { 'some/file/config.yaml' }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when there are YAML syntax errors' do
+ let(:location) { 'some/file/config.yml' }
+
+ before do
+ allow_any_instance_of(described_class)
+ .to receive(:content).and_return('invalid_syntax')
+ end
+
+ it 'is not a valid file' do
+ expect(subject).not_to be_valid
+ expect(subject.error_message).to match /does not have valid YAML syntax/
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
index 73bb4ccf468..2708d8d5b6b 100644
--- a/spec/lib/gitlab/ci/external/file/local_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::External::File::Local do
+describe Gitlab::Ci::Config::External::File::Local do
let(:project) { create(:project, :repository) }
let(:local_file) { described_class.new(location, { project: project, sha: '12345' }) }
@@ -72,7 +72,7 @@ describe Gitlab::Ci::External::File::Local do
let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
it 'should return an error message' do
- expect(local_file.error_message).to eq("Local file '#{location}' is not valid.")
+ expect(local_file.error_message).to eq("Local file `#{location}` does not exist!")
end
end
end
diff --git a/spec/lib/gitlab/ci/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
index b1819c8960b..7c1a1c38736 100644
--- a/spec/lib/gitlab/ci/external/file/remote_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::External::File::Remote do
+describe Gitlab::Ci::Config::External::File::Remote do
let(:remote_file) { described_class.new(location) }
let(:location) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
let(:remote_file_content) do
@@ -105,10 +105,53 @@ describe Gitlab::Ci::External::File::Remote do
end
describe "#error_message" do
- let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ subject { remote_file.error_message }
- it 'should return an error message' do
- expect(remote_file.error_message).to eq("Remote file '#{location}' is not valid.")
+ context 'when remote file location is not valid' do
+ let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+
+ it 'returns an error message describing invalid address' do
+ expect(subject).to match /does not have a valid address!/
+ end
+ end
+
+ context 'when timeout error has been raised' do
+ before do
+ WebMock.stub_request(:get, location).to_timeout
+ end
+
+ it 'should returns error message about a timeout' do
+ expect(subject).to match /could not be fetched because of a timeout error!/
+ end
+ end
+
+ context 'when HTTP error has been raised' do
+ before do
+ WebMock.stub_request(:get, location).to_raise(Gitlab::HTTP::Error)
+ end
+
+ it 'should returns error message about a HTTP error' do
+ expect(subject).to match /could not be fetched because of HTTP error!/
+ end
+ end
+
+ context 'when response has 404 status' do
+ before do
+ WebMock.stub_request(:get, location).to_return(body: remote_file_content, status: 404)
+ end
+
+ it 'should returns error message about a timeout' do
+ expect(subject).to match /could not be fetched because of HTTP code `404` error!/
+ end
+ end
+
+ context 'when the URL is blocked' do
+ let(:location) { 'http://127.0.0.1/some/path/to/config.yaml' }
+
+ it 'should include details about blocked URL' do
+ expect(subject).to eq "Remote file could not be fetched because URL '#{location}' " \
+ 'is blocked: Requests to localhost are not allowed!'
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index d925d6af73d..5b236fe99f1 100644
--- a/spec/lib/gitlab/ci/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::External::Mapper do
+describe Gitlab::Ci::Config::External::Mapper do
let(:project) { create(:project, :repository) }
let(:file_content) do
<<~HEREDOC
@@ -27,7 +27,8 @@ describe Gitlab::Ci::External::Mapper do
end
it 'returns File instances' do
- expect(subject.first).to be_an_instance_of(Gitlab::Ci::External::File::Local)
+ expect(subject.first)
+ .to be_an_instance_of(Gitlab::Ci::Config::External::File::Local)
end
end
@@ -49,7 +50,8 @@ describe Gitlab::Ci::External::Mapper do
end
it 'returns File instances' do
- expect(subject.first).to be_an_instance_of(Gitlab::Ci::External::File::Remote)
+ expect(subject.first)
+ .to be_an_instance_of(Gitlab::Ci::Config::External::File::Remote)
end
end
end
diff --git a/spec/lib/gitlab/ci/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index 3c7394f53d2..1a05f716247 100644
--- a/spec/lib/gitlab/ci/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Ci::External::Processor do
+describe Gitlab::Ci::Config::External::Processor do
let(:project) { create(:project, :repository) }
let(:processor) { described_class.new(values, project, '12345') }
@@ -20,8 +20,8 @@ describe Gitlab::Ci::External::Processor do
it 'should raise an error' do
expect { processor.perform }.to raise_error(
- described_class::FileError,
- "Local file '/lib/gitlab/ci/templates/non-existent-file.yml' is not valid."
+ described_class::IncludeError,
+ "Local file `/lib/gitlab/ci/templates/non-existent-file.yml` does not exist!"
)
end
end
@@ -36,8 +36,8 @@ describe Gitlab::Ci::External::Processor do
it 'should raise an error' do
expect { processor.perform }.to raise_error(
- described_class::FileError,
- "Remote file '#{remote_file}' is not valid."
+ described_class::IncludeError,
+ "Remote file `#{remote_file}` could not be fetched because of a socket error!"
)
end
end
@@ -92,7 +92,8 @@ describe Gitlab::Ci::External::Processor do
end
before do
- allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content)
+ allow_any_instance_of(Gitlab::Ci::Config::External::File::Local)
+ .to receive(:fetch_local_content).and_return(local_file_content)
end
it 'should append the file to the values' do
@@ -131,7 +132,10 @@ describe Gitlab::Ci::External::Processor do
before do
local_file_content = File.read(Rails.root.join('spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml'))
- allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content)
+
+ allow_any_instance_of(Gitlab::Ci::Config::External::File::Local)
+ .to receive(:fetch_local_content).and_return(local_file_content)
+
WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content)
end
@@ -150,11 +154,15 @@ describe Gitlab::Ci::External::Processor do
let(:local_file_content) { 'invalid content file ////' }
before do
- allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content)
+ allow_any_instance_of(Gitlab::Ci::Config::External::File::Local)
+ .to receive(:fetch_local_content).and_return(local_file_content)
end
it 'should raise an error' do
- expect { processor.perform }.to raise_error(Gitlab::Ci::Config::Loader::FormatError)
+ expect { processor.perform }.to raise_error(
+ described_class::IncludeError,
+ "Included file `/lib/gitlab/ci/templates/template.yml` does not have valid YAML syntax!"
+ )
end
end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index b43aca8a354..975e11e8cc1 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -1,6 +1,4 @@
-require 'fast_spec_helper'
-
-require_dependency 'active_model'
+require 'spec_helper'
describe Gitlab::Ci::Config do
let(:config) do
@@ -202,8 +200,8 @@ describe Gitlab::Ci::Config do
it 'raises error YamlProcessor validationError' do
expect { config }.to raise_error(
- ::Gitlab::Ci::YamlProcessor::ValidationError,
- "Local file 'invalid' is not valid."
+ described_class::ConfigError,
+ "Included file `invalid` does not have YAML extension!"
)
end
end
diff --git a/spec/migrations/rename_more_reserved_project_names_spec.rb b/spec/migrations/rename_more_reserved_project_names_spec.rb
index 034e8a6a4e5..baf16c2ce53 100644
--- a/spec/migrations/rename_more_reserved_project_names_spec.rb
+++ b/spec/migrations/rename_more_reserved_project_names_spec.rb
@@ -31,7 +31,16 @@ describe RenameMoreReservedProjectNames, :delete do
context 'when exception is raised during rename' do
before do
- allow(project).to receive(:rename_repo).and_raise(StandardError)
+ service = instance_double('service')
+
+ allow(service)
+ .to receive(:execute)
+ .and_raise(Projects::AfterRenameService::RenameFailedError)
+
+ allow(Projects::AfterRenameService)
+ .to receive(:new)
+ .with(project)
+ .and_return(service)
end
it 'captures exception from project rename' do
diff --git a/spec/migrations/rename_reserved_project_names_spec.rb b/spec/migrations/rename_reserved_project_names_spec.rb
index 592ac2b5fb9..7818aa0d560 100644
--- a/spec/migrations/rename_reserved_project_names_spec.rb
+++ b/spec/migrations/rename_reserved_project_names_spec.rb
@@ -35,7 +35,16 @@ describe RenameReservedProjectNames, :migration, schema: :latest do
context 'when exception is raised during rename' do
before do
- allow(project).to receive(:rename_repo).and_raise(StandardError)
+ service = instance_double('service')
+
+ allow(service)
+ .to receive(:execute)
+ .and_raise(Projects::AfterRenameService::RenameFailedError)
+
+ allow(Projects::AfterRenameService)
+ .to receive(:new)
+ .with(project)
+ .and_return(service)
end
it 'captures exception from project rename' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 3b01b39ecab..153244b2159 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -779,6 +779,41 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe 'ref_exists?' do
+ context 'when repository exists' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:project) { create(:project, :repository) }
+
+ where(:tag, :ref, :result) do
+ false | 'master' | true
+ false | 'non-existent-branch' | false
+ true | 'v1.1.0' | true
+ true | 'non-existent-tag' | false
+ end
+
+ with_them do
+ let(:pipeline) do
+ create(:ci_empty_pipeline, project: project, tag: tag, ref: ref)
+ end
+
+ it "correctly detects ref" do
+ expect(pipeline.ref_exists?).to be result
+ end
+ end
+ end
+
+ context 'when repository does not exist' do
+ let(:pipeline) do
+ create(:ci_empty_pipeline, project: project, ref: 'master')
+ end
+
+ it 'always returns false' do
+ expect(pipeline.ref_exists?).to eq false
+ end
+ end
+ end
+
context 'with non-empty project' do
let(:project) { create(:project, :repository) }
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 34d321ec604..f5c4b0b66ae 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -11,6 +11,9 @@ describe Clusters::Cluster do
it { is_expected.to have_one(:application_ingress) }
it { is_expected.to have_one(:application_prometheus) }
it { is_expected.to have_one(:application_runner) }
+ it { is_expected.to have_many(:kubernetes_namespaces) }
+ it { is_expected.to have_one(:kubernetes_namespace) }
+
it { is_expected.to delegate_method(:status).to(:provider) }
it { is_expected.to delegate_method(:status_reason).to(:provider) }
it { is_expected.to delegate_method(:status_name).to(:provider) }
@@ -20,6 +23,7 @@ describe Clusters::Cluster do
it { is_expected.to delegate_method(:available?).to(:application_helm).with_prefix }
it { is_expected.to delegate_method(:available?).to(:application_ingress).with_prefix }
it { is_expected.to delegate_method(:available?).to(:application_prometheus).with_prefix }
+
it { is_expected.to respond_to :project }
describe '.enabled' do
diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb
new file mode 100644
index 00000000000..dea58fa26c7
--- /dev/null
+++ b/spec/models/clusters/kubernetes_namespace_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Clusters::KubernetesNamespace, type: :model do
+ it { is_expected.to belong_to(:cluster_project) }
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:cluster) }
+ it { is_expected.to have_one(:platform_kubernetes) }
+
+ describe 'namespace uniqueness validation' do
+ let(:cluster_project) { create(:cluster_project) }
+
+ let(:kubernetes_namespace) do
+ build(:cluster_kubernetes_namespace,
+ cluster: cluster_project.cluster,
+ project: cluster_project.project,
+ cluster_project: cluster_project)
+ end
+
+ subject { kubernetes_namespace }
+
+ context 'when cluster is using the namespace' do
+ before do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster_project.cluster,
+ project: cluster_project.project,
+ cluster_project: cluster_project,
+ namespace: kubernetes_namespace.namespace)
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when cluster is not using the namespace' do
+ it { is_expected.to be_valid }
+ end
+ end
+
+ describe '#set_namespace_and_service_account_to_default' do
+ let(:cluster) { platform.cluster }
+ let(:cluster_project) { create(:cluster_project, cluster: cluster) }
+ let(:kubernetes_namespace) do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster_project.cluster,
+ project: cluster_project.project,
+ cluster_project: cluster_project)
+ end
+
+ describe 'namespace' do
+ let(:platform) { create(:cluster_platform_kubernetes, namespace: namespace) }
+
+ subject { kubernetes_namespace.namespace }
+
+ context 'when platform has a namespace assigned' do
+ let(:namespace) { 'platform-namespace' }
+
+ it 'should copy the namespace' do
+ is_expected.to eq('platform-namespace')
+ end
+ end
+
+ context 'when platform does not have namespace assigned' do
+ let(:namespace) { nil }
+
+ it 'should set default namespace' do
+ project_slug = "#{cluster_project.project.path}-#{cluster_project.project_id}"
+
+ is_expected.to eq(project_slug)
+ end
+ end
+ end
+
+ describe 'service_account_name' do
+ let(:platform) { create(:cluster_platform_kubernetes) }
+
+ subject { kubernetes_namespace.service_account_name }
+
+ it 'should set a service account name based on namespace' do
+ is_expected.to eq("#{kubernetes_namespace.namespace}-service-account")
+ end
+ end
+ end
+end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index 66198d5ee2b..e13eb554add 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -9,6 +9,15 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { is_expected.to be_kind_of(ReactiveCaching) }
it { is_expected.to respond_to :ca_pem }
+ it { is_expected.to validate_exclusion_of(:namespace).in_array(%w(gitlab-managed-apps)) }
+ it { is_expected.to validate_presence_of(:api_url) }
+ it { is_expected.to validate_presence_of(:token) }
+
+ it { is_expected.to delegate_method(:project).to(:cluster) }
+ it { is_expected.to delegate_method(:enabled?).to(:cluster) }
+ it { is_expected.to delegate_method(:managed?).to(:cluster) }
+ it { is_expected.to delegate_method(:kubernetes_namespace).to(:cluster) }
+
describe 'before_validation' do
context 'when namespace includes upper case' do
let(:kubernetes) { create(:cluster_platform_kubernetes, :configured, namespace: namespace) }
@@ -90,6 +99,28 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { expect(kubernetes.save).to be_falsey }
end
end
+
+ describe 'when using reserved namespaces' do
+ subject { build(:cluster_platform_kubernetes, namespace: namespace) }
+
+ context 'when no namespace is manually assigned' do
+ let(:namespace) { nil }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when no reserved namespace is assigned' do
+ let(:namespace) { 'my-namespace' }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when reserved namespace is assigned' do
+ let(:namespace) { 'gitlab-managed-apps' }
+
+ it { is_expected.not_to be_valid }
+ end
+ end
end
describe '#kubeclient' do
@@ -117,41 +148,39 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
end
describe '#actual_namespace' do
- subject { kubernetes.actual_namespace }
-
- let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
+ let(:cluster) { create(:cluster, :project) }
let(:project) { cluster.project }
- let(:kubernetes) { create(:cluster_platform_kubernetes, :configured, namespace: namespace) }
- context 'when namespace is present' do
+ let(:platform) do
+ create(:cluster_platform_kubernetes,
+ cluster: cluster,
+ namespace: namespace)
+ end
+
+ subject { platform.actual_namespace }
+
+ context 'with a namespace assigned' do
let(:namespace) { 'namespace-123' }
it { is_expected.to eq(namespace) }
end
- context 'when namespace is not present' do
+ context 'with no namespace assigned' do
let(:namespace) { nil }
- it { is_expected.to eq("#{project.path}-#{project.id}") }
- end
- end
-
- describe '#default_namespace' do
- subject { kubernetes.send(:default_namespace) }
+ context 'when kubernetes namespace is present' do
+ let(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) }
- let(:kubernetes) { create(:cluster_platform_kubernetes, :configured) }
+ before do
+ kubernetes_namespace
+ end
- context 'when cluster belongs to a project' do
- let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
- let(:project) { cluster.project }
-
- it { is_expected.to eq("#{project.path}-#{project.id}") }
- end
-
- context 'when cluster belongs to nothing' do
- let!(:cluster) { create(:cluster, platform_kubernetes: kubernetes) }
+ it { is_expected.to eq(kubernetes_namespace.namespace) }
+ end
- it { is_expected.to be_nil }
+ context 'when kubernetes namespace is not present' do
+ it { is_expected.to eq("#{project.path}-#{project.id}") }
+ end
end
end
diff --git a/spec/models/clusters/project_spec.rb b/spec/models/clusters/project_spec.rb
index 7d75d6ab345..82ef5a23c18 100644
--- a/spec/models/clusters/project_spec.rb
+++ b/spec/models/clusters/project_spec.rb
@@ -3,4 +3,6 @@ require 'spec_helper'
describe Clusters::Project do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_many(:kubernetes_namespaces) }
+ it { is_expected.to have_one(:kubernetes_namespace) }
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index b42c7c6e62d..62a38c66d99 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2965,88 +2965,6 @@ describe Project do
end
end
- describe '#rename_repo' do
- before do
- # Project#gitlab_shell returns a new instance of Gitlab::Shell on every
- # call. This makes testing a bit easier.
- allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
- allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
- stub_feature_flags(skip_hashed_storage_upgrade: false)
- end
-
- it 'renames a repository' do
- stub_container_registry_config(enabled: false)
-
- expect(gitlab_shell).to receive(:mv_repository)
- .ordered
- .with(project.repository_storage, "#{project.namespace.full_path}/foo", "#{project.full_path}")
- .and_return(true)
-
- expect(gitlab_shell).to receive(:mv_repository)
- .ordered
- .with(project.repository_storage, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki")
- .and_return(true)
-
- expect_any_instance_of(SystemHooksService)
- .to receive(:execute_hooks_for)
- .with(project, :rename)
-
- expect_any_instance_of(Gitlab::UploadsTransfer)
- .to receive(:rename_project)
- .with('foo', project.path, project.namespace.full_path)
-
- expect(project).to receive(:expire_caches_before_rename)
-
- project.rename_repo
- end
-
- context 'container registry with images' do
- let(:container_repository) { create(:container_repository) }
-
- before do
- stub_container_registry_config(enabled: true)
- stub_container_registry_tags(repository: :any, tags: ['tag'])
- project.container_repositories << container_repository
- end
-
- subject { project.rename_repo }
-
- it { expect { subject }.to raise_error(StandardError) }
- end
-
- context 'gitlab pages' do
- before do
- expect(project_storage).to receive(:rename_repo) { true }
- end
-
- it 'moves pages folder to new location' do
- expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project)
-
- project.rename_repo
- end
- end
-
- context 'attachments' do
- before do
- expect(project_storage).to receive(:rename_repo) { true }
- end
-
- it 'moves uploads folder to new location' do
- expect_any_instance_of(Gitlab::UploadsTransfer).to receive(:rename_project)
-
- project.rename_repo
- end
- end
-
- it 'updates project full path in .git/config' do
- allow(project_storage).to receive(:rename_repo).and_return(true)
-
- project.rename_repo
-
- expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
- end
- end
-
describe '#pages_path' do
it 'returns a path where pages are stored' do
expect(project.pages_path).to eq(File.join(Settings.pages.path, project.namespace.full_path, project.path))
@@ -3137,91 +3055,6 @@ describe Project do
end
end
- describe '#rename_repo' do
- before do
- # Project#gitlab_shell returns a new instance of Gitlab::Shell on every
- # call. This makes testing a bit easier.
- allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
- allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
- stub_feature_flags(skip_hashed_storage_upgrade: false)
- end
-
- context 'migration to hashed storage' do
- it 'calls HashedStorageMigrationService with correct options' do
- project = create(:project, :repository, :legacy_storage)
- allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
-
- expect_next_instance_of(::Projects::HashedStorageMigrationService) do |service|
- expect(service).to receive(:execute).and_return(true)
- end
-
- project.rename_repo
- end
- end
-
- it 'renames a repository' do
- stub_container_registry_config(enabled: false)
-
- expect(gitlab_shell).not_to receive(:mv_repository)
-
- expect_any_instance_of(SystemHooksService)
- .to receive(:execute_hooks_for)
- .with(project, :rename)
-
- expect(project).to receive(:expire_caches_before_rename)
-
- project.rename_repo
- end
-
- context 'container registry with images' do
- let(:container_repository) { create(:container_repository) }
-
- before do
- stub_container_registry_config(enabled: true)
- stub_container_registry_tags(repository: :any, tags: ['tag'])
- project.container_repositories << container_repository
- end
-
- subject { project.rename_repo }
-
- it { expect { subject }.to raise_error(StandardError) }
- end
-
- context 'gitlab pages' do
- it 'moves pages folder to new location' do
- expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project)
-
- project.rename_repo
- end
- end
-
- context 'attachments' do
- it 'keeps uploads folder location unchanged' do
- expect_any_instance_of(Gitlab::UploadsTransfer).not_to receive(:rename_project)
-
- project.rename_repo
- end
-
- context 'when not rolled out' do
- let(:project) { create(:project, :repository, storage_version: 1, skip_disk_validation: true) }
-
- it 'moves pages folder to hashed storage' do
- expect_next_instance_of(Projects::HashedStorage::MigrateAttachmentsService) do |service|
- expect(service).to receive(:execute)
- end
-
- project.rename_repo
- end
- end
- end
-
- it 'updates project full path in .git/config' do
- project.rename_repo
-
- expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
- end
- end
-
describe '#pages_path' do
it 'returns a path where pages are stored' do
expect(project.pages_path).to eq(File.join(Settings.pages.path, project.namespace.full_path, project.path))
diff --git a/spec/services/clusters/create_service_spec.rb b/spec/services/clusters/create_service_spec.rb
index 5373ad7f4b2..3959295c13e 100644
--- a/spec/services/clusters/create_service_spec.rb
+++ b/spec/services/clusters/create_service_spec.rb
@@ -2,104 +2,33 @@ require 'spec_helper'
describe Clusters::CreateService do
let(:access_token) { 'xxx' }
+ let(:project) { create(:project) }
let(:user) { create(:user) }
- let(:service) { described_class.new(user, params) }
- describe '#execute' do
- before do
- allow(ClusterProvisionWorker).to receive(:perform_async)
- end
+ subject { described_class.new(user, params).execute(project: project, access_token: access_token) }
- shared_context 'valid cluster create params' do
- let(:params) do
- {
- name: 'test-cluster',
- provider_type: :gcp,
- provider_gcp_attributes: {
- gcp_project_id: 'gcp-project',
- zone: 'us-central1-a',
- num_nodes: 1,
- machine_type: 'machine_type-a',
- legacy_abac: 'true'
- }
- }
- end
- end
+ context 'when provider is gcp' do
+ context 'when project has no clusters' do
+ context 'when correct params' do
+ include_context 'valid cluster create params'
- shared_context 'invalid cluster create params' do
- let(:params) do
- {
- name: 'test-cluster',
- provider_type: :gcp,
- provider_gcp_attributes: {
- gcp_project_id: '!!!!!!!',
- zone: 'us-central1-a',
- num_nodes: 1,
- machine_type: 'machine_type-a'
- }
- }
+ include_examples 'create cluster service success'
end
- end
- shared_examples 'create cluster service success' do
- it 'creates a cluster object and performs a worker' do
- expect(ClusterProvisionWorker).to receive(:perform_async)
+ context 'when invalid params' do
+ include_context 'invalid cluster create params'
- expect { subject }
- .to change { Clusters::Cluster.count }.by(1)
- .and change { Clusters::Providers::Gcp.count }.by(1)
-
- expect(subject.name).to eq('test-cluster')
- expect(subject.user).to eq(user)
- expect(subject.provider.gcp_project_id).to eq('gcp-project')
- expect(subject.provider.zone).to eq('us-central1-a')
- expect(subject.provider.num_nodes).to eq(1)
- expect(subject.provider.machine_type).to eq('machine_type-a')
- expect(subject.provider.access_token).to eq(access_token)
- expect(subject.provider).to be_legacy_abac
- expect(subject.platform).to be_nil
- end
- end
-
- shared_examples 'create cluster service error' do
- it 'returns an error' do
- expect(ClusterProvisionWorker).not_to receive(:perform_async)
- expect { subject }.to change { Clusters::Cluster.count }.by(0)
- expect(subject.errors[:"provider_gcp.gcp_project_id"]).to be_present
+ include_examples 'create cluster service error'
end
end
- context 'create cluster for project' do
- let(:project) { create(:project) }
+ context 'when project has a cluster' do
+ include_context 'valid cluster create params'
+ let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
- subject { service.execute(project: project, access_token: access_token) }
-
- context 'when project has no clusters' do
- context 'when correct params' do
- include_context 'valid cluster create params'
-
- include_examples 'create cluster service success'
-
- it 'associates project to the cluster' do
- expect(subject.project).to eq(project)
- end
- end
-
- context 'when invalid params' do
- include_context 'invalid cluster create params'
-
- include_examples 'create cluster service error'
- end
- end
-
- context 'when project has a cluster' do
- include_context 'valid cluster create params'
- let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
-
- it 'does not create a cluster' do
- expect(ClusterProvisionWorker).not_to receive(:perform_async)
- expect { subject }.to raise_error(ArgumentError).and change { Clusters::Cluster.count }.by(0)
- end
+ it 'does not create a cluster' do
+ expect(ClusterProvisionWorker).not_to receive(:perform_async)
+ expect { subject }.to raise_error(ArgumentError).and change { Clusters::Cluster.count }.by(0)
end
end
end
diff --git a/spec/services/projects/after_rename_service_spec.rb b/spec/services/projects/after_rename_service_spec.rb
new file mode 100644
index 00000000000..b4718a07204
--- /dev/null
+++ b/spec/services/projects/after_rename_service_spec.rb
@@ -0,0 +1,198 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::AfterRenameService do
+ let(:rugged_config) { rugged_repo(project.repository).config }
+
+ describe '#execute' do
+ context 'using legacy storage' do
+ let(:project) { create(:project, :repository, :legacy_storage) }
+ let(:gitlab_shell) { Gitlab::Shell.new }
+ let(:project_storage) { project.send(:storage) }
+
+ before do
+ # Project#gitlab_shell returns a new instance of Gitlab::Shell on every
+ # call. This makes testing a bit easier.
+ allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
+
+ allow(project)
+ .to receive(:previous_changes)
+ .and_return('path' => ['foo'])
+
+ allow(project)
+ .to receive(:path_was)
+ .and_return('foo')
+
+ stub_feature_flags(skip_hashed_storage_upgrade: false)
+ end
+
+ it 'renames a repository' do
+ stub_container_registry_config(enabled: false)
+
+ expect(gitlab_shell).to receive(:mv_repository)
+ .ordered
+ .with(project.repository_storage, "#{project.namespace.full_path}/foo", "#{project.full_path}")
+ .and_return(true)
+
+ expect(gitlab_shell).to receive(:mv_repository)
+ .ordered
+ .with(project.repository_storage, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki")
+ .and_return(true)
+
+ expect_any_instance_of(SystemHooksService)
+ .to receive(:execute_hooks_for)
+ .with(project, :rename)
+
+ expect_any_instance_of(Gitlab::UploadsTransfer)
+ .to receive(:rename_project)
+ .with('foo', project.path, project.namespace.full_path)
+
+ expect(project).to receive(:expire_caches_before_rename)
+
+ described_class.new(project).execute
+ end
+
+ context 'container registry with images' do
+ let(:container_repository) { create(:container_repository) }
+
+ before do
+ stub_container_registry_config(enabled: true)
+ stub_container_registry_tags(repository: :any, tags: ['tag'])
+ project.container_repositories << container_repository
+ end
+
+ it 'raises a RenameFailedError' do
+ expect { described_class.new(project).execute }
+ .to raise_error(described_class::RenameFailedError)
+ end
+ end
+
+ context 'gitlab pages' do
+ before do
+ expect(project_storage).to receive(:rename_repo) { true }
+ end
+
+ it 'moves pages folder to new location' do
+ expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project)
+
+ described_class.new(project).execute
+ end
+ end
+
+ context 'attachments' do
+ before do
+ expect(project_storage).to receive(:rename_repo) { true }
+ end
+
+ it 'moves uploads folder to new location' do
+ expect_any_instance_of(Gitlab::UploadsTransfer).to receive(:rename_project)
+
+ described_class.new(project).execute
+ end
+ end
+
+ it 'updates project full path in .git/config' do
+ allow(project_storage).to receive(:rename_repo).and_return(true)
+
+ described_class.new(project).execute
+
+ expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
+ end
+ end
+
+ context 'using hashed storage' do
+ let(:project) { create(:project, :repository, skip_disk_validation: true) }
+ let(:gitlab_shell) { Gitlab::Shell.new }
+ let(:hash) { Digest::SHA2.hexdigest(project.id.to_s) }
+ let(:hashed_prefix) { File.join('@hashed', hash[0..1], hash[2..3]) }
+ let(:hashed_path) { File.join(hashed_prefix, hash) }
+
+ before do
+ # Project#gitlab_shell returns a new instance of Gitlab::Shell on every
+ # call. This makes testing a bit easier.
+ allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
+ allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
+
+ stub_feature_flags(skip_hashed_storage_upgrade: false)
+ stub_application_setting(hashed_storage_enabled: true)
+ end
+
+ context 'migration to hashed storage' do
+ it 'calls HashedStorageMigrationService with correct options' do
+ project = create(:project, :repository, :legacy_storage)
+ allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
+
+ expect_next_instance_of(::Projects::HashedStorageMigrationService) do |service|
+ expect(service).to receive(:execute).and_return(true)
+ end
+
+ described_class.new(project).execute
+ end
+ end
+
+ it 'renames a repository' do
+ stub_container_registry_config(enabled: false)
+
+ expect(gitlab_shell).not_to receive(:mv_repository)
+
+ expect_any_instance_of(SystemHooksService)
+ .to receive(:execute_hooks_for)
+ .with(project, :rename)
+
+ expect(project).to receive(:expire_caches_before_rename)
+
+ described_class.new(project).execute
+ end
+
+ context 'container registry with images' do
+ let(:container_repository) { create(:container_repository) }
+
+ before do
+ stub_container_registry_config(enabled: true)
+ stub_container_registry_tags(repository: :any, tags: ['tag'])
+ project.container_repositories << container_repository
+ end
+
+ it 'raises a RenameFailedError' do
+ expect { described_class.new(project).execute }
+ .to raise_error(described_class::RenameFailedError)
+ end
+ end
+
+ context 'gitlab pages' do
+ it 'moves pages folder to new location' do
+ expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project)
+
+ described_class.new(project).execute
+ end
+ end
+
+ context 'attachments' do
+ it 'keeps uploads folder location unchanged' do
+ expect_any_instance_of(Gitlab::UploadsTransfer).not_to receive(:rename_project)
+
+ described_class.new(project).execute
+ end
+
+ context 'when not rolled out' do
+ let(:project) { create(:project, :repository, storage_version: 1, skip_disk_validation: true) }
+
+ it 'moves pages folder to hashed storage' do
+ expect_next_instance_of(Projects::HashedStorage::MigrateAttachmentsService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ described_class.new(project).execute
+ end
+ end
+ end
+
+ it 'updates project full path in .git/config' do
+ described_class.new(project).execute
+
+ expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/ci_artifact_metadata_generator.rb b/spec/support/helpers/ci_artifact_metadata_generator.rb
new file mode 100644
index 00000000000..ef638d59d2d
--- /dev/null
+++ b/spec/support/helpers/ci_artifact_metadata_generator.rb
@@ -0,0 +1,48 @@
+# frozen_sting_literal: true
+
+# This generates fake CI metadata .gz for testing
+# Based off https://gitlab.com/gitlab-org/gitlab-workhorse/blob/master/internal/zipartifacts/metadata.go
+class CiArtifactMetadataGenerator
+ attr_accessor :entries, :output
+
+ ARTIFACT_METADATA = "GitLab Build Artifacts Metadata 0.0.2\n".freeze
+
+ def initialize(stream)
+ @entries = {}
+ @output = Zlib::GzipWriter.new(stream)
+ end
+
+ def add_entry(filename)
+ @entries[filename] = { CRC: rand(0xfffffff), Comment: FFaker::Lorem.sentence(10) }
+ end
+
+ def write
+ write_version
+ write_errors
+ write_entries
+ output.close
+ end
+
+ private
+
+ def write_version
+ write_string(ARTIFACT_METADATA)
+ end
+
+ def write_errors
+ write_string('{}')
+ end
+
+ def write_entries
+ entries.each do |filename, metadata|
+ write_string(filename)
+ write_string(metadata.to_json + "\n")
+ end
+ end
+
+ def write_string(data)
+ bytes = [data.length].pack('L>')
+ output.write(bytes)
+ output.write(data)
+ end
+end
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index c077ca9f15b..a03d9c4045f 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -33,10 +33,11 @@ module KubernetesHelpers
WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response)
end
- def stub_kubeclient_get_secret(api_url, namespace: 'default', **options)
+ def stub_kubeclient_get_secret(api_url, **options)
options[:metadata_name] ||= "default-token-1"
+ options[:namespace] ||= "default"
- WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}/secrets/#{options[:metadata_name]}")
+ WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{options[:namespace]}/secrets/#{options[:metadata_name]}")
.to_return(kube_response(kube_v1_secret_body(options)))
end
@@ -65,6 +66,21 @@ module KubernetesHelpers
.to_return(kube_response({}))
end
+ def stub_kubeclient_create_role_binding(api_url, namespace: 'default')
+ WebMock.stub_request(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings")
+ .to_return(kube_response({}))
+ end
+
+ def stub_kubeclient_create_namespace(api_url)
+ WebMock.stub_request(:post, api_url + "/api/v1/namespaces")
+ .to_return(kube_response({}))
+ end
+
+ def stub_kubeclient_get_namespace(api_url, namespace: 'default')
+ WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}")
+ .to_return(kube_response({}))
+ end
+
def kube_v1_secret_body(**options)
{
"kind" => "SecretList",
@@ -87,7 +103,8 @@ module KubernetesHelpers
{ "name" => "deployments", "namespaced" => true, "kind" => "Deployment" },
{ "name" => "secrets", "namespaced" => true, "kind" => "Secret" },
{ "name" => "serviceaccounts", "namespaced" => true, "kind" => "ServiceAccount" },
- { "name" => "services", "namespaced" => true, "kind" => "Service" }
+ { "name" => "services", "namespaced" => true, "kind" => "Service" },
+ { "name" => "namespaces", "namespaced" => true, "kind" => "Namespace" }
]
}
end
diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb
new file mode 100644
index 00000000000..b0bf942aa09
--- /dev/null
+++ b/spec/support/services/clusters/create_service_shared.rb
@@ -0,0 +1,59 @@
+shared_context 'valid cluster create params' do
+ let(:params) do
+ {
+ name: 'test-cluster',
+ provider_type: :gcp,
+ provider_gcp_attributes: {
+ gcp_project_id: 'gcp-project',
+ zone: 'us-central1-a',
+ num_nodes: 1,
+ machine_type: 'machine_type-a',
+ legacy_abac: 'true'
+ }
+ }
+ end
+end
+
+shared_context 'invalid cluster create params' do
+ let(:params) do
+ {
+ name: 'test-cluster',
+ provider_type: :gcp,
+ provider_gcp_attributes: {
+ gcp_project_id: '!!!!!!!',
+ zone: 'us-central1-a',
+ num_nodes: 1,
+ machine_type: 'machine_type-a'
+ }
+ }
+ end
+end
+
+shared_examples 'create cluster service success' do
+ it 'creates a cluster object and performs a worker' do
+ expect(ClusterProvisionWorker).to receive(:perform_async)
+
+ expect { subject }
+ .to change { Clusters::Cluster.count }.by(1)
+ .and change { Clusters::Providers::Gcp.count }.by(1)
+
+ expect(subject.name).to eq('test-cluster')
+ expect(subject.user).to eq(user)
+ expect(subject.project).to eq(project)
+ expect(subject.provider.gcp_project_id).to eq('gcp-project')
+ expect(subject.provider.zone).to eq('us-central1-a')
+ expect(subject.provider.num_nodes).to eq(1)
+ expect(subject.provider.machine_type).to eq('machine_type-a')
+ expect(subject.provider.access_token).to eq(access_token)
+ expect(subject.provider).to be_legacy_abac
+ expect(subject.platform).to be_nil
+ end
+end
+
+shared_examples 'create cluster service error' do
+ it 'returns an error' do
+ expect(ClusterProvisionWorker).not_to receive(:perform_async)
+ expect { subject }.to change { Clusters::Cluster.count }.by(0)
+ expect(subject.errors[:"provider_gcp.gcp_project_id"]).to be_present
+ end
+end
diff --git a/spec/views/shared/runners/show.html.haml_spec.rb b/spec/views/shared/runners/show.html.haml_spec.rb
new file mode 100644
index 00000000000..5e92928b143
--- /dev/null
+++ b/spec/views/shared/runners/show.html.haml_spec.rb
@@ -0,0 +1,155 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'shared/runners/show.html.haml' do
+ include PageLayoutHelper
+
+ let(:runner) do
+ create(:ci_runner, name: 'test runner',
+ version: '11.4.0',
+ ip_address: '127.1.2.3',
+ revision: 'abcd1234',
+ architecture: 'amd64' )
+ end
+
+ before do
+ assign(:runner, runner)
+ end
+
+ subject do
+ render
+ rendered
+ end
+
+ describe 'Page title' do
+ before do
+ expect_any_instance_of(PageLayoutHelper).to receive(:page_title).with("#{runner.description} ##{runner.id}", 'Runners')
+ end
+
+ it 'sets proper page title' do
+ render
+ end
+ end
+
+ describe 'Runner id and type' do
+ context 'when runner is of type instance' do
+ it { is_expected.to have_content("Runner ##{runner.id} Shared") }
+ end
+
+ context 'when runner is of type group' do
+ let(:runner) { create(:ci_runner, :group) }
+
+ it { is_expected.to have_content("Runner ##{runner.id} Group") }
+ end
+
+ context 'when runner is of type project' do
+ let(:runner) { create(:ci_runner, :project) }
+
+ it { is_expected.to have_content("Runner ##{runner.id} Specific") }
+ end
+ end
+
+ describe 'Active value' do
+ context 'when runner is active' do
+ it { is_expected.to have_content('Active Yes') }
+ end
+
+ context 'when runner is inactive' do
+ let(:runner) { create(:ci_runner, :inactive) }
+
+ it { is_expected.to have_content('Active No') }
+ end
+ end
+
+ describe 'Protected value' do
+ context 'when runner is not protected' do
+ it { is_expected.to have_content('Protected No') }
+ end
+
+ context 'when runner is protected' do
+ let(:runner) { create(:ci_runner, :ref_protected) }
+
+ it { is_expected.to have_content('Protected Yes') }
+ end
+ end
+
+ describe 'Can run untagged jobs value' do
+ context 'when runner run untagged job is set' do
+ it { is_expected.to have_content('Can run untagged jobs Yes') }
+ end
+
+ context 'when runner run untagged job is unset' do
+ let(:runner) { create(:ci_runner, :tagged_only) }
+
+ it { is_expected.to have_content('Can run untagged jobs No') }
+ end
+ end
+
+ describe 'Locked to this project value' do
+ context 'when runner locked is not set' do
+ it { is_expected.to have_content('Locked to this project No') }
+
+ context 'when runner is of type group' do
+ let(:runner) { create(:ci_runner, :group) }
+
+ it { is_expected.not_to have_content('Locked to this project') }
+ end
+ end
+
+ context 'when runner locked is set' do
+ let(:runner) { create(:ci_runner, :locked) }
+
+ it { is_expected.to have_content('Locked to this project Yes') }
+
+ context 'when runner is of type group' do
+ let(:runner) { create(:ci_runner, :group, :locked) }
+
+ it { is_expected.not_to have_content('Locked to this project') }
+ end
+ end
+ end
+
+ describe 'Tags value' do
+ context 'when runner does not have tags' do
+ it { is_expected.to have_content('Tags') }
+ it { is_expected.not_to have_selector('span.badge.badge-primary')}
+ end
+
+ context 'when runner have tags' do
+ let(:runner) { create(:ci_runner, tag_list: %w(tag2 tag3 tag1)) }
+
+ it { is_expected.to have_content('Tags tag1 tag2 tag3') }
+ it { is_expected.to have_selector('span.badge.badge-primary')}
+ end
+ end
+
+ describe 'Metadata values' do
+ it { is_expected.to have_content("Name #{runner.name}") }
+ it { is_expected.to have_content("Version #{runner.version}") }
+ it { is_expected.to have_content("IP Address #{runner.ip_address}") }
+ it { is_expected.to have_content("Revision #{runner.revision}") }
+ it { is_expected.to have_content("Platform #{runner.platform}") }
+ it { is_expected.to have_content("Architecture #{runner.architecture}") }
+ it { is_expected.to have_content("Description #{runner.description}") }
+ end
+
+ describe 'Maximum job timeout value' do
+ let(:runner) { create(:ci_runner, maximum_timeout: 5400) }
+
+ it { is_expected.to have_content('Maximum job timeout 1h 30m') }
+ end
+
+ describe 'Last contact value' do
+ context 'when runner have not contacted yet' do
+ it { is_expected.to have_content('Last contact Never') }
+ end
+
+ context 'when runner have already contacted' do
+ let(:runner) { create(:ci_runner, contacted_at: DateTime.now - 6.days) }
+ let(:expected_contacted_at) { I18n.localize(runner.contacted_at, format: "%b %d, %Y") }
+
+ it { is_expected.to have_content("Last contact #{expected_contacted_at}") }
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index 292c7128d18..544bd4a05bd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1325,18 +1325,6 @@ binaryextensions@2:
resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.1.tgz#3209a51ca4a4ad541a3b8d3d6a6d5b83a2485935"
integrity sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA==
-blackst0ne-mermaid@^7.1.0-fixed:
- version "7.1.0-fixed"
- resolved "https://registry.yarnpkg.com/blackst0ne-mermaid/-/blackst0ne-mermaid-7.1.0-fixed.tgz#3707b3a113d78610e3068e18a588f46b4688de49"
- integrity sha1-NwezoRPXhhDjBo4YpYj0a0aI3kk=
- dependencies:
- d3 "3.5.17"
- dagre-d3-renderer "^0.4.24"
- dagre-layout "^0.8.0"
- he "^1.1.1"
- lodash "^4.17.4"
- moment "^2.18.1"
-
blob@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921"
@@ -2304,6 +2292,11 @@ d3-format@1, d3-format@1.2.1:
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.1.tgz#4e19ecdb081a341dafaf5f555ee956bcfdbf167f"
integrity sha512-U4zRVLDXW61bmqoo+OJ/V687e1T5nVd3TAKAJKgtpZ/P1JsMgyod0y9br+mlQOryTAACdiXI3wCjuERHFNp91w==
+d3-format@1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.2.tgz#1a39c479c8a57fe5051b2e67a3bee27061a74e7a"
+ integrity sha512-zH9CfF/3C8zUI47nsiKfD0+AGDEuM8LwBIP7pBVpyR4l/sKkZqITmMtxRp04rwBrlshIZ17XeFAaovN3++wzkw==
+
d3-geo@1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.9.1.tgz#157e3b0f917379d0f73bebfff3be537f49fa7356"
@@ -2376,6 +2369,11 @@ d3-selection@1, d3-selection@1.2.0, d3-selection@^1.1.0, d3-selection@^1.2.0:
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.2.0.tgz#1b8ec1c7cedadfb691f2ba20a4a3cfbeb71bbc88"
integrity sha512-xW2Pfcdzh1gOaoI+LGpPsLR2VpBQxuFoxvrvguK8ZmrJbPIVvfNG6pU6GNfK41D6Qz15sj61sbW/AFYuukwaLQ==
+d3-selection@1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.3.0.tgz#d53772382d3dc4f7507bfb28bcd2d6aed2a0ad6d"
+ integrity sha512-qgpUOg9tl5CirdqESUAu0t9MU/t3O9klYfGfyKsXEmhyxyzLpzpeh08gaxBUTQw1uXIOkr/30Ut2YRjSSxlmHA==
+
d3-shape@1.2.0, d3-shape@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777"
@@ -2428,11 +2426,6 @@ d3-zoom@1.7.1:
d3-selection "1"
d3-transition "1"
-d3@3.5.17:
- version "3.5.17"
- resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8"
- integrity sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g=
-
d3@4.12.2:
version "4.12.2"
resolved "https://registry.yarnpkg.com/d3/-/d3-4.12.2.tgz#12f775564c6a9de229f63db03446e2cb7bb56c8f"
@@ -2469,23 +2462,57 @@ d3@4.12.2:
d3-voronoi "1.1.2"
d3-zoom "1.7.1"
-dagre-d3-renderer@^0.4.24:
- version "0.4.24"
- resolved "https://registry.yarnpkg.com/dagre-d3-renderer/-/dagre-d3-renderer-0.4.24.tgz#b36ce2fe4ea20de43e7698627c6ede2a9f15ec45"
- integrity sha512-QCrYq80NTyKph+m/+kNeEq2exw5HPo/x7XprJem3wDGJbEAJDKXI2pJpqe0R4k6AsjWVd5NMVL0X7feF24Zh6Q==
+d3@^4.13.0:
+ version "4.13.0"
+ resolved "https://registry.yarnpkg.com/d3/-/d3-4.13.0.tgz#ab236ff8cf0cfc27a81e69bf2fb7518bc9b4f33d"
+ integrity sha512-l8c4+0SldjVKLaE2WG++EQlqD7mh/dmQjvi2L2lKPadAVC+TbJC4ci7Uk9bRi+To0+ansgsS0iWfPjD7DBy+FQ==
dependencies:
- d3 "3.5.17"
- dagre-layout "^0.8.0"
- graphlib "^2.1.1"
- lodash "^4.17.4"
+ d3-array "1.2.1"
+ d3-axis "1.0.8"
+ d3-brush "1.0.4"
+ d3-chord "1.0.4"
+ d3-collection "1.0.4"
+ d3-color "1.0.3"
+ d3-dispatch "1.0.3"
+ d3-drag "1.2.1"
+ d3-dsv "1.0.8"
+ d3-ease "1.0.3"
+ d3-force "1.1.0"
+ d3-format "1.2.2"
+ d3-geo "1.9.1"
+ d3-hierarchy "1.1.5"
+ d3-interpolate "1.1.6"
+ d3-path "1.0.5"
+ d3-polygon "1.0.3"
+ d3-quadtree "1.0.3"
+ d3-queue "3.0.7"
+ d3-random "1.1.0"
+ d3-request "1.0.6"
+ d3-scale "1.0.7"
+ d3-selection "1.3.0"
+ d3-shape "1.2.0"
+ d3-time "1.0.8"
+ d3-time-format "2.1.1"
+ d3-timer "1.0.7"
+ d3-transition "1.1.1"
+ d3-voronoi "1.1.2"
+ d3-zoom "1.7.1"
-dagre-layout@^0.8.0:
- version "0.8.0"
- resolved "https://registry.yarnpkg.com/dagre-layout/-/dagre-layout-0.8.0.tgz#7147b6afb655602f855158dfea171db9aa98d4ff"
- integrity sha512-vK4WiR6h3whkoW9aM/FCjZTTx10V2YnLOLEj2+uvOQmiEjGmUvkme+Qrjj/7Tq0+AI54yFHT/tbbqM9AadsK4A==
+dagre-d3-renderer@^0.5.8:
+ version "0.5.8"
+ resolved "https://registry.yarnpkg.com/dagre-d3-renderer/-/dagre-d3-renderer-0.5.8.tgz#aa071bb71d3c4d67426925906f3f6ddead49c1a3"
+ integrity sha512-XH2a86isUHRxzIYbjQVEuZtJnWEufb64H5DuXIUmn8esuB40jgLEbUUclulWOW62/ZoXlj2ZDyL8SJ+YRxs+jQ==
dependencies:
- graphlib "^2.1.1"
- lodash "^4.17.4"
+ dagre-layout "^0.8.8"
+ lodash "^4.17.5"
+
+dagre-layout@^0.8.8:
+ version "0.8.8"
+ resolved "https://registry.yarnpkg.com/dagre-layout/-/dagre-layout-0.8.8.tgz#9b6792f24229f402441c14162c1049e3f261f6d9"
+ integrity sha512-ZNV15T9za7X+fV8Z07IZquUKugCxm5owoiPPxfEx6OJRD331nkiIaF3vSt0JEY5FkrY0KfRQxcpQ3SpXB7pLPQ==
+ dependencies:
+ graphlibrary "^2.2.0"
+ lodash "^4.17.5"
date-format@^1.2.0:
version "1.2.0"
@@ -3004,6 +3031,11 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+escaper@^2.5.3:
+ version "2.5.3"
+ resolved "https://registry.yarnpkg.com/escaper/-/escaper-2.5.3.tgz#8b8fe90ba364054151ab7eff18b4ce43b1e13ab5"
+ integrity sha512-QGb9sFxBVpbzMggrKTX0ry1oiI4CSDAl9vIL702hzl1jGW8VZs7qfqTRX7WDOjoNDoEVGcEtu1ZOQgReSfT2kQ==
+
escodegen@1.8.x:
version "1.8.1"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018"
@@ -3893,12 +3925,12 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=
-graphlib@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.1.tgz#42352c52ba2f4d035cb566eb91f7395f76ebc951"
- integrity sha1-QjUsUrovTQNctWbrkfc5X3bryVE=
+graphlibrary@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/graphlibrary/-/graphlibrary-2.2.0.tgz#017a14899775228dec4497a39babfdd6bf56eac6"
+ integrity sha512-XTcvT55L8u4MBZrM37zXoUxsgxs/7sow7YSygd9CIwfWTVO8RVu7AYXhhCiTuFEf+APKgx6Jk4SuQbYR0vYKmQ==
dependencies:
- lodash "^4.11.1"
+ lodash "^4.17.5"
gzip-size@^5.0.0:
version "5.0.0"
@@ -4181,11 +4213,6 @@ ignore-walk@^3.0.1:
dependencies:
minimatch "^3.0.4"
-ignore@^3.3.7:
- version "3.3.8"
- resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.8.tgz#3f8e9c35d38708a3a7e0e9abb6c73e7ee7707b2b"
- integrity sha512-pUh+xUQQhQzevjRHHFqqcTy0/dP/kS9I8HSrUydhihjuD09W6ldVWFtIrwhXdUJHis3i2rZNqEHpZH/cbinFbg==
-
ignore@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
@@ -4567,6 +4594,11 @@ is-regex@^1.0.4:
dependencies:
has "^1.0.1"
+is-regexp@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
+ integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk=
+
is-resolvable@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
@@ -5134,7 +5166,7 @@ lodash.upperfirst@4.3.1:
resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce"
integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984=
-lodash@^4.11.1, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0:
+lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
@@ -5293,6 +5325,20 @@ merge-source-map@^1.1.0:
dependencies:
source-map "^0.6.1"
+mermaid@^8.0.0-rc.8:
+ version "8.0.0-rc.8"
+ resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.0.0-rc.8.tgz#74ed54d0d46e9ee71c4db2730b2d83d516a21e72"
+ integrity sha512-GbF9jHWfqE7YGx9vQySmBxy2Ahlclxmpk4tJ9ntNyafENl96s96ggUK/NQS5ydYoFab6MavTm4YMTIPKqWVvPQ==
+ dependencies:
+ d3 "^4.13.0"
+ dagre-d3-renderer "^0.5.8"
+ dagre-layout "^0.8.8"
+ graphlibrary "^2.2.0"
+ he "^1.1.1"
+ lodash "^4.17.5"
+ moment "^2.21.0"
+ scope-css "^1.0.5"
+
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@@ -5451,11 +5497,16 @@ mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0:
dependencies:
minimist "0.0.8"
-moment@2.x, moment@^2.18.1:
+moment@2.x:
version "2.19.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.2.tgz#8a7f774c95a64550b4c7ebd496683908f9419dbe"
integrity sha512-Rf6jiHPEfxp9+dlzxPTmRHbvoFXsh2L/U8hOupUMpnuecHQmI6cF6lUbJl3QqKPko1u6ujO+FxtcajLVfLpAtA==
+moment@^2.21.0:
+ version "2.22.2"
+ resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
+ integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=
+
monaco-editor-webpack-plugin@^1.5.4:
version "1.5.4"
resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.5.4.tgz#6781a130e3e1379bb8f4cd190132f4af6dcd2c16"
@@ -6893,6 +6944,15 @@ schema-utils@^1.0.0:
ajv-errors "^1.0.0"
ajv-keywords "^3.1.0"
+scope-css@^1.0.5:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/scope-css/-/scope-css-1.2.1.tgz#c35768bc900cad030a3e0d663a818c0f6a57f40e"
+ integrity sha512-UjLRmyEYaDNiOS673xlVkZFlVCtckJR/dKgr434VMm7Lb+AOOqXKdAcY7PpGlJYErjXXJzKN7HWo4uRPiZZG0Q==
+ dependencies:
+ escaper "^2.5.3"
+ slugify "^1.3.1"
+ strip-css-comments "^3.0.0"
+
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -7066,6 +7126,11 @@ slice-ansi@1.0.0:
dependencies:
is-fullwidth-code-point "^2.0.0"
+slugify@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.3.1.tgz#f572127e8535329fbc6c1edb74ab856b61ad7de2"
+ integrity sha512-6BwyhjF5tG5P8s+0DPNyJmBSBePG6iMyhjvIW5zGdA3tFik9PtK+yNkZgTeiroCRGZYgkHftFA62tGVK1EI9Kw==
+
smooshpack@^0.0.48:
version "0.0.48"
resolved "https://registry.yarnpkg.com/smooshpack/-/smooshpack-0.0.48.tgz#6fbeaaf59226a1fe500f56aa17185eed377d2823"
@@ -7439,6 +7504,13 @@ strip-bom@^3.0.0:
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
+strip-css-comments@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-css-comments/-/strip-css-comments-3.0.0.tgz#7a5625eff8a2b226cf8947a11254da96e13dae89"
+ integrity sha1-elYl7/iisibPiUehElTaluE9rok=
+ dependencies:
+ is-regexp "^1.0.0"
+
strip-eof@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"