diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index bd14e8533ef36877c5845b6d73783a3507c95340..b53d377d90990a7c9a5b8eec177c4eaad7551208 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.78.0 +0.80.0 diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index d5c0c99142898e7915ff3a5805c69eafe9461f26..40c341bdcdbe83bbbda981fa85368c0e1a63d0c7 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -3.5.1 +3.6.0 diff --git a/Gemfile b/Gemfile index 52ced4a132b041bff63ea538ed99d58c4a0b65a6..27c745b99a218e95fed80d7d513fc27cca3a86d5 100644 --- a/Gemfile +++ b/Gemfile @@ -411,6 +411,8 @@ end # Gitaly GRPC client gem 'gitaly-proto', '~> 0.83.0', require: 'gitaly' +# Locked until https://github.com/google/protobuf/issues/4210 is closed +gem 'google-protobuf', '= 3.5.1' gem 'toml-rb', '~> 0.3.15', require: false diff --git a/Gemfile.lock b/Gemfile.lock index cf9f160499d6b30c13409d1673805bd4e6d86ce1..e78c3c5f79496b795e43c27cb382c9a4aaf381cd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -340,7 +340,7 @@ GEM mime-types (~> 3.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) - google-protobuf (3.5.1.1) + google-protobuf (3.5.1) googleapis-common-protos-types (1.0.1) google-protobuf (~> 3.0) googleauth (0.5.3) @@ -1066,6 +1066,7 @@ DEPENDENCIES gollum-rugged_adapter (~> 0.4.4) gon (~> 6.1.0) google-api-client (~> 0.13.6) + google-protobuf (= 3.5.1) gpgme grape (~> 1.0) grape-entity (~> 0.6.0) diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index 4dddb6eb0d6f5e7c44187416620e161141888c21..3d6ec37e6dd38ba59712fceabff6499cd61a8087 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -35,10 +35,11 @@ export default class Clusters { clusterStatus, clusterStatusReason, helpPath, + ingressHelpPath, } = document.querySelector('.js-edit-cluster-form').dataset; this.store = new ClustersStore(); - this.store.setHelpPath(helpPath); + this.store.setHelpPaths(helpPath, ingressHelpPath); this.store.updateStatus(clusterStatus); this.store.updateStatusReason(clusterStatusReason); this.service = new ClustersService({ @@ -93,6 +94,7 @@ export default class Clusters { props: { applications: this.state.applications, helpPath: this.state.helpPath, + ingressHelpPath: this.state.ingressHelpPath, }, }); }, @@ -172,7 +174,7 @@ export default class Clusters { .map(appId => newApplicationMap[appId].title); if (appTitles.length > 0) { - const text = sprintf(s__('ClusterIntegration|%{appList} was successfully installed on your cluster'), { + const text = sprintf(s__('ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster'), { appList: appTitles.join(', '), }); Flash(text, 'notice', this.successApplicationContainer); diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index ff2e0768a879c6a0df41bb51d56714f0ba63e3de..f425970037092173fac77acde7d9c043845fdd52 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -18,11 +18,16 @@ required: false, default: '', }, + ingressHelpPath: { + type: String, + required: false, + default: '', + }, }, computed: { generalApplicationDescription() { return sprintf( - _.escape(s__(`ClusterIntegration|Install applications on your cluster. + _.escape(s__(`ClusterIntegration|Install applications on your Kubernetes cluster. Read more about %{helpLink}`)), { helpLink: ` @@ -34,7 +39,7 @@ }, helmTillerDescription() { return _.escape(s__( - `ClusterIntegration|Helm streamlines installing and managing Kubernets applications. + `ClusterIntegration|Helm streamlines installing and managing Kubernetes applications. Tiller runs inside of your Kubernetes Cluster, and manages releases of your charts.`, )); @@ -49,7 +54,7 @@ _.escape(s__( `ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on - the hosting provider Kubernetes is installed on. If you are using GKE, + the hosting provider your Kubernetes cluster is installed on. If you are using GKE, you can %{pricingLink}.`, )), { boldNotice: `${_.escape(s__('ClusterIntegration|Note:'))}`, @@ -59,13 +64,28 @@ false, ); + const externalIpParagraph = sprintf( + _.escape(s__( + `ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS + at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}`, + )), { + ingressHelpLink: ` + ${_.escape(s__('ClusterIntegration|More information'))} + `, + }, + false, + ); + return `

${descriptionParagraph}

-

+

${extraCostParagraph}

+

+ ${externalIpParagraph} +

`; }, gitlabRunnerDescription() { diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js index bd4a1fb37f93a1113f39a1d8e81b7acad0222b5d..49c3d184ef92e33b9ad1a35331b90cc5c2996124 100644 --- a/app/assets/javascripts/clusters/stores/clusters_store.js +++ b/app/assets/javascripts/clusters/stores/clusters_store.js @@ -4,6 +4,7 @@ export default class ClusterStore { constructor() { this.state = { helpPath: null, + ingressHelpPath: null, status: null, statusReason: null, applications: { @@ -39,8 +40,9 @@ export default class ClusterStore { }; } - setHelpPath(helpPath) { + setHelpPaths(helpPath, ingressHelpPath) { this.state.helpPath = helpPath; + this.state.ingressHelpPath = ingressHelpPath; } updateStatus(status) { diff --git a/app/assets/javascripts/toggle_buttons.js b/app/assets/javascripts/toggle_buttons.js index 2d680d0f0dc2e6805d0d93967089e81f0b72985d..199b14458ed5b3de32c66f1535808b969777fb26 100644 --- a/app/assets/javascripts/toggle_buttons.js +++ b/app/assets/javascripts/toggle_buttons.js @@ -8,7 +8,7 @@ import { convertPermissionToBoolean } from './lib/utils/common_utils'; ``` %button.js-project-feature-toggle.project-feature-toggle{ type: "button", class: "#{'is-checked' if enabled?}", - 'aria-label': _('Toggle Cluster') } + 'aria-label': _('Toggle Kubernetes Cluster') } %input{ type: "hidden", class: 'js-project-feature-toggle-input', value: enabled? } ``` */ diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index 32b9894ae04e0a9ad95f7014c2b31b4b1b3e91b3..a6b1bf9b0996318bc44a5ec1cbcfc268c0227bbd 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -4,6 +4,11 @@ .page-title { margin-top: 0; + + .color-label { + font-size: $gl-font-size; + padding: $gl-vert-padding $label-padding-modal; + } } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 1cc22f5658d5a76686079adce0bff1fd94044ac1..0d21a9f5f774fa622f4954f6978dc314d0e339f2 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -558,6 +558,7 @@ $jq-ui-default-color: #777; * Label */ $label-padding: 7px; +$label-padding-modal: 10px; $label-gray-bg: #f8fafc; $label-inverse-bg: #333; $label-remove-border: rgba(0, 0, 0, .1); diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index e8cd8a4905c7dd25db92a0fc80f012e8b3d25fcd..a72e654824e37c297d4320206607a473baaf8028 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -58,13 +58,13 @@ @media (min-width: $screen-sm-min) { width: 200px; + margin-left: $gl-padding * 2; margin-bottom: 0; } .label { overflow: hidden; text-overflow: ellipsis; - vertical-align: middle; max-width: 100%; } } @@ -79,26 +79,33 @@ width: 100px; margin-left: 10px; margin-bottom: 0; - vertical-align: middle; + vertical-align: top; } } .label-description { display: block; margin-bottom: 10px; - margin-left: 50px; + + .description-text { + margin-bottom: $gl-padding; + } + + a { + color: $blue-600; + } @media (min-width: $screen-sm-min) { display: inline-block; - width: 30%; + max-width: 50%; margin-left: 10px; margin-bottom: 0; - vertical-align: middle; + vertical-align: top; } } .label { - padding: 8px 9px 9px; + padding: 8px 12px; font-size: 14px; } } @@ -116,6 +123,12 @@ } .manage-labels-list { + @media(min-width: $screen-md-min) { + &.content-list li { + padding: $gl-padding 0; + } + } + > li:not(.empty-message):not(.is-not-draggable) { background-color: $white-light; cursor: move; @@ -133,8 +146,6 @@ } .btn-action { - color: $gl-text-color; - .fa { font-size: 18px; vertical-align: middle; @@ -155,10 +166,18 @@ float: right; } } + + @media (max-width: $screen-xs-max) { + .dropdown-menu { + min-width: 100%; + } + } } .draggable-handler { display: inline-block; + vertical-align: top; + margin: 5px 0; opacity: 0; transition: opacity .3s; color: $gray-darkest; @@ -188,7 +207,7 @@ .toggle-priority { display: inline-block; - vertical-align: middle; + vertical-align: top; button { border-color: transparent; @@ -255,6 +274,11 @@ } .label-subscribe-button { + @media(min-width: $screen-md-min) { + min-width: 105px; + margin-left: $gl-padding; + } + .label-subscribe-button-icon { &[disabled] { opacity: 0.5; diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index dda59262483d344bfb35aa942142e7d0fc140617..f3a9e591c3e82731b85995547cd000ecd54db509 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -54,7 +54,7 @@ class Groups::LabelsController < Groups::ApplicationController respond_to do |format| format.html do - redirect_to group_labels_path(@group), status: 302, notice: 'Label was removed' + redirect_to group_labels_path(@group), status: 302, notice: "#{@label.name} deleted permanently" end format.js end diff --git a/app/controllers/projects/clusters/gcp_controller.rb b/app/controllers/projects/clusters/gcp_controller.rb index 4fc515bd03eb406b54e85ab656fb81a58963bc57..94d33b91562735b69bf1d8b18d134f5f16d337e3 100644 --- a/app/controllers/projects/clusters/gcp_controller.rb +++ b/app/controllers/projects/clusters/gcp_controller.rb @@ -42,7 +42,7 @@ class Projects::Clusters::GcpController < Projects::ApplicationController when 'true' return when 'false' - flash[:alert] = _('Please enable billing for one of your projects to be able to create a cluster, then try again.').html_safe % { link_to_billing: "https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral" } + flash[:alert] = _('Please enable billing for one of your projects to be able to create a Kubernetes cluster, then try again.').html_safe % { link_to_billing: "https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral" } else flash[:alert] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.') end diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb index 1dc7f1b3a7f6b9f5b4215e573ce249504928caea..142e8b6e4bc1fd414c0acc5e349569c70b749a76 100644 --- a/app/controllers/projects/clusters_controller.rb +++ b/app/controllers/projects/clusters_controller.rb @@ -41,7 +41,7 @@ class Projects::ClustersController < Projects::ApplicationController head :no_content end format.html do - flash[:notice] = "Cluster was successfully updated." + flash[:notice] = _('Kubernetes cluster was successfully updated.') redirect_to project_cluster_path(project, cluster) end end @@ -55,10 +55,10 @@ class Projects::ClustersController < Projects::ApplicationController def destroy if cluster.destroy - flash[:notice] = "Cluster integration was successfully removed." + flash[:notice] = _('Kubernetes cluster integration was successfully removed.') redirect_to project_clusters_path(project), status: 302 else - flash[:notice] = "Cluster integration was not removed." + flash[:notice] = _('Kubernetes cluster integration was not removed.') render :show end end diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 71ae60cb8cdacbb7578560a09124f2e62997da73..45910a9be44ae3891d5e5e6a536362c4b3b2171d 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -5,6 +5,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController rescue_from Gitlab::GitAccess::UnauthorizedError, with: :render_403 rescue_from Gitlab::GitAccess::NotFoundError, with: :render_404 + rescue_from Gitlab::GitAccess::ProjectCreationError, with: :render_422 # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull) # GET /foo/bar.git/info/refs?service=git-receive-pack (git push) @@ -55,8 +56,15 @@ class Projects::GitHttpController < Projects::GitHttpClientController render plain: exception.message, status: :not_found end + def render_422(exception) + render plain: exception.message, status: :unprocessable_entity + end + def access - @access ||= access_klass.new(access_actor, project, 'http', authentication_abilities: authentication_abilities, redirected_path: redirected_path) + @access ||= access_klass.new(access_actor, project, + 'http', authentication_abilities: authentication_abilities, + namespace_path: params[:namespace_id], project_path: project_path, + redirected_path: redirected_path) end def access_actor @@ -68,12 +76,17 @@ class Projects::GitHttpController < Projects::GitHttpClientController # Use the magic string '_any' to indicate we do not know what the # changes are. This is also what gitlab-shell does. access.check(git_command, '_any') + @project ||= access.project end def access_klass @access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess end + def project_path + @project_path ||= params[:project_id].sub(/\.git$/, '') + end + def log_user_activity Users::ActivityService.new(user, 'pull').execute end diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index fde96cbd35d032123643e6521943c679be5769fc..c4930d3d18d736d36504bdaf8a0602ddc1662b4e 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -76,9 +76,9 @@ class Projects::WikisController < Projects::ApplicationController @page = @project_wiki.find_page(params[:id]) if @page - @page_versions = Kaminari.paginate_array(@page.versions(page: params[:page]), + @page_versions = Kaminari.paginate_array(@page.versions(page: params[:page].to_i), total_count: @page.count_versions) - .page(params[:page]) + .page(params[:page]) else redirect_to( project_wiki_path(@project, :home), diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 09450eaa5b72d587e00dacf56bc1e674a4eede4d..23de3590b9398082973ce55426b24bf5c30171aa 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -1,4 +1,8 @@ module GroupsHelper + def group_nav_link_paths + %w[groups#projects groups#edit ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index] + end + def can_change_group_visibility_level?(group) can?(current_user, :change_visibility_level, group) end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 78906e7a968a0903df6f41f73e340c3f2f81ab6f..20534b8eed03d1681603e0b28fb43a32f6bd31a3 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -21,6 +21,7 @@ module Ci has_many :job_artifacts, class_name: 'Ci::JobArtifact', foreign_key: :job_id, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_one :job_artifacts_archive, -> { where(file_type: Ci::JobArtifact.file_types[:archive]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id has_one :job_artifacts_metadata, -> { where(file_type: Ci::JobArtifact.file_types[:metadata]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id + has_one :job_artifacts_trace, -> { where(file_type: Ci::JobArtifact.file_types[:trace]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id # The "environment" field for builds is a String, and is the unexpanded name def persisted_environment diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index 84fc6863567ccb480654e64404d152c6fcc1c86d..0a599f72bc7786f593bef5a9037660dae6e5c1e6 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -9,9 +9,12 @@ module Ci mount_uploader :file, JobArtifactUploader + delegate :open, :exists?, to: :file + enum file_type: { archive: 1, - metadata: 2 + metadata: 2, + trace: 3 } def self.artifacts_size_for(project) diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 7f38dcc4a9cec18699979960ba3c0ef2db6266a2..7ce8befeeeb4b3d0b8b9f096c5d46ffbce2e8ba5 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -180,7 +180,7 @@ module Clusters return unless managed? if api_url_changed? || token_changed? || ca_pem_changed? - errors.add(:base, "cannot modify managed cluster") + errors.add(:base, _('Cannot modify managed Kubernetes cluster')) return false end diff --git a/app/models/concerns/artifact_migratable.rb b/app/models/concerns/artifact_migratable.rb index 0460439e9e6bcbb1feb7479e7d91939d93ec1ef0..ff52ca644590809193d1231338da2facbb1668bf 100644 --- a/app/models/concerns/artifact_migratable.rb +++ b/app/models/concerns/artifact_migratable.rb @@ -39,7 +39,6 @@ module ArtifactMigratable end def artifacts_size - read_attribute(:artifacts_size).to_i + - job_artifacts_archive&.size.to_i + job_artifacts_metadata&.size.to_i + read_attribute(:artifacts_size).to_i + job_artifacts.sum(:size).to_i end end diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index 5c1cce98ad4826ace20834d00a6d1d4ffb41da49..dfd7d94450bf7fac71510326454cc58b84af3c02 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -7,11 +7,12 @@ module Routable has_one :route, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent - validates_associated :route validates :route, presence: true scope :with_route, -> { includes(:route) } + after_validation :set_path_errors + before_validation do if full_path_changed? || full_name_changed? prepare_route @@ -125,6 +126,11 @@ module Routable private + def set_path_errors + route_path_errors = self.errors.delete(:"route.path") + self.errors[:path].concat(route_path_errors) if route_path_errors + end + def uncached_full_path if route && route.path.present? @full_path ||= route.path # rubocop:disable Gitlab/ModuleWithInstanceVariables diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 06655298950f706a8725c612f77999c6e926c820..d95489ee9f283c2c44528bd249c01bd5a6c12301 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -20,6 +20,9 @@ class Namespace < ActiveRecord::Base has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :project_statistics + + # This should _not_ be `inverse_of: :namespace`, because that would also set + # `user.namespace` when this user creates a group with themselves as `owner`. belongs_to :owner, class_name: "User" belongs_to :parent, class_name: "Namespace" @@ -29,7 +32,6 @@ class Namespace < ActiveRecord::Base validates :owner, presence: true, unless: ->(n) { n.type == "Group" } validates :name, presence: true, - uniqueness: { scope: :parent_id }, length: { maximum: 255 }, namespace_name: true diff --git a/app/models/project.rb b/app/models/project.rb index 12d5f28f5ea4c6cfa46833e3f78ccc5f9b97a025..3edb6109fb8bf6eee25e0bc46151e2f6edfbfa0f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -245,8 +245,7 @@ class Project < ActiveRecord::Base validates :path, presence: true, project_path: true, - length: { maximum: 255 }, - uniqueness: { scope: :namespace_id } + length: { maximum: 255 } validates :namespace, presence: true validates :name, uniqueness: { scope: :namespace_id } @@ -511,10 +510,13 @@ class Project < ActiveRecord::Base @repository ||= Repository.new(full_path, self, disk_path: disk_path) end - def reload_repository! + def cleanup + @repository&.cleanup @repository = nil end + alias_method :reload_repository!, :cleanup + def container_registry_url if Gitlab.config.registry.enabled "#{Gitlab.config.registry.host_port}/#{full_path.downcase}" diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index e42fd802b923329e6105bae827cbced5ca9128a3..ad4ad7903adb540a3fb026c6540585ab639e9049 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -150,9 +150,10 @@ class KubernetesService < DeploymentService end def deprecation_message - content = <<-MESSAGE.strip_heredoc - Kubernetes service integration has been deprecated. #{deprecated_message_content} your clusters using the new Clusters page - MESSAGE + content = _("Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page") % { + deprecated_message_content: deprecated_message_content, + url: Gitlab::Routing.url_helpers.project_clusters_path(project) + } content.html_safe end @@ -248,9 +249,9 @@ class KubernetesService < DeploymentService def deprecated_message_content if active? - "Your cluster information on this page is still editable, but you are advised to disable and reconfigure" + _("Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure") else - "Fields on this page are now uneditable, you can configure" + _("Fields on this page are now uneditable, you can configure") end end end diff --git a/app/models/repository.rb b/app/models/repository.rb index f1abe5c3e07b9cfb3a1d52c9bfc682c026ff5c95..3d6f8f0c3055a577b92f59cb8b4f94ba2adcddd3 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -93,6 +93,10 @@ class Repository alias_method :raw, :raw_repository + def cleanup + @raw_repository&.cleanup + end + # Return absolute path to repository def path_to_repo @path_to_repo ||= File.expand_path( diff --git a/app/models/route.rb b/app/models/route.rb index 3d4b5a8b5ee73e33c440d403f2d0d4900bf9224d..07d96c21cf1f7ef0b54adc3deef44ae74000c519 100644 --- a/app/models/route.rb +++ b/app/models/route.rb @@ -75,7 +75,7 @@ class Route < ActiveRecord::Base def ensure_permanent_paths return if path.nil? - errors.add(:path, "#{path} has been taken before. Please use another one") if conflicting_redirect_exists? + errors.add(:path, "has been taken before") if conflicting_redirect_exists? end def conflicting_redirect_exists? diff --git a/app/models/upload.rb b/app/models/upload.rb index 2024228537aeb52a8f205b38f862511a2371540c..99ad37dc89207067fa2a7229fd68b7b1f5803674 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -12,6 +12,10 @@ class Upload < ActiveRecord::Base before_save :calculate_checksum!, if: :foreground_checksummable? after_commit :schedule_checksum, if: :checksummable? + # as the FileUploader is not mounted, the default CarrierWave ActiveRecord + # hooks are not executed and the file will not be deleted + after_destroy :delete_file!, if: -> { uploader_class <= FileUploader } + def self.hexdigest(path) Digest::SHA256.file(path).hexdigest end @@ -49,6 +53,10 @@ class Upload < ActiveRecord::Base private + def delete_file! + build_uploader.remove! + end + def checksummable? checksum.nil? && local? && exist? end diff --git a/app/models/user.rb b/app/models/user.rb index 4b44e9bb7eb70fb392c036e035565ec194824f3a..05c93d3cb17ebdc46424ef9bf4fc13da50afd1af 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -77,7 +77,7 @@ class User < ActiveRecord::Base # # Namespace for personal projects - has_one :namespace, -> { where(type: nil) }, dependent: :destroy, foreign_key: :owner_id, autosave: true # rubocop:disable Cop/ActiveRecordDependent + has_one :namespace, -> { where(type: nil) }, dependent: :destroy, foreign_key: :owner_id, inverse_of: :owner, autosave: true # rubocop:disable Cop/ActiveRecordDependent # Profile has_many :keys, -> do @@ -151,12 +151,9 @@ class User < ActiveRecord::Base validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: Gitlab::Database::MAX_INT_VALUE } - validates :username, - user_path: true, - presence: true, - uniqueness: { case_sensitive: false } + validates :username, presence: true - validate :namespace_uniq, if: :username_changed? + validates :namespace, presence: true validate :namespace_move_dir_allowed, if: :username_changed? validate :unique_email, if: :email_changed? @@ -171,7 +168,8 @@ class User < ActiveRecord::Base before_save :ensure_user_rights_and_limits, if: ->(user) { user.new_record? || user.external_changed? } before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) } before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? } - after_save :ensure_namespace_correct + before_validation :ensure_namespace_correct + after_validation :set_username_errors after_update :username_changed_hook, if: :username_changed? after_destroy :post_destroy_hook after_destroy :remove_key_cache @@ -230,8 +228,8 @@ class User < ActiveRecord::Base scope :active, -> { with_state(:active).non_internal } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members WHERE user_id IS NOT NULL AND requested_at IS NULL)') } scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) } - scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('last_sign_in_at', 'DESC')) } - scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('last_sign_in_at', 'ASC')) } + scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) } + scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) } def self.with_two_factor joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id") @@ -505,17 +503,6 @@ class User < ActiveRecord::Base end end - def namespace_uniq - # Return early if username already failed the first uniqueness validation - return if errors.key?(:username) && - errors[:username].include?('has already been taken') - - existing_namespace = Namespace.by_path(username) - if existing_namespace && existing_namespace != namespace - errors.add(:username, 'has already been taken') - end - end - def namespace_move_dir_allowed if namespace&.any_project_has_container_registry_tags? errors.add(:username, 'cannot be changed if a personal project has container registry tags.') @@ -884,19 +871,18 @@ class User < ActiveRecord::Base end def ensure_namespace_correct - # Ensure user has namespace - create_namespace!(path: username, name: username) unless namespace - - if username_changed? - unless namespace.update_attributes(path: username, name: username) - namespace.errors.each do |attribute, message| - self.errors.add(:"namespace_#{attribute}", message) - end - raise ActiveRecord::RecordInvalid.new(namespace) - end + if namespace + namespace.path = namespace.name = username if username_changed? + else + build_namespace(path: username, name: username) end end + def set_username_errors + namespace_path_errors = self.errors.delete(:"namespace.path") + self.errors[:username].concat(namespace_path_errors) if namespace_path_errors + end + def username_changed_hook system_hook_service.execute_hooks_for(self, :rename) end diff --git a/app/services/ci/create_trace_artifact_service.rb b/app/services/ci/create_trace_artifact_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..280a2c3afa4c91fefcf2deeb93b5492a094567a0 --- /dev/null +++ b/app/services/ci/create_trace_artifact_service.rb @@ -0,0 +1,16 @@ +module Ci + class CreateTraceArtifactService < BaseService + def execute(job) + return if job.job_artifacts_trace + + job.trace.read do |stream| + if stream.file? + job.create_job_artifacts_trace!( + project: job.project, + file_type: :trace, + file: stream) + end + end + end + end +end diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb index 0471b0f17a22f1c370f489b6c79e38e9c3148285..418888e329366972953f7500ee1be923b02671a6 100644 --- a/app/services/clusters/create_service.rb +++ b/app/services/clusters/create_service.rb @@ -5,7 +5,7 @@ module Clusters def execute(access_token = nil) @access_token = access_token - raise ArgumentError.new('Instance does not support multiple clusters') unless can_create_cluster? + raise ArgumentError.new(_('Instance does not support multiple Kubernetes clusters')) unless can_create_cluster? create_cluster.tap do |cluster| ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted? diff --git a/app/services/clusters/gcp/verify_provision_status_service.rb b/app/services/clusters/gcp/verify_provision_status_service.rb index bc33756f27c1675a9c0798a97f523b7d89fd1299..f994aacd086564f28154bada29cc4d7b66e41cec 100644 --- a/app/services/clusters/gcp/verify_provision_status_service.rb +++ b/app/services/clusters/gcp/verify_provision_status_service.rb @@ -28,7 +28,7 @@ module Clusters if elapsed_time_from_creation(operation) < TIMEOUT WaitForClusterCreationWorker.perform_in(EAGER_INTERVAL, provider.cluster_id) else - provider.make_errored!("Cluster creation time exceeds timeout; #{TIMEOUT}") + provider.make_errored!(_('Kubernetes cluster creation time exceeds timeout; %{timeout}') % { timeout: TIMEOUT }) end end diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index 00a8dcf093452510c4407450dbb25dfbc3d384df..46acdc5406c2d032a439aefebed158bdc76127bc 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -1,10 +1,20 @@ module Files class CreateService < Files::BaseService def create_commit! + handler = Lfs::FileModificationHandler.new(project, @branch_name) + + handler.new_file(@file_path, @file_content) do |content_or_lfs_pointer| + create_transformed_commit(content_or_lfs_pointer) + end + end + + private + + def create_transformed_commit(content_or_lfs_pointer) repository.create_file( current_user, @file_path, - @file_content, + content_or_lfs_pointer, message: @commit_message, branch_name: @branch_name, author_email: @author_email, diff --git a/app/services/lfs/file_modification_handler.rb b/app/services/lfs/file_modification_handler.rb new file mode 100644 index 0000000000000000000000000000000000000000..fe9091a6e5d697d190879b0719ceb8710b6702f5 --- /dev/null +++ b/app/services/lfs/file_modification_handler.rb @@ -0,0 +1,42 @@ +module Lfs + class FileModificationHandler + attr_reader :project, :branch_name + + delegate :repository, to: :project + + def initialize(project, branch_name) + @project = project + @branch_name = branch_name + end + + def new_file(file_path, file_content) + if project.lfs_enabled? && lfs_file?(file_path) + lfs_pointer_file = Gitlab::Git::LfsPointerFile.new(file_content) + lfs_object = create_lfs_object!(lfs_pointer_file, file_content) + content = lfs_pointer_file.pointer + + success = yield(content) + + link_lfs_object!(lfs_object) if success + else + yield(file_content) + end + end + + private + + def lfs_file?(file_path) + repository.attributes_at(branch_name, file_path)['filter'] == 'lfs' + end + + def create_lfs_object!(lfs_pointer_file, file_content) + LfsObject.find_or_create_by(oid: lfs_pointer_file.sha256, size: lfs_pointer_file.size) do |lfs_object| + lfs_object.file = CarrierWaveStringFile.new(file_content) + end + end + + def link_lfs_object!(lfs_object) + project.lfs_objects << lfs_object + end + end +end diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index 2310e67cb2e3a41d06273063154775411a092d6c..bde1161dfa8cfa0934183cb61734028307111ca8 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -15,6 +15,8 @@ class FileUploader < GitlabUploader storage :file + after :remove, :prune_store_dir + def self.root File.join(options.storage_path, 'uploads') end @@ -140,6 +142,10 @@ class FileUploader < GitlabUploader end end + def prune_store_dir + storage.delete_dir!(store_dir) # only remove when empty + end + def markdown_name (image_or_video? ? File.basename(filename, File.extname(filename)) : filename).gsub("]", "\\]") end diff --git a/app/uploaders/job_artifact_uploader.rb b/app/uploaders/job_artifact_uploader.rb index 0abb462ab7d75e10a50f988ca4b0bf9ee920ad86..ad5385f45a45c149bafb83037669601bd467e1b0 100644 --- a/app/uploaders/job_artifact_uploader.rb +++ b/app/uploaders/job_artifact_uploader.rb @@ -13,6 +13,12 @@ class JobArtifactUploader < GitlabUploader dynamic_segment end + def open + raise 'Only File System is supported' unless file_storage? + + File.open(path, "rb") if path + end + private def dynamic_segment diff --git a/app/validators/abstract_path_validator.rb b/app/validators/abstract_path_validator.rb index adbccb65a849a4c7ad708fb5474396912df0df2e..e43b66cbe3a09e2c569d592ced8db1d27634d6ae 100644 --- a/app/validators/abstract_path_validator.rb +++ b/app/validators/abstract_path_validator.rb @@ -13,10 +13,6 @@ class AbstractPathValidator < ActiveModel::EachValidator raise NotImplementedError end - def self.full_path(record, value) - value - end - def self.valid_path?(path) encode!(path) "#{path}/" =~ path_regex @@ -28,7 +24,7 @@ class AbstractPathValidator < ActiveModel::EachValidator return end - full_path = self.class.full_path(record, value) + full_path = record.build_full_path return unless full_path unless self.class.valid_path?(full_path) diff --git a/app/validators/namespace_path_validator.rb b/app/validators/namespace_path_validator.rb index 4a0aa64ae0c2726407de76523843fdf7e17e21f9..7b0ae4db5d43d1e72fbb3dbcd5d1710d15ae7a94 100644 --- a/app/validators/namespace_path_validator.rb +++ b/app/validators/namespace_path_validator.rb @@ -12,8 +12,4 @@ class NamespacePathValidator < AbstractPathValidator def self.format_error_message Gitlab::PathRegex.namespace_format_message end - - def self.full_path(record, value) - record.build_full_path - end end diff --git a/app/validators/project_path_validator.rb b/app/validators/project_path_validator.rb index 829b596ad3cbff707f4ba1b9e12eedfba32aa9a6..424fd77a6a3b07807b2cb1f263bb63d9dc7de97d 100644 --- a/app/validators/project_path_validator.rb +++ b/app/validators/project_path_validator.rb @@ -12,8 +12,4 @@ class ProjectPathValidator < AbstractPathValidator def self.format_error_message Gitlab::PathRegex.project_path_format_message end - - def self.full_path(record, value) - record.build_full_path - end end diff --git a/app/validators/user_path_validator.rb b/app/validators/user_path_validator.rb deleted file mode 100644 index adf02901802b9acee65c1ac0d92ed9f0b7541b0f..0000000000000000000000000000000000000000 --- a/app/validators/user_path_validator.rb +++ /dev/null @@ -1,15 +0,0 @@ -class UserPathValidator < AbstractPathValidator - extend Gitlab::EncodingHelper - - def self.path_regex - Gitlab::PathRegex.root_namespace_path_regex - end - - def self.format_regex - Gitlab::PathRegex.namespace_format_regex - end - - def self.format_error_message - Gitlab::PathRegex.namespace_format_message - end -end diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index 96aae06a9df5f789cb9d4f4cee0530019f91f363..09a43a2cac5ee04083778c20176f305913528296 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -88,7 +88,7 @@ %strong.fly-out-top-item-name #{ _('Members') } - if current_user && can?(current_user, :admin_group, @group) - = nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do + = nav_link(path: group_nav_link_paths) do = link_to edit_group_path(@group) do .nav-icon-container = sprite_icon('settings') diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index e20e58d27e1466c5de6852187c5fa6e1ed806885..2b98cb9de9978f9747898d8fdef2622f641baaab 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -186,9 +186,9 @@ - if project_nav_tab? :clusters - show_cluster_hint = show_gke_cluster_integration_callout?(@project) = nav_link(controller: [:clusters, :user, :gcp]) do - = link_to project_clusters_path(@project), title: 'Cluster', class: 'shortcuts-cluster' do + = link_to project_clusters_path(@project), title: _('Kubernetes'), class: 'shortcuts-cluster' do %span - Clusters + = _('Kubernetes') - if show_cluster_hint .feature-highlight.js-feature-highlight{ disabled: true, data: { trigger: 'manual', @@ -206,13 +206,12 @@ %p = _('Protip:') = link_to 'Auto DevOps', help_page_path('topics/autodevops/index.md') - %span= _('uses clusters to deploy your code!') + %span= _('uses Kubernetes clusters to deploy your code!') %hr %button.btn.btn-create.btn-xs.dismiss-feature-highlight{ type: 'button' } %span= _("Got it!") = sprite_icon('thumb-up') - - if @project.feature_available?(:builds, current_user) && !@project.empty_repo? = nav_link(path: 'pipelines#charts') do = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do diff --git a/app/views/projects/clusters/_advanced_settings.html.haml b/app/views/projects/clusters/_advanced_settings.html.haml index 8a13713ae02ca5d6ac2d7397a94ee26c7bb90532..14979bee71444452cbc40827bef2f7f836cac74c 100644 --- a/app/views/projects/clusters/_advanced_settings.html.haml +++ b/app/views/projects/clusters/_advanced_settings.html.haml @@ -5,11 +5,11 @@ = s_('ClusterIntegration|Google Kubernetes Engine') %p - link_gke = link_to(s_('ClusterIntegration|Google Kubernetes Engine'), @cluster.gke_cluster_url, target: '_blank', rel: 'noopener noreferrer') - = s_('ClusterIntegration|Manage your cluster by visiting %{link_gke}').html_safe % { link_gke: link_gke } + = s_('ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}').html_safe % { link_gke: link_gke } .well.form-group %label.text-danger - = s_('ClusterIntegration|Remove cluster integration') + = s_('ClusterIntegration|Remove Kubernetes cluster integration') %p - = s_("ClusterIntegration|Remove this cluster's configuration from this project. This will not delete your actual cluster.") - = link_to(s_('ClusterIntegration|Remove integration'), namespace_project_cluster_path(@project.namespace, @project, @cluster.id), method: :delete, class: 'btn btn-danger', data: { confirm: s_("ClusterIntegration|Are you sure you want to remove this cluster's integration? This will not delete your actual cluster.")}) + = s_("ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster.") + = link_to(s_('ClusterIntegration|Remove integration'), namespace_project_cluster_path(@project.namespace, @project, @cluster.id), method: :delete, class: 'btn btn-danger', data: { confirm: s_("ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster.")}) diff --git a/app/views/projects/clusters/_banner.html.haml b/app/views/projects/clusters/_banner.html.haml index 26ca3307a4a5375ebc432bee5fef2fdc6a743cf3..f18caa3f4ac9ce45953e4fb5469485d6381a6ce5 100644 --- a/app/views/projects/clusters/_banner.html.haml +++ b/app/views/projects/clusters/_banner.html.haml @@ -1,14 +1,14 @@ -%h4= s_('ClusterIntegration|Cluster integration') +%h4= s_('ClusterIntegration|Kubernetes cluster integration') .settings-content .hidden.js-cluster-error.alert.alert-danger.alert-block.append-bottom-10{ role: 'alert' } - = s_('ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine') + = s_('ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine') %p.js-error-reason .hidden.js-cluster-creating.alert.alert-info.alert-block.append-bottom-10{ role: 'alert' } - = s_('ClusterIntegration|Cluster is being created on Google Kubernetes Engine...') + = s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...') .hidden.js-cluster-success.alert.alert-success.alert-block.append-bottom-10{ role: 'alert' } - = s_('ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster\'s details') + = s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details") - %p= s_('ClusterIntegration|Control how your cluster integrates with GitLab') + %p= s_('ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab') diff --git a/app/views/projects/clusters/_cluster.html.haml b/app/views/projects/clusters/_cluster.html.haml index 20ee8086f93468d1a60cbaa8d5786907053f1397..2d7f7c6b1fb1822a1a14e43cda83e0d265bb7061 100644 --- a/app/views/projects/clusters/_cluster.html.haml +++ b/app/views/projects/clusters/_cluster.html.haml @@ -1,6 +1,6 @@ .gl-responsive-table-row .table-section.section-30 - .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Cluster") + .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Kubernetes cluster") .table-mobile-content = link_to cluster.name, namespace_project_cluster_path(@project.namespace, @project, cluster) .table-section.section-30 @@ -14,7 +14,7 @@ .table-mobile-content %button.js-project-feature-toggle.project-feature-toggle{ type: "button", class: "#{'is-checked' if cluster.enabled?} #{'is-disabled' if !cluster.can_toggle_cluster?}", - "aria-label": s_("ClusterIntegration|Toggle Cluster"), + "aria-label": s_("ClusterIntegration|Toggle Kubernetes Cluster"), disabled: !cluster.can_toggle_cluster?, data: { endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } } %input.js-project-feature-toggle-input{ type: "hidden", value: cluster.enabled? } diff --git a/app/views/projects/clusters/_dropdown.html.haml b/app/views/projects/clusters/_dropdown.html.haml index e36dd900f8dbf2dfd46871e81e91e6c640ed8619..d55a9c60b64fefc258629add29aa88e9ee515dfa 100644 --- a/app/views/projects/clusters/_dropdown.html.haml +++ b/app/views/projects/clusters/_dropdown.html.haml @@ -1,4 +1,4 @@ -%h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up cluster integration') +%h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up Kubernetes cluster integration') .dropdown.clusters-dropdown %button.dropdown-menu-toggle.dropdown-menu-full-width{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false } @@ -7,6 +7,6 @@ = icon('chevron-down') %ul.dropdown-menu.clusters-dropdown-menu.dropdown-menu-full-width %li - = link_to(s_('ClusterIntegration|Create cluster on Google Kubernetes Engine'), gcp_new_namespace_project_clusters_path(@project.namespace, @project)) + = link_to(s_('ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine'), gcp_new_namespace_project_clusters_path(@project.namespace, @project)) %li - = link_to(s_('ClusterIntegration|Add an existing cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project)) + = link_to(s_('ClusterIntegration|Add an existing Kubernetes cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project)) diff --git a/app/views/projects/clusters/_empty_state.html.haml b/app/views/projects/clusters/_empty_state.html.haml index b525f4efc83680ebc6bf9553f7d8d01426d6475b..600d679b60c3ed52b4c663f3f596e92ee03796f9 100644 --- a/app/views/projects/clusters/_empty_state.html.haml +++ b/app/views/projects/clusters/_empty_state.html.haml @@ -3,10 +3,9 @@ .svg-content= image_tag 'illustrations/clusters_empty.svg' .col-xs-12 .text-content - %h4.text-center= s_('ClusterIntegration|Integrate cluster automation') - - link_to_help_page = link_to(s_('ClusterIntegration|Learn more about Clusters'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') - %p= s_('ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page} + %h4.text-center= s_('ClusterIntegration|Integrate Kubernetes cluster automation') + - link_to_help_page = link_to(s_('ClusterIntegration|Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') + %p= s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page} .text-center - = link_to s_('ClusterIntegration|Add cluster'), new_project_cluster_path(@project), class: 'btn btn-success' - + = link_to s_('ClusterIntegration|Add Kubernetes cluster'), new_project_cluster_path(@project), class: 'btn btn-success' diff --git a/app/views/projects/clusters/_integration_form.html.haml b/app/views/projects/clusters/_integration_form.html.haml index 0af6e6e05772193be78100079f32f05c70415dbe..d4c0cd82ce3e6a7e5e0c679f2d14f6f44293847c 100644 --- a/app/views/projects/clusters/_integration_form.html.haml +++ b/app/views/projects/clusters/_integration_form.html.haml @@ -5,15 +5,15 @@ %p - if @cluster.enabled? - if can?(current_user, :update_cluster, @cluster) - = s_('ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab\'s connection to it.') + = s_('ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab\'s connection to it.') - else - = s_('ClusterIntegration|Cluster integration is enabled for this project.') + = s_('ClusterIntegration|Kubernetes cluster integration is enabled for this project.') - else - = s_('ClusterIntegration|Cluster integration is disabled for this project.') + = s_('ClusterIntegration|Kubernetes cluster integration is disabled for this project.') %label.append-bottom-10.js-cluster-enable-toggle-area %button{ type: 'button', class: "js-project-feature-toggle project-feature-toggle #{'is-checked' if @cluster.enabled?} #{'is-disabled' unless can?(current_user, :update_cluster, @cluster)}", - "aria-label": s_("ClusterIntegration|Toggle Cluster"), + "aria-label": s_("ClusterIntegration|Toggle Kubernetes cluster"), disabled: !can?(current_user, :update_cluster, @cluster) } = field.hidden_field :enabled, { class: 'js-project-feature-toggle-input'} %span.toggle-icon @@ -23,7 +23,7 @@ .form-group %h5= s_('ClusterIntegration|Environment scope') %p - = s_("ClusterIntegration|Choose which of your project's environments will use this cluster.") + = s_("ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster.") = link_to s_("ClusterIntegration|Learn more about environments"), help_page_path('ci/environments') = field.text_field :environment_scope, class: 'form-control js-select-on-focus', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope') diff --git a/app/views/projects/clusters/_sidebar.html.haml b/app/views/projects/clusters/_sidebar.html.haml index 761879db32b4509a662092d06c88250a550a3112..73cd7c50922dcbadf857a875dc42d411bd48a28a 100644 --- a/app/views/projects/clusters/_sidebar.html.haml +++ b/app/views/projects/clusters/_sidebar.html.haml @@ -1,7 +1,7 @@ %h4.prepend-top-0 - = s_('ClusterIntegration|Cluster integration') + = s_('ClusterIntegration|Kubernetes cluster integration') %p - = s_('ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way.') + = s_('ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way.') %p - - link = link_to(s_('ClusterIntegration|cluster'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') + - link = link_to(_('Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') = s_('ClusterIntegration|Learn more about %{link_to_documentation}').html_safe % { link_to_documentation: link } diff --git a/app/views/projects/clusters/gcp/_form.html.haml b/app/views/projects/clusters/gcp/_form.html.haml index e384b60d8d94c111767161fb6f311832fb015591..5739a57dcfea11a4fc7eac07c964d0921733ed10 100644 --- a/app/views/projects/clusters/gcp/_form.html.haml +++ b/app/views/projects/clusters/gcp/_form.html.haml @@ -1,12 +1,12 @@ %p - link_to_help_page = link_to(s_('ClusterIntegration|help page'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') - = s_('ClusterIntegration|Read our %{link_to_help_page} on cluster integration.').html_safe % { link_to_help_page: link_to_help_page} + = s_('ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration.').html_safe % { link_to_help_page: link_to_help_page} = form_for @cluster, html: { class: 'prepend-top-20' }, url: gcp_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field| = form_errors(@cluster) .form-group - = field.label :name, s_('ClusterIntegration|Cluster name') - = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name') + = field.label :name, s_('ClusterIntegration|Kubernetes cluster name') + = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name') .form-group = field.label :environment_scope, s_('ClusterIntegration|Environment scope') = field.text_field :environment_scope, class: 'form-control', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope') @@ -32,4 +32,4 @@ = provider_gcp_field.text_field :machine_type, class: 'form-control', placeholder: 'n1-standard-4' .form-group - = field.submit s_('ClusterIntegration|Create cluster'), class: 'btn btn-success' + = field.submit s_('ClusterIntegration|Create Kubernetes cluster'), class: 'btn btn-success' diff --git a/app/views/projects/clusters/gcp/_header.html.haml b/app/views/projects/clusters/gcp/_header.html.haml index bddb902115d87e46a1999a3651897d709f7083c7..fa989943492c5e820ab9ad1a829b106a4bee20ea 100644 --- a/app/views/projects/clusters/gcp/_header.html.haml +++ b/app/views/projects/clusters/gcp/_header.html.haml @@ -1,5 +1,5 @@ %h4.prepend-top-20 - = s_('ClusterIntegration|Enter the details for your cluster') + = s_('ClusterIntegration|Enter the details for your Kubernetes cluster') %p = s_('ClusterIntegration|Please make sure that your Google account meets the following requirements:') %ul @@ -8,7 +8,7 @@ = s_('ClusterIntegration|Your account must have %{link_to_kubernetes_engine}').html_safe % { link_to_kubernetes_engine: link_to_kubernetes_engine } %li - link_to_requirements = link_to(s_('ClusterIntegration|meets the requirements'), 'https://cloud.google.com/kubernetes-engine/docs/quickstart?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer') - = s_('ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters').html_safe % { link_to_requirements: link_to_requirements } + = s_('ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters').html_safe % { link_to_requirements: link_to_requirements } %li - link_to_container_project = link_to(s_('ClusterIntegration|Google Kubernetes Engine project'), 'https://console.cloud.google.com/home/dashboard?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer') - = s_('ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below').html_safe % { link_to_container_project: link_to_container_project } + = s_('ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below').html_safe % { link_to_container_project: link_to_container_project } diff --git a/app/views/projects/clusters/gcp/_show.html.haml b/app/views/projects/clusters/gcp/_show.html.haml index f3122a1bf4742770e02ef060f61b23504b7f0a2f..78cd687ef93c366bcdd08a29e0774a271146ba11 100644 --- a/app/views/projects/clusters/gcp/_show.html.haml +++ b/app/views/projects/clusters/gcp/_show.html.haml @@ -1,10 +1,10 @@ .form-group %label.append-bottom-10{ for: 'cluster-name' } - = s_('ClusterIntegration|Cluster name') + = s_('ClusterIntegration|Kubernetes cluster name') .input-group %input.form-control.cluster-name.js-select-on-focus{ value: @cluster.name, readonly: true } %span.input-group-btn - = clipboard_button(text: @cluster.name, title: s_('ClusterIntegration|Copy cluster name'), class: 'btn-default') + = clipboard_button(text: @cluster.name, title: s_('ClusterIntegration|Copy Kubernetes cluster name'), class: 'btn-default') = form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| = form_errors(@cluster) diff --git a/app/views/projects/clusters/gcp/login.html.haml b/app/views/projects/clusters/gcp/login.html.haml index 878ebaded8874595ea8787c33132170aca06306e..dada51f39daaabf9bb887a3eb0ca560c7aaaf546 100644 --- a/app/views/projects/clusters/gcp/login.html.haml +++ b/app/views/projects/clusters/gcp/login.html.haml @@ -1,11 +1,11 @@ -- breadcrumb_title "Cluster" +- breadcrumb_title 'Kubernetes' - page_title _("Login") .row.prepend-top-default .col-sm-4 = render 'projects/clusters/sidebar' .col-sm-8 - = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Create cluster on Google Kubernetes Engine') + = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine') = render 'header' .row .col-sm-8.col-sm-offset-4.signin-with-google diff --git a/app/views/projects/clusters/gcp/new.html.haml b/app/views/projects/clusters/gcp/new.html.haml index 8d92fb1e320de2f2388b6a1a9f4469f228daf71f..ea78d66d88399b6c3bdaf7d096a16c8fc8c010c5 100644 --- a/app/views/projects/clusters/gcp/new.html.haml +++ b/app/views/projects/clusters/gcp/new.html.haml @@ -1,10 +1,10 @@ -- breadcrumb_title "Cluster" -- page_title _("New Cluster") +- breadcrumb_title 'Kubernetes' +- page_title _("New Kubernetes Cluster") .row.prepend-top-default .col-sm-4 = render 'projects/clusters/sidebar' .col-sm-8 - = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Create cluster on Google Kubernetes Engine') + = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine') = render 'header' = render 'form' diff --git a/app/views/projects/clusters/index.html.haml b/app/views/projects/clusters/index.html.haml index 74dbe859eeab55c0300387ca50c0930a81529b41..17b244f4bf7e18bf6dbdf54d419fecf03eee3005 100644 --- a/app/views/projects/clusters/index.html.haml +++ b/app/views/projects/clusters/index.html.haml @@ -1,5 +1,5 @@ -- breadcrumb_title "Clusters" -- page_title "Clusters" +- breadcrumb_title 'Kubernetes' +- page_title "Kubernetes Clusters" .clusters-container - if @clusters.empty? @@ -7,11 +7,11 @@ - else .top-area.adjust .nav-text - = s_("ClusterIntegration|Clusters can be used to deploy applications and to provide Review Apps for this project") + = s_("ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project") .ci-table.js-clusters-list .gl-responsive-table-row.table-row-header{ role: "row" } .table-section.section-30{ role: "rowheader" } - = s_("ClusterIntegration|Cluster") + = s_("ClusterIntegration|Kubernetes cluster") .table-section.section-30{ role: "rowheader" } = s_("ClusterIntegration|Environment scope") .table-section.section-30{ role: "rowheader" } diff --git a/app/views/projects/clusters/new.html.haml b/app/views/projects/clusters/new.html.haml index ddd13f8ea968b31bbbe3357d0033329cd5a57783..ebb7d247125813efbeafef2b784a92bfb00f162d 100644 --- a/app/views/projects/clusters/new.html.haml +++ b/app/views/projects/clusters/new.html.haml @@ -1,13 +1,13 @@ -- breadcrumb_title "Cluster" -- page_title _("Cluster") +- breadcrumb_title 'Kubernetes' +- page_title _("Kubernetes Cluster") .row.prepend-top-default .col-sm-4 = render 'sidebar' .col-sm-8 - %h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up cluster integration') + %h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up Kubernetes cluster integration') - %p= s_('ClusterIntegration|Create a new cluster on Google Kubernetes Engine right from GitLab') + %p= s_('ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab') = link_to s_('ClusterIntegration|Create on GKE'), gcp_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20' %p= s_('ClusterIntegration|Enter the details for an existing Kubernetes cluster') - = link_to s_('ClusterIntegration|Add an existing cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20' + = link_to s_('ClusterIntegration|Add an existing Kubernetes cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20' diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml index 2049105dff619a7f0ad7584c503001f3fb41b508..a60afde06d2764468715196f810f7bffa7711cb9 100644 --- a/app/views/projects/clusters/show.html.haml +++ b/app/views/projects/clusters/show.html.haml @@ -1,7 +1,7 @@ - @content_class = "limit-container-width" unless fluid_layout -- add_to_breadcrumbs "Clusters", project_clusters_path(@project) +- add_to_breadcrumbs "Kubernetes Clusters", project_clusters_path(@project) - breadcrumb_title @cluster.name -- page_title _("Cluster") +- page_title _("Kubernetes Cluster") - expanded = Rails.env.test? @@ -13,7 +13,8 @@ toggle_status: @cluster.enabled? ? 'true': 'false', cluster_status: @cluster.status_name, cluster_status_reason: @cluster.status_reason, - help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications') } } + help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'), + ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-ip-address') } } .js-cluster-application-notice .flash-container @@ -26,10 +27,10 @@ %section.settings#js-cluster-details{ class: ('expanded' if expanded) } .settings-header - %h4= s_('ClusterIntegration|Cluster details') + %h4= s_('ClusterIntegration|Kubernetes cluster details') %button.btn.js-settings-toggle = expanded ? 'Collapse' : 'Expand' - %p= s_('ClusterIntegration|See and edit the details for your cluster') + %p= s_('ClusterIntegration|See and edit the details for your Kubernetes cluster') .settings-content - if @cluster.managed? = render 'projects/clusters/gcp/show' @@ -41,6 +42,6 @@ %h4= _('Advanced settings') %button.btn.js-settings-toggle = expanded ? 'Collapse' : 'Expand' - %p= s_("ClusterIntegration|Advanced options on this cluster's integration") + %p= s_("ClusterIntegration|Advanced options on this Kubernetes cluster's integration") .settings-content = render 'advanced_settings' diff --git a/app/views/projects/clusters/user/_form.html.haml b/app/views/projects/clusters/user/_form.html.haml index babfca0c5672554f43407a06211941fcbb1a4db1..2e92524ce8fe29e1c16796a2c097646fae408610 100644 --- a/app/views/projects/clusters/user/_form.html.haml +++ b/app/views/projects/clusters/user/_form.html.haml @@ -1,8 +1,8 @@ = form_for @cluster, url: user_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field| = form_errors(@cluster) .form-group - = field.label :name, s_('ClusterIntegration|Cluster name') - = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name') + = field.label :name, s_('ClusterIntegration|Kubernetes cluster name') + = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name') .form-group = field.label :environment_scope, s_('ClusterIntegration|Environment scope') = field.text_field :environment_scope, class: 'form-control', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope') @@ -25,4 +25,4 @@ = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') .form-group - = field.submit s_('ClusterIntegration|Add cluster'), class: 'btn btn-success' + = field.submit s_('ClusterIntegration|Add Kubernetes cluster'), class: 'btn btn-success' diff --git a/app/views/projects/clusters/user/_header.html.haml b/app/views/projects/clusters/user/_header.html.haml index 06ac210a06df0c24bc00cc59eba300eeec6ae665..04c7ce96a4b2aaf4422dcc5863f6c96aec19ca15 100644 --- a/app/views/projects/clusters/user/_header.html.haml +++ b/app/views/projects/clusters/user/_header.html.haml @@ -1,5 +1,5 @@ %h4.prepend-top-20 - = s_('ClusterIntegration|Enter the details for your cluster') + = s_('ClusterIntegration|Enter the details for your Kubernetes cluster') %p - link_to_help_page = link_to(s_('ClusterIntegration|documentation'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') - = s_('ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters').html_safe % { link_to_help_page: link_to_help_page } + = s_('ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes').html_safe % { link_to_help_page: link_to_help_page } diff --git a/app/views/projects/clusters/user/_show.html.haml b/app/views/projects/clusters/user/_show.html.haml index 5931e0b7f179b57ac425078ac2e15abf43f62dbc..ebbf7e775c7c8015d99eab3e6c2a8c24134d5901 100644 --- a/app/views/projects/clusters/user/_show.html.haml +++ b/app/views/projects/clusters/user/_show.html.haml @@ -1,8 +1,8 @@ = form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| = form_errors(@cluster) .form-group - = field.label :name, s_('ClusterIntegration|Cluster name') - = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name') + = field.label :name, s_('ClusterIntegration|Kubernetes cluster name') + = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name') = field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field| .form-group diff --git a/app/views/projects/clusters/user/new.html.haml b/app/views/projects/clusters/user/new.html.haml index 68f38f834530af762feade81a5bc200f0caea036..7fb75cd9cc720f9aad88424d4a75b937d19d4991 100644 --- a/app/views/projects/clusters/user/new.html.haml +++ b/app/views/projects/clusters/user/new.html.haml @@ -1,11 +1,11 @@ -- breadcrumb_title "Cluster" -- page_title _("New Cluster") +- breadcrumb_title 'Kubernetes' +- page_title _("New Kubernetes cluster") .row.prepend-top-default .col-sm-4 = render 'projects/clusters/sidebar' .col-sm-8 - = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Add an existing cluster') + = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Add an existing Kubernetes cluster') = render 'header' .prepend-top-20 = render 'form' diff --git a/app/views/shared/_delete_label_modal.html.haml b/app/views/shared/_delete_label_modal.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..01effefc34def8fca2ba393b87d4f7e2ba943534 --- /dev/null +++ b/app/views/shared/_delete_label_modal.html.haml @@ -0,0 +1,20 @@ +.modal{ id: "modal-delete-label-#{label.id}", tabindex: -1 } + .modal-dialog + .modal-content + .modal-header + %button.close{ data: {dismiss: 'modal' } } × + %h3.page-title Delete #{render_colored_label(label, tooltip: false)} ? + + .modal-body + %p + %strong= label.name + %span will be permanently deleted from #{label.is_a?(ProjectLabel)? label.project.name : label.group.name}. This cannot be undone. + + .modal-footer + %a{ href: '#', data: { dismiss: 'modal' }, class: 'btn btn-default' } Cancel + + = link_to 'Delete label', + destroy_label_path(label), + title: 'Delete', + method: :delete, + class: 'btn btn-remove' diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 8e88cecaf9e6dfd58520ccaac9cd571468da34d7..c0eebdfadddac658061aa5ecaf3a19cd9d84ea4c 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -5,10 +5,10 @@ - show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) - show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) -%li{ id: label_css_id, data: { id: label.id } } +%li.label-list-item{ id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label - .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown + .visible-xs.visible-sm-inline-block.dropdown %button.btn.btn-default.label-options-toggle{ type: 'button', data: { toggle: "dropdown" } } Options = icon('caret-down') @@ -46,14 +46,19 @@ data: {confirm: 'Remove this label? Are you sure?'}, class: 'text-danger' - .pull-right.hidden-xs.hidden-sm.hidden-md - - if show_label_merge_requests_link - = link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action btn-link') do - view merge requests - - if show_label_issues_link - = link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action btn-link') do - view open issues - + .pull-right.hidden-xs.hidden-sm + - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group) + = link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "You are about to promote #{label.title} to a group level. This will make this milestone available to all projects inside #{label.project.group.name}. The existing project label will be merged into the group level. This action cannot be reversed.", toggle: "tooltip"}, method: :post do + %span.sr-only Promote to Group + = sprite_icon('level-up') + - if can?(current_user, :admin_label, label) + = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do + %span.sr-only Edit + = sprite_icon('pencil') + %span{ data: { toggle: 'modal', target: "#modal-delete-label-#{label.id}" } } + = link_to "#", title: "Delete", class: 'btn btn-transparent btn-action remove-row', data: { toggle: "tooltip" } do + %span.sr-only Delete + = sprite_icon('remove') - if current_user .label-subscription.inline - if can_subscribe_to_label_in_different_levels?(label) @@ -76,14 +81,4 @@ %span= label_subscription_toggle_button_text(label, @project) = icon('spinner spin', class: 'label-subscribe-button-loading') - - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group) - = link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "You are about to promote #{label.title} to a group level. This will make this milestone available to all projects inside #{label.project.group.name}. The existing project label will be merged into the group level. This action cannot be reversed.", toggle: "tooltip"}, method: :post do - %span.sr-only Promote to Group - = icon('level-up') - - if can?(current_user, :admin_label, label) - = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do - %span.sr-only Edit - = icon('pencil-square-o') - = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, data: {confirm: label_deletion_confirm_text(label), toggle: "tooltip"} do - %span.sr-only Delete - = icon('trash-o') += render 'shared/delete_label_modal', label: label diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 7f58298c60fc70ab7c2f85fffa615c534d844b5b..bd4f191502e7e2f609863c838dc4f48bf7b697d2 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -1,3 +1,7 @@ +- subject = local_assigns[:subject] +- show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project) +- show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) + %span.label-row - if can?(current_user, :admin_label, @project) .draggable-handler @@ -13,6 +17,14 @@ - if defined?(@project) && @project.group.present? %span.label-type = label.model_name.human.titleize - - if label.description - %span.label-description - = markdown_field(label, :description) + + %span.label-description + - if label.description.present? + .description-text + = markdown_field(label, :description) + .hidden-xs.hidden-sm + - if show_label_issues_link + = link_to_label(label, subject: subject) { 'Issues' } + - if show_label_merge_requests_link + · + = link_to_label(label, subject: subject, type: :merge_request) { 'Merge requests' } diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 50e876b1d1936651ba9ff3a5edccfcb39b02ee98..f2c20114534791eeb9e6ecd1f6811305f4736ba5 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -43,6 +43,7 @@ - pipeline_creation:run_pipeline_schedule - pipeline_default:build_coverage - pipeline_default:build_trace_sections +- pipeline_default:create_trace_artifact - pipeline_default:pipeline_metrics - pipeline_default:pipeline_notification - pipeline_default:update_head_pipeline_for_merge_request diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb index 97d80305bec92bb4bb3b3074edc343073ef1c90d..b5ed8d607b350ce4bd2ba324565739fbbd4839e3 100644 --- a/app/workers/build_finished_worker.rb +++ b/app/workers/build_finished_worker.rb @@ -6,9 +6,13 @@ class BuildFinishedWorker def perform(build_id) Ci::Build.find_by(id: build_id).try do |build| - BuildTraceSectionsWorker.perform_async(build.id) + # We execute that in sync as this access the files in order to access local file, and reduce IO + BuildTraceSectionsWorker.new.perform(build.id) BuildCoverageWorker.new.perform(build.id) - BuildHooksWorker.new.perform(build.id) + + # We execute that async as this are two indepentent operations that can be executed after TraceSections and Coverage + BuildHooksWorker.perform_async(build.id) + CreateTraceArtifactWorker.perform_async(build.id) end end end diff --git a/app/workers/create_trace_artifact_worker.rb b/app/workers/create_trace_artifact_worker.rb new file mode 100644 index 0000000000000000000000000000000000000000..11cda58021e9a2aec0643bd71e10c6836979a644 --- /dev/null +++ b/app/workers/create_trace_artifact_worker.rb @@ -0,0 +1,10 @@ +class CreateTraceArtifactWorker + include ApplicationWorker + include PipelineQueue + + def perform(job_id) + Ci::Build.preload(:project, :user).find_by(id: job_id).try do |job| + Ci::CreateTraceArtifactService.new(job.project, job.user).execute(job) + end + end +end diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb index f19bcbf946a3d70c3a40bef3cc04b25207c24277..a993b4b2680f251f20caea957b5ebafc0221c4e3 100644 --- a/app/workers/project_cache_worker.rb +++ b/app/workers/project_cache_worker.rb @@ -18,6 +18,8 @@ class ProjectCacheWorker update_statistics(project, statistics.map(&:to_sym)) project.repository.refresh_method_caches(files.map(&:to_sym)) + + project.cleanup end def update_statistics(project, statistics = []) diff --git a/changelogs/unreleased/14256-upload-destroy-removes-file.yml b/changelogs/unreleased/14256-upload-destroy-removes-file.yml new file mode 100644 index 0000000000000000000000000000000000000000..d97188e23f174e1dfb12349496b3df174b0e6bfe --- /dev/null +++ b/changelogs/unreleased/14256-upload-destroy-removes-file.yml @@ -0,0 +1,5 @@ +--- +title: Deleting an upload will correctly clean up the filesystem. +merge_request: 16799 +author: +type: fixed diff --git a/changelogs/unreleased/26388-push-to-create-a-new-project.yml b/changelogs/unreleased/26388-push-to-create-a-new-project.yml new file mode 100644 index 0000000000000000000000000000000000000000..f641fcced37670255fae4e8221cd1d89dc767d34 --- /dev/null +++ b/changelogs/unreleased/26388-push-to-create-a-new-project.yml @@ -0,0 +1,5 @@ +--- +title: User can now git push to create a new project +merge_request: 16547 +author: +type: added diff --git a/changelogs/unreleased/26468-fix-sort-by-recent-sign-in.yml b/changelogs/unreleased/26468-fix-sort-by-recent-sign-in.yml new file mode 100644 index 0000000000000000000000000000000000000000..a2c81f6c995c0a676691ec371022f5c0b600ebef --- /dev/null +++ b/changelogs/unreleased/26468-fix-sort-by-recent-sign-in.yml @@ -0,0 +1,4 @@ +--- +title: Fix Sort by Recent Sign-in in Admin Area +merge_request: 13852 +author: Poornima M diff --git a/changelogs/unreleased/42669-allow-order_by-users-in-gitlab-api.yml b/changelogs/unreleased/42669-allow-order_by-users-in-gitlab-api.yml new file mode 100644 index 0000000000000000000000000000000000000000..24fcc38ee0e976cb70edf9376139855dfc4f2d56 --- /dev/null +++ b/changelogs/unreleased/42669-allow-order_by-users-in-gitlab-api.yml @@ -0,0 +1,5 @@ +--- +title: Add sorting options for /users API (admin only) +merge_request: 16945 +author: +type: added diff --git a/changelogs/unreleased/42693-42693-add-a-link-to-documentation-on-how-to-get-external-ip-in-the-kubernetes-cluster-details-page.yml b/changelogs/unreleased/42693-42693-add-a-link-to-documentation-on-how-to-get-external-ip-in-the-kubernetes-cluster-details-page.yml new file mode 100644 index 0000000000000000000000000000000000000000..aeadf8ffc4a13b2aab6085c2f910d539731eaeb8 --- /dev/null +++ b/changelogs/unreleased/42693-42693-add-a-link-to-documentation-on-how-to-get-external-ip-in-the-kubernetes-cluster-details-page.yml @@ -0,0 +1,5 @@ +--- +title: Add a link to documentation on how to get external ip in the Kubernetes cluster details page +merge_request: 16937 +author: +type: added diff --git a/changelogs/unreleased/42730-close-rugged-repository.yml b/changelogs/unreleased/42730-close-rugged-repository.yml new file mode 100644 index 0000000000000000000000000000000000000000..a632f5030a5e88ebd5ad6763ec0dfb97b9a22aea --- /dev/null +++ b/changelogs/unreleased/42730-close-rugged-repository.yml @@ -0,0 +1,5 @@ +--- +title: Close low level rugged repository in project cache worker +merge_request: 16930 +author: Bastian Blank +type: fixed diff --git a/changelogs/unreleased/bump-workhorse.yml b/changelogs/unreleased/bump-workhorse.yml new file mode 100644 index 0000000000000000000000000000000000000000..37ee402dac70fae2ae2a2c452dc31764a2f134c8 --- /dev/null +++ b/changelogs/unreleased/bump-workhorse.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade GitLab Workhorse to v3.6.0 +merge_request: +author: +type: other diff --git a/changelogs/unreleased/dm-route-path-validation.yml b/changelogs/unreleased/dm-route-path-validation.yml new file mode 100644 index 0000000000000000000000000000000000000000..df3ed1de1b9b6a38336b0a175fe99189b6ee26ee --- /dev/null +++ b/changelogs/unreleased/dm-route-path-validation.yml @@ -0,0 +1,5 @@ +--- +title: Validate user, group and project paths consistently, and only once +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/dm-user-namespace-route-path-validation.yml b/changelogs/unreleased/dm-user-namespace-route-path-validation.yml new file mode 100644 index 0000000000000000000000000000000000000000..36615e5b9764ad540cc44c514d7668d08e75d7e6 --- /dev/null +++ b/changelogs/unreleased/dm-user-namespace-route-path-validation.yml @@ -0,0 +1,5 @@ +--- +title: Validate user namespace before saving so that errors persist on model +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/feature-sm-artifacts-trace.yml b/changelogs/unreleased/feature-sm-artifacts-trace.yml new file mode 100644 index 0000000000000000000000000000000000000000..7654ce58aeb3a5663e921ba4001cb7ff89d4c105 --- /dev/null +++ b/changelogs/unreleased/feature-sm-artifacts-trace.yml @@ -0,0 +1,5 @@ +--- +title: Save traces as artifacts +merge_request: 16702 +author: +type: changed diff --git a/changelogs/unreleased/fix-show-sidebar-sub-level-items-for-billing.yml b/changelogs/unreleased/fix-show-sidebar-sub-level-items-for-billing.yml new file mode 100644 index 0000000000000000000000000000000000000000..883eecabe041f7fa3b7bab6b64d7892e0e10657e --- /dev/null +++ b/changelogs/unreleased/fix-show-sidebar-sub-level-items-for-billing.yml @@ -0,0 +1,5 @@ +--- +title: Override group sidebar links +merge_request: 16942 +author: George Tsiolis +type: fixed diff --git a/changelogs/unreleased/jej-upload-file-tracks-lfs.yml b/changelogs/unreleased/jej-upload-file-tracks-lfs.yml new file mode 100644 index 0000000000000000000000000000000000000000..a7cf6b6ba2ca3c956f2ca13b6b12a415b37a7da7 --- /dev/null +++ b/changelogs/unreleased/jej-upload-file-tracks-lfs.yml @@ -0,0 +1,5 @@ +--- +title: File Upload UI can create LFS pointers based on .gitattributes +merge_request: 16412 +author: +type: fixed diff --git a/changelogs/unreleased/winh-kubernetes-clusters.yml b/changelogs/unreleased/winh-kubernetes-clusters.yml new file mode 100644 index 0000000000000000000000000000000000000000..387a719848d0cad20e6170e8eb96d1b79cf751bb --- /dev/null +++ b/changelogs/unreleased/winh-kubernetes-clusters.yml @@ -0,0 +1,5 @@ +--- +title: Replace "cluster" with "Kubernetes cluster" +merge_request: 16778 +author: +type: changed diff --git a/changelogs/unreleased/zj-protobuf.yml b/changelogs/unreleased/zj-protobuf.yml new file mode 100644 index 0000000000000000000000000000000000000000..830c2e82da9c7932d7dc78b109878a06e64b9258 --- /dev/null +++ b/changelogs/unreleased/zj-protobuf.yml @@ -0,0 +1,5 @@ +--- +title: Downgrade google-protobuf gem +merge_request: 16941 +author: +type: other diff --git a/db/migrate/20180206200543_reset_events_primary_key_sequence.rb b/db/migrate/20180206200543_reset_events_primary_key_sequence.rb new file mode 100644 index 0000000000000000000000000000000000000000..eb5c4a6a1e72dfe005850e2b519f0dd447a3cbb5 --- /dev/null +++ b/db/migrate/20180206200543_reset_events_primary_key_sequence.rb @@ -0,0 +1,35 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class ResetEventsPrimaryKeySequence < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + class Event < ActiveRecord::Base + self.table_name = 'events' + end + + def up + if Gitlab::Database.postgresql? + reset_primary_key_for_postgresql + else + reset_primary_key_for_mysql + end + end + + def down + # No-op + end + + def reset_primary_key_for_postgresql + reset_pk_sequence!(Event.table_name) + end + + def reset_primary_key_for_mysql + amount = Event.pluck('COALESCE(MAX(id), 1)').first + + execute "ALTER TABLE #{Event.table_name} AUTO_INCREMENT = #{amount}" + end +end diff --git a/db/schema.rb b/db/schema.rb index 82ebe4369dd5bfc8dedba8bab6e1ad206e089849..41f313c904fcfee9aacc41b35c4f4fb35f870e0a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180204200836) do +ActiveRecord::Schema.define(version: 20180206200543) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/doc/api/users.md b/doc/api/users.md index 1da6fcf297dec0dd4f2f8e3fa38cfa5f66b2889d..2082e45756a8547618999755d0eb6de2dbaebf7b 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -51,6 +51,11 @@ GET /users?blocked=true GET /users ``` +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `order_by` | string | no | Return projects ordered by `id`, `name`, `username`, `created_at`, or `updated_at` fields. Default is `id` | +| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | + ```json [ { diff --git a/doc/development/file_storage.md b/doc/development/file_storage.md index 76354b928203d469e25951de1bbd9c8315167c04..34a02bd2c3cd4e2cbda0ef31d8b6b29291985013 100644 --- a/doc/development/file_storage.md +++ b/doc/development/file_storage.md @@ -16,7 +16,7 @@ There are many places where file uploading is used, according to contexts: - Project avatars - Issues/MR/Notes Markdown attachments - Issues/MR/Notes Legacy Markdown attachments - - CI Build Artifacts + - CI Artifacts (archive, metadata, trace) - LFS Objects @@ -35,7 +35,7 @@ they are still not 100% standardized. You can see them below: | Project avatars | yes | uploads/-/system/project/avatar/:id/:filename | `AvatarUploader` | Project | | Issues/MR/Notes Markdown attachments | yes | uploads/:project_path_with_namespace/:random_hex/:filename | `FileUploader` | Project | | Issues/MR/Notes Legacy Markdown attachments | no | uploads/-/system/note/attachment/:id/:filename | `AttachmentUploader` | Note | -| CI Artifacts (CE) | yes | shared/artifacts/:year_:month/:project_id/:id | `ArtifactUploader` | Ci::Build | +| CI Artifacts (CE) | yes | shared/artifacts/:disk_hash[0..1]/:disk_hash[2..3]/:disk_hash/:year_:month_:date/:job_id/:job_artifact_id (:disk_hash is SHA256 digest of project_id) | `JobArtifactUploader` | Ci::JobArtifact | | LFS Objects (CE) | yes | shared/lfs-objects/:hex/:hex/:object_hash | `LfsObjectUploader` | LfsObject | CI Artifacts and LFS Objects behave differently in CE and EE. In CE they inherit the `GitlabUploader` diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md index e18711f3392664c8d1beeb8a8b40fa33ca97dcf0..7b87039da84d655ed0751f417ec583fdecce07eb 100644 --- a/doc/gitlab-basics/create-project.md +++ b/doc/gitlab-basics/create-project.md @@ -33,5 +33,40 @@ 1. Click **Create project**. +## Push to create a new project + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/26388) in GitLab 10.5. + +When you create a new repo locally, instead of going to GitLab to manually +create a new project and then push the repo, you can directly push it to +GitLab to create the new project, all without leaving your terminal. If you have access to that +namespace, we will automatically create a new project under that GitLab namespace with its +visibility set to private by default (you can later change it in the UI). + +This can be done by using either SSH or HTTP: + +``` +## Git push using SSH +git push git@gitlab.example.com:namespace/nonexistent-project.git + +## Git push using HTTP +git push https://gitlab.example.com/namespace/nonexistent-project.git +``` + +Once the push finishes successfully, a remote message will indicate +the command to set the remote and the URL to the new project: + +``` +remote: +remote: The private project namespace/nonexistent-project was created. +remote: +remote: To configure the remote, run: +remote: git remote add origin https://gitlab.example.com/namespace/nonexistent-project.git +remote: +remote: To view the project, visit: +remote: https://gitlab.example.com/namespace/nonexistent-project +remote: +``` + [import it]: ../workflow/importing/README.md [reserved]: ../user/reserved_names.md diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md index 96968c1e3aba794965aa4c26513d7ef1e8c656f6..84eeacac3fdd9810d91be346d869f9482a930429 100644 --- a/doc/install/kubernetes/gitlab_chart.md +++ b/doc/install/kubernetes/gitlab_chart.md @@ -1,17 +1,17 @@ # GitLab Helm Chart -> **Note**: -* This chart is deprecated, and is being replaced by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). For more information on available charts, please see our [overview](index.md#chart-overview). -* These charts have been tested on Google Kubernetes Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues). +> **Note:** +* This chart has been tested on Google Kubernetes Engine and Azure Container Service. + +**This chart is deprecated.** For small installations on Kubernetes today, we recommend the beta [`gitlab-omnibus` Helm chart](gitlab_omnibus.md). +A new [cloud native GitLab chart](index.md#cloud-native-gitlab-chart) is in development with increased scalability and resilience, among other benefits. The cloud native chart will replace both the `gitlab` and `gitlab-omnibus` charts when available later this year. -For more information on available GitLab Helm Charts, please see our [overview](index.md#chart-overview). +Due to the significant architectural changes, migrating will require backing up data out of this instance and restoring it into the new deployment. For more information on available GitLab Helm Charts, please see our [overview](index.md#chart-overview). ## Introduction The `gitlab` Helm chart deploys just GitLab into your Kubernetes cluster, and offers extensive configuration options. This chart requires advanced knowledge of Kubernetes to successfully use. We **strongly recommend** the [gitlab-omnibus](gitlab_omnibus.md) chart. -This chart is deprecated, and will be replaced by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). Due to the difficulty in supporting upgrades, migrating will require exporting data out of this instance and importing it into the new deployment. - This chart includes the following: - Deployment using the [gitlab-ce](https://hub.docker.com/r/gitlab/gitlab-ce) or [gitlab-ee](https://hub.docker.com/r/gitlab/gitlab-ee) container image diff --git a/doc/install/kubernetes/gitlab_omnibus.md b/doc/install/kubernetes/gitlab_omnibus.md index 5a5f8d67ff52635d665220021d731cef582b72a7..9c5258c2cdfaf16545735cbf962c28fdff4406d9 100644 --- a/doc/install/kubernetes/gitlab_omnibus.md +++ b/doc/install/kubernetes/gitlab_omnibus.md @@ -1,17 +1,18 @@ # GitLab-Omnibus Helm Chart -> **Note:** -* This Helm chart is in beta, and will be deprecated by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). -* These charts have been tested on Google Kubernetes Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/charts.gitlab.io/issues). +> **Note:**. +* This chart has been tested on Google Kubernetes Engine and Azure Container Service. -This work is based partially on: https://github.com/lwolf/kubernetes-gitlab/. GitLab would like to thank Sergey Nuzhdin for his work. +**[This chart is beta](#limitations), and is the best way to install GitLab on Kubernetes today.** A new [cloud native GitLab chart](index.md#cloud-native-gitlab-chart) is in development with increased scalability and resilience, among other benefits. Once available, the cloud native chart will be the recommended installation method for Kubernetes, and this chart will be deprecated. For more information on available GitLab Helm Charts, please see our [overview](index.md#chart-overview). +This work is based partially on: https://github.com/lwolf/kubernetes-gitlab/. GitLab would like to thank Sergey Nuzhdin for his work. + ## Introduction This chart provides an easy way to get started with GitLab, provisioning an installation with nearly all functionality enabled. SSL is automatically provisioned via [Let's Encrypt](https://letsencrypt.org/). -This Helm chart is in beta, and will be deprecated by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md) once available. Due to the difficulty in supporting upgrades, migrating will require exporting data out of this instance and importing it into the new deployment. +This Helm chart is in beta, and is suited for small to medium deployments. It will be deprecated by the [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md) once available. Due to the significant architectural changes, migrating will require backing up data out of this instance and importing it into the new deployment. The deployment includes: diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index bd3011b1cd8f95a3e922e01564c6ced927b6f4b4..959cf7d3e54509b64080a89fe8003d04b7f37065 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -154,8 +154,8 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps step 'commit has ci status' do @project.enable_ci - pipeline = create :ci_pipeline, project: @project, sha: sample_commit.id - create :ci_build, pipeline: pipeline + @pipeline = create(:ci_pipeline, project: @project, sha: sample_commit.id) + create(:ci_build, pipeline: @pipeline) end step 'repository contains ".gitlab-ci.yml" file' do @@ -163,7 +163,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I see commit ci info' do - expect(page).to have_content "Pipeline #1 pending" + expect(page).to have_content "Pipeline ##{@pipeline.id} pending" end step 'I search "submodules" commits' do diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb index 196e0fff63a09a04557c88f703e362932874c04e..4df96e081f98aacadb9f46f7f44b75d14e406347 100644 --- a/features/steps/project/issues/labels.rb +++ b/features/steps/project/issues/labels.rb @@ -15,8 +15,9 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps step 'I delete all labels' do page.within '.labels' do - page.all('.remove-row').each do - accept_confirm { first('.remove-row').click } + page.all('.label-list-item').each do + first('.remove-row').click + first(:link, 'Delete label').click end end end diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index eb67de81a0d612fad451ee56aa2701163d0a9992..cd59da6fc7050efeb79dd106a8ba137fdb62ca4b 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -60,8 +60,20 @@ module API false end + def project_path + project&.path || project_path_match[:project_path] + end + + def namespace_path + project&.namespace&.full_path || project_path_match[:namespace_path] + end + private + def project_path_match + @project_path_match ||= params[:project].match(Gitlab::PathRegex.full_project_git_path_regex) || {} + end + # rubocop:disable Gitlab/ModuleWithInstanceVariables def set_project if params[:gl_repository] diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 063f0d6599c34e696fd1f74d4ea9af3a02173d24..9285fb90cdc8ccb27f80b59c44bebdfad41e6f29 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -42,11 +42,14 @@ module API end access_checker_klass = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess - access_checker = access_checker_klass - .new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities, redirected_path: redirected_path) + access_checker = access_checker_klass.new(actor, project, + protocol, authentication_abilities: ssh_authentication_abilities, + namespace_path: namespace_path, project_path: project_path, + redirected_path: redirected_path) begin access_checker.check(params[:action], params[:changes]) + @project ||= access_checker.project rescue Gitlab::GitAccess::UnauthorizedError, Gitlab::GitAccess::NotFoundError => e return { status: false, message: e.message } end @@ -207,8 +210,11 @@ module API # A user is not guaranteed to be returned; an orphaned write deploy # key could be used if user - redirect_message = Gitlab::Checks::ProjectMoved.fetch_redirect_message(user.id, project.id) + redirect_message = Gitlab::Checks::ProjectMoved.fetch_message(user.id, project.id) + project_created_message = Gitlab::Checks::ProjectCreated.fetch_message(user.id, project.id) + output[:redirected_message] = redirect_message if redirect_message + output[:project_created_message] = project_created_message if project_created_message end output diff --git a/lib/api/users.rb b/lib/api/users.rb index c7c2aa280d5f278ab4ae68ce95c357d016e66c95..3cc12724b8aa1bcf4af4d7ff588c55d12fed48c8 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -18,6 +18,14 @@ module API User.find_by(id: id) || not_found!('User') end + def reorder_users(users) + if params[:order_by] && params[:sort] + users.reorder(params[:order_by] => params[:sort]) + else + users + end + end + params :optional_attributes do optional :skype, type: String, desc: 'The Skype username' optional :linkedin, type: String, desc: 'The LinkedIn username' @@ -35,6 +43,13 @@ module API optional :avatar, type: File, desc: 'Avatar image for user' all_or_none_of :extern_uid, :provider end + + params :sort_params do + optional :order_by, type: String, values: %w[id name username created_at updated_at], + default: 'id', desc: 'Return users ordered by a field' + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return users sorted in ascending and descending order' + end end desc 'Get the list of users' do @@ -53,16 +68,18 @@ module API optional :created_before, type: DateTime, desc: 'Return users created before the specified time' all_or_none_of :extern_uid, :provider + use :sort_params use :pagination end get do authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?) unless current_user&.admin? - params.except!(:created_after, :created_before) + params.except!(:created_after, :created_before, :order_by, :sort) end users = UsersFinder.new(current_user, params).execute + users = reorder_users(users) authorized = can?(current_user, :read_users_list) diff --git a/lib/carrier_wave_string_file.rb b/lib/carrier_wave_string_file.rb new file mode 100644 index 0000000000000000000000000000000000000000..6c848902e4a20a5f832412c6dfb71b2a5ceb3044 --- /dev/null +++ b/lib/carrier_wave_string_file.rb @@ -0,0 +1,5 @@ +class CarrierWaveStringFile < StringIO + def original_filename + "" + end +end diff --git a/lib/constraints/user_url_constrainer.rb b/lib/constraints/user_url_constrainer.rb index b7633aa7cbbbec73794de62555c590de9d70ca3b..3b3ed1c6ddb13efd1b6400197fafa80ab6252769 100644 --- a/lib/constraints/user_url_constrainer.rb +++ b/lib/constraints/user_url_constrainer.rb @@ -2,7 +2,7 @@ class UserUrlConstrainer def matches?(request) full_path = request.params[:username] - return false unless UserPathValidator.valid_path?(full_path) + return false unless NamespacePathValidator.valid_path?(full_path) User.find_by_full_path(full_path, follow_redirects: request.get?).present? end diff --git a/lib/gitlab/checks/force_push.rb b/lib/gitlab/checks/force_push.rb index dc5d285ea65bf79a2c787c7f9a4cadc84cd4213a..c9c3050cfc26a3133e2a8a6580c1d048d2f9cd5b 100644 --- a/lib/gitlab/checks/force_push.rb +++ b/lib/gitlab/checks/force_push.rb @@ -15,8 +15,8 @@ module Gitlab .ancestor?(oldrev, newrev) else Gitlab::Git::RevList.new( - path_to_repo: project.repository.path_to_repo, - oldrev: oldrev, newrev: newrev).missed_ref.present? + project.repository.raw, oldrev: oldrev, newrev: newrev + ).missed_ref.present? end end end diff --git a/lib/gitlab/checks/post_push_message.rb b/lib/gitlab/checks/post_push_message.rb new file mode 100644 index 0000000000000000000000000000000000000000..473c0385b34c2d624ea029dde01aec069b5eaa0c --- /dev/null +++ b/lib/gitlab/checks/post_push_message.rb @@ -0,0 +1,46 @@ +module Gitlab + module Checks + class PostPushMessage + def initialize(project, user, protocol) + @project = project + @user = user + @protocol = protocol + end + + def self.fetch_message(user_id, project_id) + key = message_key(user_id, project_id) + + Gitlab::Redis::SharedState.with do |redis| + message = redis.get(key) + redis.del(key) + message + end + end + + def add_message + return unless user.present? && project.present? + + Gitlab::Redis::SharedState.with do |redis| + key = self.class.message_key(user.id, project.id) + redis.setex(key, 5.minutes, message) + end + end + + def message + raise NotImplementedError + end + + protected + + attr_reader :project, :user, :protocol + + def self.message_key(user_id, project_id) + raise NotImplementedError + end + + def url_to_repo + protocol == 'ssh' ? project.ssh_url_to_repo : project.http_url_to_repo + end + end + end +end diff --git a/lib/gitlab/checks/project_created.rb b/lib/gitlab/checks/project_created.rb new file mode 100644 index 0000000000000000000000000000000000000000..cec270d6a585f1f08244b55188afc8cd0ba66b5c --- /dev/null +++ b/lib/gitlab/checks/project_created.rb @@ -0,0 +1,31 @@ +module Gitlab + module Checks + class ProjectCreated < PostPushMessage + PROJECT_CREATED = "project_created".freeze + + def message + <<~MESSAGE + + The private project #{project.full_path} was successfully created. + + To configure the remote, run: + git remote add origin #{url_to_repo} + + To view the project, visit: + #{project_url} + + MESSAGE + end + + private + + def self.message_key(user_id, project_id) + "#{PROJECT_CREATED}:#{user_id}:#{project_id}" + end + + def project_url + Gitlab::Routing.url_helpers.project_url(project) + end + end + end +end diff --git a/lib/gitlab/checks/project_moved.rb b/lib/gitlab/checks/project_moved.rb index dfb2f4d4054b99fd9ababb8cd750a4b2ed722098..3263790a8768326cd7ecdaf59baa66e90611614c 100644 --- a/lib/gitlab/checks/project_moved.rb +++ b/lib/gitlab/checks/project_moved.rb @@ -1,38 +1,16 @@ module Gitlab module Checks - class ProjectMoved + class ProjectMoved < PostPushMessage REDIRECT_NAMESPACE = "redirect_namespace".freeze - def initialize(project, user, redirected_path, protocol) - @project = project - @user = user + def initialize(project, user, protocol, redirected_path) @redirected_path = redirected_path - @protocol = protocol - end - - def self.fetch_redirect_message(user_id, project_id) - redirect_key = redirect_message_key(user_id, project_id) - Gitlab::Redis::SharedState.with do |redis| - message = redis.get(redirect_key) - redis.del(redirect_key) - message - end - end - - def add_redirect_message - # Don't bother with sending a redirect message for anonymous clones - # because they never see it via the `/internal/post_receive` endpoint - return unless user.present? && project.present? - - Gitlab::Redis::SharedState.with do |redis| - key = self.class.redirect_message_key(user.id, project.id) - redis.setex(key, 5.minutes, redirect_message) - end + super(project, user, protocol) end - def redirect_message(rejected: false) - <<~MESSAGE.strip_heredoc + def message(rejected: false) + <<~MESSAGE Project '#{redirected_path}' was moved to '#{project.full_path}'. Please update your Git remote: @@ -47,17 +25,17 @@ module Gitlab private - attr_reader :project, :redirected_path, :protocol, :user + attr_reader :redirected_path - def self.redirect_message_key(user_id, project_id) + def self.message_key(user_id, project_id) "#{REDIRECT_NAMESPACE}:#{user_id}:#{project_id}" end def remote_url_message(rejected) if rejected - "git remote set-url origin #{url} and try again." + "git remote set-url origin #{url_to_repo} and try again." else - "git remote set-url origin #{url}" + "git remote set-url origin #{url_to_repo}" end end diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb index baf55b1fa0704f1fac3ba40bce6277a8c5649f99..f2e5124c8a81c60a668a466af9034822de26fd24 100644 --- a/lib/gitlab/ci/trace.rb +++ b/lib/gitlab/ci/trace.rb @@ -52,12 +52,14 @@ module Gitlab end def exist? - current_path.present? || old_trace.present? + trace_artifact&.exists? || current_path.present? || old_trace.present? end def read stream = Gitlab::Ci::Trace::Stream.new do - if current_path + if trace_artifact + trace_artifact.open + elsif current_path File.open(current_path, "rb") elsif old_trace StringIO.new(old_trace) @@ -82,6 +84,8 @@ module Gitlab end def erase! + trace_artifact&.destroy + paths.each do |trace_path| FileUtils.rm(trace_path, force: true) end @@ -137,6 +141,10 @@ module Gitlab "#{job.id}.log" ) if job.project&.ci_id end + + def trace_artifact + job.job_artifacts_trace + end end end end diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb index e29a1f7afa1eae905ef9e131dec0665cf8644237..24f027d8da4e018f5a6ba0df9c202c5c2b48e409 100644 --- a/lib/gitlab/git/hook.rb +++ b/lib/gitlab/git/hook.rb @@ -82,14 +82,20 @@ module Gitlab end def call_update_hook(gl_id, gl_username, oldrev, newrev, ref) - Dir.chdir(repo_path) do - env = { - 'GL_ID' => gl_id, - 'GL_USERNAME' => gl_username - } - stdout, stderr, status = Open3.capture3(env, path, ref, oldrev, newrev) - [status.success?, (stderr.presence || stdout).gsub(/\R/, "
").html_safe] - end + env = { + 'GL_ID' => gl_id, + 'GL_USERNAME' => gl_username, + 'PWD' => repo_path + } + + options = { + chdir: repo_path + } + + args = [ref, oldrev, newrev] + + stdout, stderr, status = Open3.capture3(env, path, *args, options) + [status.success?, (stderr.presence || stdout).gsub(/\R/, "
").html_safe] end def retrieve_error_message(stderr, stdout) diff --git a/lib/gitlab/git/lfs_changes.rb b/lib/gitlab/git/lfs_changes.rb index 732dd5d998a2503e0556a3475bb8a5e2069e2b5a..48434047fcee484c7dcae70d0e60c23adee8cdde 100644 --- a/lib/gitlab/git/lfs_changes.rb +++ b/lib/gitlab/git/lfs_changes.rb @@ -25,8 +25,7 @@ module Gitlab private def rev_list - ::Gitlab::Git::RevList.new(path_to_repo: @repository.path_to_repo, - newrev: @newrev) + Gitlab::Git::RevList.new(@repository, newrev: @newrev) end end end diff --git a/lib/gitlab/git/lfs_pointer_file.rb b/lib/gitlab/git/lfs_pointer_file.rb new file mode 100644 index 0000000000000000000000000000000000000000..da12ed7d12574d546b068333d0790b996d037883 --- /dev/null +++ b/lib/gitlab/git/lfs_pointer_file.rb @@ -0,0 +1,25 @@ +module Gitlab + module Git + class LfsPointerFile + def initialize(data) + @data = data + end + + def pointer + @pointer ||= <<~FILE + version https://git-lfs.github.com/spec/v1 + oid sha256:#{sha256} + size #{size} + FILE + end + + def size + @size ||= @data.bytesize + end + + def sha256 + @sha256 ||= Digest::SHA256.hexdigest(@data) + end + end + end +end diff --git a/lib/gitlab/git/popen.rb b/lib/gitlab/git/popen.rb index e0bd2bbe47b93ddf0875beeb8f4b47efac050b0b..c1767046ff08bbe02af50530673aa9535dc2dab1 100644 --- a/lib/gitlab/git/popen.rb +++ b/lib/gitlab/git/popen.rb @@ -25,7 +25,7 @@ module Gitlab stdin.close if lazy_block - return lazy_block.call(stdout.lazy) + return [lazy_block.call(stdout.lazy), 0] else cmd_output << stdout.read end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index ab1362a3bb00479449c98f5efff81ace6bc429d9..d7510061def4d31acf6c6e3a99ba1b6390e873a4 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -128,6 +128,10 @@ module Gitlab raise NoRepository.new('no repository for such path') end + def cleanup + @rugged&.close + end + def circuit_breaker @circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(storage) end @@ -1427,6 +1431,26 @@ module Gitlab end end + def rev_list(including: [], excluding: [], objects: false, &block) + args = ['rev-list'] + + args.push(*rev_list_param(including)) + + exclude_param = *rev_list_param(excluding) + if exclude_param.any? + args.push('--not') + args.push(*exclude_param) + end + + args.push('--objects') if objects + + run_git!(args, lazy_block: block) + end + + def missed_ref(oldrev, newrev) + run_git!(['rev-list', '--max-count=1', oldrev, "^#{newrev}"]) + end + private def local_write_ref(ref_path, ref, old_ref: nil, shell: true) @@ -1475,7 +1499,7 @@ module Gitlab Rails.logger.error "Unable to create #{ref_path} reference for repository #{path}: #{ex}" end - def run_git(args, chdir: path, env: {}, nice: false, &block) + def run_git(args, chdir: path, env: {}, nice: false, lazy_block: nil, &block) cmd = [Gitlab.config.git.bin_path, *args] cmd.unshift("nice") if nice @@ -1485,12 +1509,12 @@ module Gitlab end circuit_breaker.perform do - popen(cmd, chdir, env, &block) + popen(cmd, chdir, env, lazy_block: lazy_block, &block) end end - def run_git!(args, chdir: path, env: {}, nice: false, &block) - output, status = run_git(args, chdir: chdir, env: env, nice: nice, &block) + def run_git!(args, chdir: path, env: {}, nice: false, lazy_block: nil, &block) + output, status = run_git(args, chdir: chdir, env: env, nice: nice, lazy_block: lazy_block, &block) raise GitError, output unless status.zero? @@ -2372,6 +2396,10 @@ module Gitlab rescue Rugged::ReferenceError 0 end + + def rev_list_param(spec) + spec == :all ? ['--all'] : spec + end end end end diff --git a/lib/gitlab/git/rev_list.rb b/lib/gitlab/git/rev_list.rb index f8b2e7e0e210c39016d2f49350467eb8df2c2052..38c3a55f96ff840b468db1a6920cf31d645ebd3e 100644 --- a/lib/gitlab/git/rev_list.rb +++ b/lib/gitlab/git/rev_list.rb @@ -5,17 +5,17 @@ module Gitlab class RevList include Gitlab::Git::Popen - attr_reader :oldrev, :newrev, :path_to_repo + attr_reader :oldrev, :newrev, :repository - def initialize(path_to_repo:, newrev:, oldrev: nil) + def initialize(repository, newrev:, oldrev: nil) @oldrev = oldrev @newrev = newrev - @path_to_repo = path_to_repo + @repository = repository end # This method returns an array of new commit references def new_refs - execute([*base_args, newrev, '--not', '--all']) + repository.rev_list(including: newrev, excluding: :all).split("\n") end # Finds newly added objects @@ -28,66 +28,39 @@ module Gitlab # When given a block it will yield objects as a lazy enumerator so # the caller can limit work done instead of processing megabytes of data def new_objects(require_path: nil, not_in: nil, &lazy_block) - args = [*base_args, newrev, *not_in_refs(not_in), '--objects'] + opts = { + including: newrev, + excluding: not_in.nil? ? :all : not_in, + require_path: require_path + } - get_objects(args, require_path: require_path, &lazy_block) + get_objects(opts, &lazy_block) end def all_objects(require_path: nil, &lazy_block) - args = [*base_args, '--all', '--objects'] - - get_objects(args, require_path: require_path, &lazy_block) + get_objects(including: :all, require_path: require_path, &lazy_block) end # This methods returns an array of missed references # # Should become obsolete after https://gitlab.com/gitlab-org/gitaly/issues/348. def missed_ref - execute([*base_args, '--max-count=1', oldrev, "^#{newrev}"]) + repository.missed_ref(oldrev, newrev).split("\n") end private - def not_in_refs(references) - return ['--not', '--all'] unless references - return [] if references.empty? - - references.prepend('--not') - end - def execute(args) - output, status = popen(args, nil, Gitlab::Git::Env.to_env_hash) - - unless status.zero? - raise "Got a non-zero exit code while calling out `#{args.join(' ')}`: #{output}" - end - - output.split("\n") - end - - def lazy_execute(args, &lazy_block) - popen(args, nil, Gitlab::Git::Env.to_env_hash, lazy_block: lazy_block) - end - - def base_args - [ - Gitlab.config.git.bin_path, - "--git-dir=#{path_to_repo}", - 'rev-list' - ] + repository.rev_list(args).split("\n") end - def get_objects(args, require_path: nil) - if block_given? - lazy_execute(args) do |lazy_output| - objects = objects_from_output(lazy_output, require_path: require_path) + def get_objects(including: [], excluding: [], require_path: nil) + opts = { including: including, excluding: excluding, objects: true } - yield(objects) - end - else - object_output = execute(args) + repository.rev_list(opts) do |lazy_output| + objects = objects_from_output(lazy_output, require_path: require_path) - objects_from_output(object_output, require_path: require_path) + yield(objects) end end diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb index daa17fc72cf4340aac493b04f096eafd039ba2fb..39040d569714a43d6f5b39a08289b2188761bf79 100644 --- a/lib/gitlab/git/wiki.rb +++ b/lib/gitlab/git/wiki.rb @@ -96,11 +96,23 @@ module Gitlab # :per_page - The number of items per page. # :limit - Total number of items to return. def page_versions(page_path, options = {}) - current_page = gollum_page_by_path(page_path) + @repository.gitaly_migrate(:wiki_page_versions) do |is_enabled| + if is_enabled + versions = gitaly_wiki_client.page_versions(page_path, options) + + # Gitaly uses gollum-lib to get the versions. Gollum defaults to 20 + # per page, but also fetches 20 if `limit` or `per_page` < 20. + # Slicing returns an array with the expected number of items. + slice_bound = options[:limit] || options[:per_page] || Gollum::Page.per_page + versions[0..slice_bound] + else + current_page = gollum_page_by_path(page_path) - commits_from_page(current_page, options).map do |gitlab_git_commit| - gollum_page = gollum_wiki.page(current_page.title, gitlab_git_commit.id) - Gitlab::Git::WikiPageVersion.new(gitlab_git_commit, gollum_page&.format) + commits_from_page(current_page, options).map do |gitlab_git_commit| + gollum_page = gollum_wiki.page(current_page.title, gitlab_git_commit.id) + Gitlab::Git::WikiPageVersion.new(gitlab_git_commit, gollum_page&.format) + end + end end end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 56f6febe86d000c03ec1ddae858a41ff9ae08d8b..8ec3386184a843ed24cb7f05d36be1785a7a3e74 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -2,15 +2,19 @@ # class return an instance of `GitlabAccessStatus` module Gitlab class GitAccess + include Gitlab::Utils::StrongMemoize + UnauthorizedError = Class.new(StandardError) NotFoundError = Class.new(StandardError) + ProjectCreationError = Class.new(StandardError) ProjectMovedError = Class.new(NotFoundError) ERROR_MESSAGES = { upload: 'You are not allowed to upload code for this project.', download: 'You are not allowed to download code from this project.', - deploy_key_upload: - 'This deploy key does not have write access to this project.', + auth_upload: 'You are not allowed to upload code.', + auth_download: 'You are not allowed to download code.', + deploy_key_upload: 'This deploy key does not have write access to this project.', no_repo: 'A repository for this project does not exist yet.', project_not_found: 'The project you were looking for could not be found.', account_blocked: 'Your account has been blocked.', @@ -25,24 +29,31 @@ module Gitlab PUSH_COMMANDS = %w{ git-receive-pack }.freeze ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS - attr_reader :actor, :project, :protocol, :authentication_abilities, :redirected_path + attr_reader :actor, :project, :protocol, :authentication_abilities, :namespace_path, :project_path, :redirected_path - def initialize(actor, project, protocol, authentication_abilities:, redirected_path: nil) + def initialize(actor, project, protocol, authentication_abilities:, namespace_path: nil, project_path: nil, redirected_path: nil) @actor = actor @project = project @protocol = protocol - @redirected_path = redirected_path @authentication_abilities = authentication_abilities + @namespace_path = namespace_path + @project_path = project_path + @redirected_path = redirected_path end def check(cmd, changes) check_protocol! check_valid_actor! check_active_user! - check_project_accessibility! - check_project_moved! + check_authentication_abilities!(cmd) check_command_disabled!(cmd) check_command_existence!(cmd) + check_db_accessibility!(cmd) + + ensure_project_on_push!(cmd, changes) + + check_project_accessibility! + check_project_moved! check_repository_existence! case cmd @@ -95,6 +106,19 @@ module Gitlab end end + def check_authentication_abilities!(cmd) + case cmd + when *DOWNLOAD_COMMANDS + unless authentication_abilities.include?(:download_code) || authentication_abilities.include?(:build_download_code) + raise UnauthorizedError, ERROR_MESSAGES[:auth_download] + end + when *PUSH_COMMANDS + unless authentication_abilities.include?(:push_code) + raise UnauthorizedError, ERROR_MESSAGES[:auth_upload] + end + end + end + def check_project_accessibility! if project.blank? || !can_read_project? raise NotFoundError, ERROR_MESSAGES[:project_not_found] @@ -104,12 +128,12 @@ module Gitlab def check_project_moved! return if redirected_path.nil? - project_moved = Checks::ProjectMoved.new(project, user, redirected_path, protocol) + project_moved = Checks::ProjectMoved.new(project, user, protocol, redirected_path) if project_moved.permanent_redirect? - project_moved.add_redirect_message + project_moved.add_message else - raise ProjectMovedError, project_moved.redirect_message(rejected: true) + raise ProjectMovedError, project_moved.message(rejected: true) end end @@ -139,6 +163,40 @@ module Gitlab end end + def check_db_accessibility!(cmd) + return unless receive_pack?(cmd) + + if Gitlab::Database.read_only? + raise UnauthorizedError, push_to_read_only_message + end + end + + def ensure_project_on_push!(cmd, changes) + return if project || deploy_key? + return unless receive_pack?(cmd) && changes == '_any' && authentication_abilities.include?(:push_code) + + namespace = Namespace.find_by_full_path(namespace_path) + + return unless user&.can?(:create_projects, namespace) + + project_params = { + path: project_path, + namespace_id: namespace.id, + visibility_level: Gitlab::VisibilityLevel::PRIVATE + } + + project = Projects::CreateService.new(user, project_params).execute + + unless project.saved? + raise ProjectCreationError, "Could not create project: #{project.errors.full_messages.join(', ')}" + end + + @project = project + user_access.project = @project + + Checks::ProjectCreated.new(project, user, protocol).add_message + end + def check_repository_existence! unless project.repository.exists? raise UnauthorizedError, ERROR_MESSAGES[:no_repo] @@ -146,9 +204,8 @@ module Gitlab end def check_download_access! - return if deploy_key? - - passed = user_can_download_code? || + passed = deploy_key? || + user_can_download_code? || build_can_download_code? || guest_can_download_code? @@ -162,35 +219,21 @@ module Gitlab raise UnauthorizedError, ERROR_MESSAGES[:read_only] end - if Gitlab::Database.read_only? - raise UnauthorizedError, push_to_read_only_message - end - if deploy_key - check_deploy_key_push_access! + unless deploy_key.can_push_to?(project) + raise UnauthorizedError, ERROR_MESSAGES[:deploy_key_upload] + end elsif user - check_user_push_access! + # User access is verified in check_change_access! else raise UnauthorizedError, ERROR_MESSAGES[:upload] end - return if changes.blank? # Allow access. + return if changes.blank? # Allow access this is needed for EE. check_change_access!(changes) end - def check_user_push_access! - unless authentication_abilities.include?(:push_code) - raise UnauthorizedError, ERROR_MESSAGES[:upload] - end - end - - def check_deploy_key_push_access! - unless deploy_key.can_push_to?(project) - raise UnauthorizedError, ERROR_MESSAGES[:deploy_key_upload] - end - end - def check_change_access!(changes) changes_list = Gitlab::ChangesList.new(changes) diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb index 8e87a8cc36fdefb26c8676f09502ed4f1e432659..0d8dd5cb8f4612e3741743ec409eaa73435000ce 100644 --- a/lib/gitlab/gitaly_client/wiki_service.rb +++ b/lib/gitlab/gitaly_client/wiki_service.rb @@ -101,6 +101,30 @@ module Gitlab pages end + # options: + # :page - The Integer page number. + # :per_page - The number of items per page. + # :limit - Total number of items to return. + def page_versions(page_path, options) + request = Gitaly::WikiGetPageVersionsRequest.new( + repository: @gitaly_repo, + page_path: encode_binary(page_path), + page: options[:page] || 1, + per_page: options[:per_page] || Gollum::Page.per_page + ) + + stream = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_page_versions, request) + + versions = [] + stream.each do |message| + message.versions.each do |version| + versions << new_wiki_page_version(version) + end + end + + versions + end + def find_file(name, revision) request = Gitaly::WikiFindFileRequest.new( repository: @gitaly_repo, @@ -141,7 +165,7 @@ module Gitlab private - # If a block is given and the yielded value is true, iteration will be + # If a block is given and the yielded value is truthy, iteration will be # stopped early at that point; else the iterator is consumed entirely. # The iterator is traversed with `next` to allow resuming the iteration. def wiki_page_from_iterator(iterator) @@ -158,10 +182,7 @@ module Gitlab else wiki_page = GitalyClient::WikiPage.new(page.to_h) - version = Gitlab::Git::WikiPageVersion.new( - Gitlab::Git::Commit.decorate(@repository, page.version.commit), - page.version.format - ) + version = new_wiki_page_version(page.version) end end @@ -170,6 +191,13 @@ module Gitlab [wiki_page, version] end + def new_wiki_page_version(version) + Gitlab::Git::WikiPageVersion.new( + Gitlab::Git::Commit.decorate(@repository, version.commit), + version.format + ) + end + def gitaly_commit_details(commit_details) Gitaly::WikiCommitDetails.new( name: encode_binary(commit_details.name), diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index e40a001d20c543e3e1b2eca20c80718dc214231e..a3e1c66c19f2c914977c268ea1595c82acf29381 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -178,7 +178,7 @@ module Gitlab valid_username = ::Namespace.clean_path(username) uniquify = Uniquify.new - valid_username = uniquify.string(valid_username) { |s| !UserPathValidator.valid_path?(s) } + valid_username = uniquify.string(valid_username) { |s| !NamespacePathValidator.valid_path?(s) } name = auth_hash.name name = valid_username if name.strip.empty? diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb index 7e5dfd3350204416720d7693810b8de336f952e8..4dc38aae61e71a6a62c781b9e366c3217da331b3 100644 --- a/lib/gitlab/path_regex.rb +++ b/lib/gitlab/path_regex.rb @@ -171,24 +171,16 @@ module Gitlab @project_git_route_regex ||= /#{project_route_regex}\.git/.freeze end - def root_namespace_path_regex - @root_namespace_path_regex ||= %r{\A#{root_namespace_route_regex}/\z} - end - def full_namespace_path_regex @full_namespace_path_regex ||= %r{\A#{full_namespace_route_regex}/\z} end - def project_path_regex - @project_path_regex ||= %r{\A#{project_route_regex}/\z} - end - def full_project_path_regex @full_project_path_regex ||= %r{\A#{full_namespace_route_regex}/#{project_route_regex}/\z} end - def full_namespace_format_regex - @namespace_format_regex ||= /A#{FULL_NAMESPACE_FORMAT_REGEX}\z/.freeze + def full_project_git_path_regex + @full_project_git_path_regex ||= %r{\A\/?(?#{full_namespace_route_regex})\/(?#{project_route_regex})\.git\z} end def namespace_format_regex diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb index 2bfb7caefd99423ee7b1a3a76b44290188984cb1..b89ae2505c944b305456f7ddc38400aca0091bda 100644 --- a/lib/gitlab/sidekiq_middleware/memory_killer.rb +++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb @@ -45,7 +45,7 @@ module Gitlab private def get_rss - output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{pid})) + output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{pid}), Rails.root.to_s) return 0 unless status.zero? output.to_i diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb index f357488ac61a80a91a061e39d4cda52cfe955459..15eb1c4121324d05d6fb218848351361093626c9 100644 --- a/lib/gitlab/user_access.rb +++ b/lib/gitlab/user_access.rb @@ -6,7 +6,8 @@ module Gitlab [user&.id, project&.id] end - attr_reader :user, :project + attr_reader :user + attr_accessor :project def initialize(user, project: nil) @user = user diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index b3f8b0d174dcc3a8778da3a1ce281dca6e71d8d8..823df67ea39e81b6d56cab731a625a6329ed83e0 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -161,6 +161,18 @@ module Gitlab ] end + def send_url(url, allow_redirects: false) + params = { + 'URL' => url, + 'AllowRedirects' => allow_redirects + } + + [ + SEND_DATA_HEADER, + "send-url:#{encode(params)}" + ] + end + def terminal_websocket(terminal) details = { 'Terminal' => { diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 159521a750b6dbdcdd6d49c55c55dd825aee6fe1..d5cb5400d4f666cea02907e27a903b55c662ae24 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-05 16:03+0100\n" -"PO-Revision-Date: 2018-02-05 16:03+0100\n" +"POT-Creation-Date: 2018-02-06 10:02+0100\n" +"PO-Revision-Date: 2018-02-06 10:02+0100\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -153,12 +153,21 @@ msgstr "" msgid "All" msgstr "" +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + msgid "An error occurred previewing the blob" msgstr "" msgid "An error occurred when toggling the notification subscription" msgstr "" +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + msgid "An error occurred while fetching sidebar data" msgstr "" @@ -171,12 +180,18 @@ msgstr "" msgid "An error occurred while rendering KaTeX" msgstr "" +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + msgid "An error occurred while retrieving calendar activity" msgstr "" msgid "An error occurred while retrieving diff" msgstr "" +msgid "An error occurred while validating username" +msgstr "" + msgid "An error occurred. Please try again." msgstr "" @@ -392,6 +407,9 @@ msgstr "" msgid "Cancel edit" msgstr "" +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + msgid "ChangeTypeActionLabel|Pick into branch" msgstr "" @@ -524,28 +542,25 @@ msgstr "" msgid "Clone repository" msgstr "" -msgid "Cluster" -msgstr "" - -msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" msgstr "" msgid "ClusterIntegration|API URL" msgstr "" -msgid "ClusterIntegration|Add an existing cluster" +msgid "ClusterIntegration|Add Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Add cluster" +msgid "ClusterIntegration|Add an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Advanced options on this cluster's integration" +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" msgstr "" msgid "ClusterIntegration|Applications" msgstr "" -msgid "ClusterIntegration|Are you sure you want to remove this cluster's integration? This will not delete your actual cluster." +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|CA Certificate" @@ -554,121 +569,121 @@ msgstr "" msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgstr "" -msgid "ClusterIntegration|Choose how to set up cluster integration" +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" msgstr "" -msgid "ClusterIntegration|Choose which of your project's environments will use this cluster." +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." msgstr "" -msgid "ClusterIntegration|Cluster" +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" msgstr "" -msgid "ClusterIntegration|Cluster details" +msgid "ClusterIntegration|Copy API URL" msgstr "" -msgid "ClusterIntegration|Cluster integration" +msgid "ClusterIntegration|Copy CA Certificate" msgstr "" -msgid "ClusterIntegration|Cluster integration is disabled for this project." +msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project." +msgid "ClusterIntegration|Copy Token" msgstr "" -msgid "ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab's connection to it." +msgid "ClusterIntegration|Create Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Cluster is being created on Google Kubernetes Engine..." +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|Cluster name" +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" msgstr "" -msgid "ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster's details" +msgid "ClusterIntegration|Create on GKE" msgstr "" -msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Clusters can be used to deploy applications and to provide Review Apps for this project" +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Control how your cluster integrates with GitLab" +msgid "ClusterIntegration|Environment scope" msgstr "" -msgid "ClusterIntegration|Copy API URL" +msgid "ClusterIntegration|GitLab Integration" msgstr "" -msgid "ClusterIntegration|Copy CA Certificate" +msgid "ClusterIntegration|GitLab Runner" msgstr "" -msgid "ClusterIntegration|Copy Token" +msgid "ClusterIntegration|Google Cloud Platform project ID" msgstr "" -msgid "ClusterIntegration|Copy cluster name" +msgid "ClusterIntegration|Google Kubernetes Engine" msgstr "" -msgid "ClusterIntegration|Create a new cluster on Google Kubernetes Engine right from GitLab" +msgid "ClusterIntegration|Google Kubernetes Engine project" msgstr "" -msgid "ClusterIntegration|Create cluster" +msgid "ClusterIntegration|Helm Tiller" msgstr "" -msgid "ClusterIntegration|Create cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Ingress" msgstr "" -msgid "ClusterIntegration|Create on GKE" +msgid "ClusterIntegration|Install" msgstr "" -msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" +msgid "ClusterIntegration|Installed" msgstr "" -msgid "ClusterIntegration|Enter the details for your cluster" +msgid "ClusterIntegration|Installing" msgstr "" -msgid "ClusterIntegration|Environment scope" +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" msgstr "" -msgid "ClusterIntegration|GitLab Integration" +msgid "ClusterIntegration|Integration status" msgstr "" -msgid "ClusterIntegration|GitLab Runner" +msgid "ClusterIntegration|Kubernetes cluster" msgstr "" -msgid "ClusterIntegration|Google Cloud Platform project ID" +msgid "ClusterIntegration|Kubernetes cluster details" msgstr "" -msgid "ClusterIntegration|Google Kubernetes Engine" +msgid "ClusterIntegration|Kubernetes cluster integration" msgstr "" -msgid "ClusterIntegration|Google Kubernetes Engine project" +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." msgstr "" -msgid "ClusterIntegration|Helm Tiller" +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." msgstr "" -msgid "ClusterIntegration|Ingress" +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." msgstr "" -msgid "ClusterIntegration|Install" +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." msgstr "" -msgid "ClusterIntegration|Installed" +msgid "ClusterIntegration|Kubernetes cluster name" msgstr "" -msgid "ClusterIntegration|Installing" +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" msgstr "" -msgid "ClusterIntegration|Integrate cluster automation" +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" msgstr "" -msgid "ClusterIntegration|Integration status" +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Clusters" +msgid "ClusterIntegration|Learn more about Kubernetes" msgstr "" msgid "ClusterIntegration|Learn more about environments" @@ -677,10 +692,10 @@ msgstr "" msgid "ClusterIntegration|Machine type" msgstr "" -msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" msgstr "" -msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" msgstr "" msgid "ClusterIntegration|Note:" @@ -689,7 +704,7 @@ msgstr "" msgid "ClusterIntegration|Number of nodes" msgstr "" -msgid "ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" msgstr "" msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" @@ -707,16 +722,16 @@ msgstr "" msgid "ClusterIntegration|Prometheus" msgstr "" -msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." msgstr "" -msgid "ClusterIntegration|Remove cluster integration" +msgid "ClusterIntegration|Remove Kubernetes cluster integration" msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "" -msgid "ClusterIntegration|Remove this cluster's configuration from this project. This will not delete your actual cluster." +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" @@ -725,7 +740,7 @@ msgstr "" msgid "ClusterIntegration|Save changes" msgstr "" -msgid "ClusterIntegration|See and edit the details for your cluster" +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" msgstr "" msgid "ClusterIntegration|See machine types" @@ -746,22 +761,25 @@ msgstr "" msgid "ClusterIntegration|Something went wrong on our end." msgstr "" -msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine" +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "" -msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" +msgstr "" + +msgid "ClusterIntegration|Toggle Kubernetes Cluster" msgstr "" -msgid "ClusterIntegration|Toggle Cluster" +msgid "ClusterIntegration|Toggle Kubernetes cluster" msgstr "" msgid "ClusterIntegration|Token" msgstr "" -msgid "ClusterIntegration|With a cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" @@ -776,9 +794,6 @@ msgstr "" msgid "ClusterIntegration|check the pricing here" msgstr "" -msgid "ClusterIntegration|cluster" -msgstr "" - msgid "ClusterIntegration|documentation" msgstr "" @@ -1212,6 +1227,9 @@ msgstr "" msgid "February" msgstr "" +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + msgid "File name" msgstr "" @@ -1280,6 +1298,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1381,6 +1402,9 @@ msgstr "" msgid "Install a Runner compatible with GitLab CI" msgstr "" +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + msgid "Interested parties can even contribute by pushing commits if they want to." msgstr "" @@ -1426,6 +1450,27 @@ msgstr "" msgid "June" msgstr "" +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" +msgstr "" + msgid "LFSStatus|Disabled" msgstr "" @@ -1551,14 +1596,17 @@ msgstr "" msgid "More information is available|here" msgstr "" -msgid "New Cluster" -msgstr "" - msgid "New Issue" msgid_plural "New Issues" msgstr[0] "" msgstr[1] "" +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + msgid "New Pipeline Schedule" msgstr "" @@ -1835,7 +1883,7 @@ msgstr "" msgid "Play" msgstr "" -msgid "Please enable billing for one of your projects to be able to create a cluster, then try again." +msgid "Please enable billing for one of your projects to be able to create a Kubernetes cluster, then try again." msgstr "" msgid "Please solve the reCAPTCHA" @@ -2009,6 +2057,9 @@ msgstr "" msgid "PrometheusService|View environments" msgstr "" +msgid "Protip:" +msgstr "" + msgid "Public - The group and any public projects can be viewed without any authentication." msgstr "" @@ -2480,9 +2531,15 @@ msgstr "" msgid "There are problems accessing Git storage: " msgstr "" +msgid "There was an error loading users activity calendar." +msgstr "" + msgid "There was an error saving your notification settings." msgstr "" +msgid "There was an error subscribing to this label." +msgstr "" + msgid "There was an error when reseting email token." msgstr "" @@ -2946,6 +3003,9 @@ msgstr "" msgid "You'll need to use different branch names to get a valid comparison." msgstr "" +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -3125,3 +3185,6 @@ msgstr "" msgid "username" msgstr "" + +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index a3b13647c92a6ec86be9e0b5889294c21915e75f..954fc79f57dc8ad16b8f92af61e02b3832a23527 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -177,7 +177,7 @@ describe Projects::ClustersController do cluster.reload expect(response).to redirect_to(project_cluster_path(project, cluster)) - expect(flash[:notice]).to eq('Cluster was successfully updated.') + expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.') expect(cluster.enabled).to be_falsey end @@ -276,7 +276,7 @@ describe Projects::ClustersController do cluster.reload expect(response).to redirect_to(project_cluster_path(project, cluster)) - expect(flash[:notice]).to eq('Cluster was successfully updated.') + expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.') expect(cluster.enabled).to be_falsey expect(cluster.name).to eq('my-new-cluster-name') expect(cluster.platform_kubernetes.namespace).to eq('my-namespace') @@ -336,7 +336,7 @@ describe Projects::ClustersController do .and change { Clusters::Providers::Gcp.count }.by(-1) expect(response).to redirect_to(project_clusters_path(project)) - expect(flash[:notice]).to eq('Cluster integration was successfully removed.') + expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.') end end @@ -349,7 +349,7 @@ describe Projects::ClustersController do .and change { Clusters::Providers::Gcp.count }.by(-1) expect(response).to redirect_to(project_clusters_path(project)) - expect(flash[:notice]).to eq('Cluster integration was successfully removed.') + expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.') end end end @@ -364,7 +364,7 @@ describe Projects::ClustersController do .and change { Clusters::Providers::Gcp.count }.by(0) expect(response).to redirect_to(project_clusters_path(project)) - expect(flash[:notice]).to eq('Cluster integration was successfully removed.') + expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.') end end end diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index db5954309794cd4ded10dca546df66eb6d32e6dd..f3e303bb0fe32b8c775f0255aef63b22bcce94b1 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -159,8 +159,19 @@ describe Projects::JobsController do get_trace end + context 'when job has a trace artifact' do + let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) } + + it 'returns a trace' do + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['id']).to eq job.id + expect(json_response['status']).to eq job.status + expect(json_response['html']).to eq(job.trace.html) + end + end + context 'when job has a trace' do - let(:job) { create(:ci_build, :trace, pipeline: pipeline) } + let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) } it 'returns a trace' do expect(response).to have_gitlab_http_status(:ok) @@ -182,7 +193,7 @@ describe Projects::JobsController do end context 'when job has a trace with ANSI sequence and Unicode' do - let(:job) { create(:ci_build, :unicode_trace, pipeline: pipeline) } + let(:job) { create(:ci_build, :unicode_trace_live, pipeline: pipeline) } it 'returns a trace with Unicode' do expect(response).to have_gitlab_http_status(:ok) @@ -381,7 +392,7 @@ describe Projects::JobsController do end context 'when job is erasable' do - let(:job) { create(:ci_build, :erasable, :trace, pipeline: pipeline) } + let(:job) { create(:ci_build, :erasable, :trace_artifact, pipeline: pipeline) } it 'redirects to the erased job page' do expect(response).to have_gitlab_http_status(:found) @@ -408,7 +419,7 @@ describe Projects::JobsController do context 'when user is developer' do let(:role) { :developer } - let(:job) { create(:ci_build, :erasable, :trace, pipeline: pipeline, user: triggered_by) } + let(:job) { create(:ci_build, :erasable, :trace_artifact, pipeline: pipeline, user: triggered_by) } context 'when triggered by same user' do let(:triggered_by) { user } @@ -439,8 +450,18 @@ describe Projects::JobsController do get_raw end + context 'when job has a trace artifact' do + let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) } + + it 'returns a trace' do + expect(response).to have_gitlab_http_status(:ok) + expect(response.content_type).to eq 'text/plain; charset=utf-8' + expect(response.body).to eq job.job_artifacts_trace.open.read + end + end + context 'when job has a trace file' do - let(:job) { create(:ci_build, :trace, pipeline: pipeline) } + let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) } it 'send a trace file' do expect(response).to have_gitlab_http_status(:ok) diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 6f66468570f38aaa1157c67b6c9957b640c193ce..6ba599cdf83dc798f1c91c7e60492b0c5c475a52 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -135,13 +135,19 @@ FactoryBot.define do coverage_regex '/(d+)/' end - trait :trace do + trait :trace_live do after(:create) do |build, evaluator| build.trace.set('BUILD TRACE') end end - trait :unicode_trace do + trait :trace_artifact do + after(:create) do |build, evaluator| + create(:ci_job_artifact, :trace, job: build) + end + end + + trait :unicode_trace_live do after(:create) do |build, evaluator| trace = File.binread( File.expand_path( diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb index 46afba2953c24efddfe922f2f779afba7219bbf3..7ee379ca2ec3a983c178bee21136f90d7c8362db 100644 --- a/spec/factories/ci/job_artifacts.rb +++ b/spec/factories/ci/job_artifacts.rb @@ -26,5 +26,14 @@ FactoryBot.define do Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip') end end + + trait :trace do + file_type :trace + + after(:build) do |artifact, evaluator| + artifact.file = fixture_file_upload( + Rails.root.join('spec/fixtures/trace/sample_trace'), 'text/plain') + end + end end end diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb index 9c4abec115fa877ed6c29d1a6dc651a6d07a1b82..8d1e10b71919b1a4d65217f74e0850374be77cdf 100644 --- a/spec/features/projects/clusters/applications_spec.rb +++ b/spec/features/projects/clusters/applications_spec.rb @@ -64,7 +64,7 @@ feature 'Clusters Applications', :js do expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installed') end - expect(page).to have_content('Helm Tiller was successfully installed on your cluster') + expect(page).to have_content('Helm Tiller was successfully installed on your Kubernetes cluster') end end @@ -98,7 +98,7 @@ feature 'Clusters Applications', :js do expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installed') end - expect(page).to have_content('Ingress was successfully installed on your cluster') + expect(page).to have_content('Ingress was successfully installed on your Kubernetes cluster') end end end diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 94bde723e2f12835e3e29c8f1fc668263adf9b70..02dbd3380b33d6b1ee6335750041744bd20a62a4 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -32,7 +32,7 @@ feature 'Gcp Cluster', :js do before do visit project_clusters_path(project) - click_link 'Add cluster' + click_link 'Add Kubernetes cluster' click_link 'Create on GKE' end @@ -50,19 +50,19 @@ feature 'Gcp Cluster', :js do fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123' fill_in 'cluster_name', with: 'dev-cluster' - click_button 'Create cluster' + click_button 'Create Kubernetes cluster' end it 'user sees a cluster details page and creation status' do - expect(page).to have_content('Cluster is being created on Google Kubernetes Engine...') + expect(page).to have_content('Kubernetes cluster is being created on Google Kubernetes Engine...') Clusters::Cluster.last.provider.make_created! - expect(page).to have_content('Cluster was successfully created on Google Kubernetes Engine') + expect(page).to have_content('Kubernetes cluster was successfully created on Google Kubernetes Engine') end it 'user sees a error if something worng during creation' do - expect(page).to have_content('Cluster is being created on Google Kubernetes Engine...') + expect(page).to have_content('Kubernetes cluster is being created on Google Kubernetes Engine...') Clusters::Cluster.last.provider.make_errored!('Something wrong!') @@ -72,7 +72,7 @@ feature 'Gcp Cluster', :js do context 'when user filled form with invalid parameters' do before do - click_button 'Create cluster' + click_button 'Create Kubernetes cluster' end it 'user sees a validation error' do @@ -100,7 +100,7 @@ feature 'Gcp Cluster', :js do end it 'user sees the successful message' do - expect(page).to have_content('Cluster was successfully updated.') + expect(page).to have_content('Kubernetes cluster was successfully updated.') end end @@ -111,7 +111,7 @@ feature 'Gcp Cluster', :js do end it 'user sees the successful message' do - expect(page).to have_content('Cluster was successfully updated.') + expect(page).to have_content('Kubernetes cluster was successfully updated.') expect(cluster.reload.platform_kubernetes.namespace).to eq('my-namespace') end end @@ -124,8 +124,8 @@ feature 'Gcp Cluster', :js do end it 'user sees creation form with the successful message' do - expect(page).to have_content('Cluster integration was successfully removed.') - expect(page).to have_link('Add cluster') + expect(page).to have_content('Kubernetes cluster integration was successfully removed.') + expect(page).to have_link('Add Kubernetes cluster') end end end @@ -138,16 +138,16 @@ feature 'Gcp Cluster', :js do visit project_clusters_path(project) - click_link 'Add cluster' + click_link 'Add Kubernetes cluster' click_link 'Create on GKE' fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123' fill_in 'cluster_name', with: 'dev-cluster' - click_button 'Create cluster' + click_button 'Create Kubernetes cluster' end it 'user sees form with error' do - expect(page).to have_content('Please enable billing for one of your projects to be able to create a cluster, then try again.') + expect(page).to have_content('Please enable billing for one of your projects to be able to create a Kubernetes cluster, then try again.') end end @@ -158,12 +158,12 @@ feature 'Gcp Cluster', :js do visit project_clusters_path(project) - click_link 'Add cluster' + click_link 'Add Kubernetes cluster' click_link 'Create on GKE' fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123' fill_in 'cluster_name', with: 'dev-cluster' - click_button 'Create cluster' + click_button 'Create Kubernetes cluster' end it 'user sees form with error' do @@ -176,7 +176,7 @@ feature 'Gcp Cluster', :js do before do visit project_clusters_path(project) - click_link 'Add cluster' + click_link 'Add Kubernetes cluster' click_link 'Create on GKE' end diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb index b9ab434c259499df3803af3076e887d7df2a3909..698b64a659cdbdfb9b643a4c474da6989a02975a 100644 --- a/spec/features/projects/clusters/user_spec.rb +++ b/spec/features/projects/clusters/user_spec.rb @@ -16,8 +16,8 @@ feature 'User Cluster', :js do before do visit project_clusters_path(project) - click_link 'Add cluster' - click_link 'Add an existing cluster' + click_link 'Add Kubernetes cluster' + click_link 'Add an existing Kubernetes cluster' end context 'when user filled form with valid parameters' do @@ -25,11 +25,11 @@ feature 'User Cluster', :js do fill_in 'cluster_name', with: 'dev-cluster' fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'http://example.com' fill_in 'cluster_platform_kubernetes_attributes_token', with: 'my-token' - click_button 'Add cluster' + click_button 'Add Kubernetes cluster' end it 'user sees a cluster details page' do - expect(page).to have_content('Cluster integration') + expect(page).to have_content('Kubernetes cluster integration') expect(page.find_field('cluster[name]').value).to eq('dev-cluster') expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value) .to have_content('http://example.com') @@ -40,7 +40,7 @@ feature 'User Cluster', :js do context 'when user filled form with invalid parameters' do before do - click_button 'Add cluster' + click_button 'Add Kubernetes cluster' end it 'user sees a validation error' do @@ -68,7 +68,7 @@ feature 'User Cluster', :js do end it 'user sees the successful message' do - expect(page).to have_content('Cluster was successfully updated.') + expect(page).to have_content('Kubernetes cluster was successfully updated.') end end @@ -80,7 +80,7 @@ feature 'User Cluster', :js do end it 'user sees the successful message' do - expect(page).to have_content('Cluster was successfully updated.') + expect(page).to have_content('Kubernetes cluster was successfully updated.') expect(cluster.reload.name).to eq('my-dev-cluster') expect(cluster.reload.platform_kubernetes.namespace).to eq('my-namespace') end @@ -94,8 +94,8 @@ feature 'User Cluster', :js do end it 'user sees creation form with the successful message' do - expect(page).to have_content('Cluster integration was successfully removed.') - expect(page).to have_link('Add cluster') + expect(page).to have_content('Kubernetes cluster integration was successfully removed.') + expect(page).to have_link('Add Kubernetes cluster') end end end diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb index 497a50bebe476ae2b070464f1e7affae38e9906a..bd9f7745cf810f5c2289f8fa4a50a65d34f3952d 100644 --- a/spec/features/projects/clusters_spec.rb +++ b/spec/features/projects/clusters_spec.rb @@ -17,7 +17,7 @@ feature 'Clusters', :js do end it 'sees empty state' do - expect(page).to have_link('Add cluster') + expect(page).to have_link('Add Kubernetes cluster') expect(page).to have_selector('.empty-state') end end @@ -82,7 +82,7 @@ feature 'Clusters', :js do before do visit project_clusters_path(project) - click_link 'Add cluster' + click_link 'Add Kubernetes cluster' click_link 'Create on GKE' end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index e661db1809a0960282806bb9c85c6ce7a83561fc..5d311f2dde33deb4c4c60d3abd35c1547e620368 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -7,7 +7,7 @@ feature 'Jobs' do let(:project) { create(:project, :repository) } let(:pipeline) { create(:ci_pipeline, project: project) } - let(:job) { create(:ci_build, :trace, pipeline: pipeline) } + let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) } let(:job2) { create(:ci_build) } let(:artifacts_file) do @@ -490,18 +490,34 @@ feature 'Jobs' do describe 'GET /:project/jobs/:id/raw', :js do context 'access source' do context 'job from project' do - before do - job.run! - end + context 'when job is running' do + before do + job.run! + end - it 'sends the right headers' do - requests = inspect_requests(inject_headers: { 'X-Sendfile-Type' => 'X-Sendfile' }) do - visit raw_project_job_path(project, job) + it 'sends the right headers' do + requests = inspect_requests(inject_headers: { 'X-Sendfile-Type' => 'X-Sendfile' }) do + visit raw_project_job_path(project, job) + end + + expect(requests.first.status_code).to eq(200) + expect(requests.first.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') + expect(requests.first.response_headers['X-Sendfile']).to eq(job.trace.send(:current_path)) end + end - expect(requests.first.status_code).to eq(200) - expect(requests.first.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') - expect(requests.first.response_headers['X-Sendfile']).to eq(job.trace.send(:current_path)) + context 'when job is complete' do + let(:job) { create(:ci_build, :success, :trace_artifact, pipeline: pipeline) } + + it 'sends the right headers' do + requests = inspect_requests(inject_headers: { 'X-Sendfile-Type' => 'X-Sendfile' }) do + visit raw_project_job_path(project, job) + end + + expect(requests.first.status_code).to eq(200) + expect(requests.first.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') + expect(requests.first.response_headers['X-Sendfile']).to eq(job.job_artifacts_trace.file.path) + end end end diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index 85bd776932b14a089470c75a1da4ec6d5da6e0c2..ae8b1364ec74f0e2f03c3631b3b32b15f3741c67 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -99,7 +99,7 @@ feature 'Prioritize labels' do expect(page).to have_content 'wontfix' # Sort labels - drag_to(selector: '.js-prioritized-labels', from_index: 1, to_index: 2) + drag_to(selector: '.label-list-item', from_index: 1, to_index: 2) page.within('.prioritized-labels') do expect(first('li')).to have_content('feature') diff --git a/spec/fixtures/trace/sample_trace b/spec/fixtures/trace/sample_trace new file mode 100644 index 0000000000000000000000000000000000000000..55fcb9d275686556fa3360f244c9bcb3e0aa8371 --- /dev/null +++ b/spec/fixtures/trace/sample_trace @@ -0,0 +1,1185 @@ +Running with gitlab-runner 10.4.0 (857480b6) + on docker-auto-scale-com (9a6801bd) +Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.14-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6 ... +Starting service postgres:9.2 ... +Pulling docker image postgres:9.2 ... +Using docker image postgres:9.2 ID=sha256:18cdbca56093c841d28e629eb8acd4224afe0aa4c57c839351fc181888b8a470 for postgres service... +Starting service redis:alpine ... +Pulling docker image redis:alpine ... +Using docker image redis:alpine ID=sha256:cb1ec54b370d4a91dff57d00f91fd880dc710160a58440adaa133e0f84ae999d for redis service... +Waiting for services to be up and running... +Using docker image sha256:3006a02a5a6f0a116358a13bbc46ee46fb2471175efd5b7f9b1c22345ec2a8e9 for predefined container... +Pulling docker image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.14-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6 ... +Using docker image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.14-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6 ID=sha256:1f59be408f12738509ffe4177d65e9de6391f32461de83d9d45f58517b30af99 for build container... +section_start:1517486886:prepare_script +Running on runner-9a6801bd-project-13083-concurrent-0 via runner-9a6801bd-gsrm-1517484168-a8449153... +section_end:1517486887:prepare_script +section_start:1517486887:get_sources +Fetching changes for 42624-gitaly-bundle-isolation-not-working-in-ci with git depth set to 20... +Removing .gitlab_shell_secret +Removing .gitlab_workhorse_secret +Removing .yarn-cache/ +Removing config/database.yml +Removing config/gitlab.yml +Removing config/redis.cache.yml +Removing config/redis.queues.yml +Removing config/redis.shared_state.yml +Removing config/resque.yml +Removing config/secrets.yml +Removing coverage/ +Removing knapsack/ +Removing log/api_json.log +Removing log/application.log +Removing log/gitaly-test.log +Removing log/githost.log +Removing log/grpc.log +Removing log/test_json.log +Removing node_modules/ +Removing public/assets/ +Removing rspec_flaky/ +Removing shared/tmp/ +Removing tmp/tests/ +Removing vendor/ruby/ +HEAD is now at 4cea24f Converted todos.js to axios +From https://gitlab.com/gitlab-org/gitlab-ce + * [new branch] 42624-gitaly-bundle-isolation-not-working-in-ci -> origin/42624-gitaly-bundle-isolation-not-working-in-ci +Checking out f42a5e24 as 42624-gitaly-bundle-isolation-not-working-in-ci... +Skipping Git submodules setup +section_end:1517486896:get_sources +section_start:1517486896:restore_cache +Checking cache for ruby-2.3.6-with-yarn... +Downloading cache.zip from http://runners-cache-5-internal.gitlab.com:444/runner/project/13083/ruby-2.3.6-with-yarn +Successfully extracted cache +section_end:1517486919:restore_cache +section_start:1517486919:download_artifacts +Downloading artifacts for retrieve-tests-metadata (50551658)... +Downloading artifacts from coordinator... ok  id=50551658 responseStatus=200 OK token=HhF7y_1X +Downloading artifacts for compile-assets (50551659)... +Downloading artifacts from coordinator... ok  id=50551659 responseStatus=200 OK token=wTz6JrCP +Downloading artifacts for setup-test-env (50551660)... +Downloading artifacts from coordinator... ok  id=50551660 responseStatus=200 OK token=DTGgeVF5 +WARNING: tmp/tests/gitlab-shell/.gitlab_shell_secret: chmod tmp/tests/gitlab-shell/.gitlab_shell_secret: no such file or directory (suppressing repeats) +section_end:1517486934:download_artifacts +section_start:1517486934:build_script +$ bundle --version +Bundler version 1.16.1 +$ source scripts/utils.sh +$ source scripts/prepare_build.sh +The Gemfile's dependencies are satisfied +Successfully installed knapsack-1.15.0 +1 gem installed +NOTICE: database "gitlabhq_test" does not exist, skipping +DROP DATABASE +CREATE DATABASE +CREATE ROLE +GRANT +-- enable_extension("plpgsql") + -> 0.0156s +-- enable_extension("pg_trgm") + -> 0.0156s +-- create_table("abuse_reports", {:force=>:cascade}) + -> 0.0119s +-- create_table("appearances", {:force=>:cascade}) + -> 0.0065s +-- create_table("application_settings", {:force=>:cascade}) + -> 0.0382s +-- create_table("audit_events", {:force=>:cascade}) + -> 0.0056s +-- add_index("audit_events", ["entity_id", "entity_type"], {:name=>"index_audit_events_on_entity_id_and_entity_type", :using=>:btree}) + -> 0.0040s +-- create_table("award_emoji", {:force=>:cascade}) + -> 0.0058s +-- add_index("award_emoji", ["awardable_type", "awardable_id"], {:name=>"index_award_emoji_on_awardable_type_and_awardable_id", :using=>:btree}) + -> 0.0068s +-- add_index("award_emoji", ["user_id", "name"], {:name=>"index_award_emoji_on_user_id_and_name", :using=>:btree}) + -> 0.0043s +-- create_table("boards", {:force=>:cascade}) + -> 0.0049s +-- add_index("boards", ["project_id"], {:name=>"index_boards_on_project_id", :using=>:btree}) + -> 0.0056s +-- create_table("broadcast_messages", {:force=>:cascade}) + -> 0.0056s +-- add_index("broadcast_messages", ["starts_at", "ends_at", "id"], {:name=>"index_broadcast_messages_on_starts_at_and_ends_at_and_id", :using=>:btree}) + -> 0.0041s +-- create_table("chat_names", {:force=>:cascade}) + -> 0.0056s +-- add_index("chat_names", ["service_id", "team_id", "chat_id"], {:name=>"index_chat_names_on_service_id_and_team_id_and_chat_id", :unique=>true, :using=>:btree}) + -> 0.0039s +-- add_index("chat_names", ["user_id", "service_id"], {:name=>"index_chat_names_on_user_id_and_service_id", :unique=>true, :using=>:btree}) + -> 0.0036s +-- create_table("chat_teams", {:force=>:cascade}) + -> 0.0068s +-- add_index("chat_teams", ["namespace_id"], {:name=>"index_chat_teams_on_namespace_id", :unique=>true, :using=>:btree}) + -> 0.0098s +-- create_table("ci_build_trace_section_names", {:force=>:cascade}) + -> 0.0048s +-- add_index("ci_build_trace_section_names", ["project_id", "name"], {:name=>"index_ci_build_trace_section_names_on_project_id_and_name", :unique=>true, :using=>:btree}) + -> 0.0035s +-- create_table("ci_build_trace_sections", {:force=>:cascade}) + -> 0.0040s +-- add_index("ci_build_trace_sections", ["build_id", "section_name_id"], {:name=>"index_ci_build_trace_sections_on_build_id_and_section_name_id", :unique=>true, :using=>:btree}) + -> 0.0035s +-- add_index("ci_build_trace_sections", ["project_id"], {:name=>"index_ci_build_trace_sections_on_project_id", :using=>:btree}) + -> 0.0033s +-- create_table("ci_builds", {:force=>:cascade}) + -> 0.0062s +-- add_index("ci_builds", ["auto_canceled_by_id"], {:name=>"index_ci_builds_on_auto_canceled_by_id", :using=>:btree}) + -> 0.0035s +-- add_index("ci_builds", ["commit_id", "stage_idx", "created_at"], {:name=>"index_ci_builds_on_commit_id_and_stage_idx_and_created_at", :using=>:btree}) + -> 0.0032s +-- add_index("ci_builds", ["commit_id", "status", "type"], {:name=>"index_ci_builds_on_commit_id_and_status_and_type", :using=>:btree}) + -> 0.0032s +-- add_index("ci_builds", ["commit_id", "type", "name", "ref"], {:name=>"index_ci_builds_on_commit_id_and_type_and_name_and_ref", :using=>:btree}) + -> 0.0035s +-- add_index("ci_builds", ["commit_id", "type", "ref"], {:name=>"index_ci_builds_on_commit_id_and_type_and_ref", :using=>:btree}) + -> 0.0042s +-- add_index("ci_builds", ["project_id", "id"], {:name=>"index_ci_builds_on_project_id_and_id", :using=>:btree}) + -> 0.0031s +-- add_index("ci_builds", ["protected"], {:name=>"index_ci_builds_on_protected", :using=>:btree}) + -> 0.0031s +-- add_index("ci_builds", ["runner_id"], {:name=>"index_ci_builds_on_runner_id", :using=>:btree}) + -> 0.0033s +-- add_index("ci_builds", ["stage_id"], {:name=>"index_ci_builds_on_stage_id", :using=>:btree}) + -> 0.0035s +-- add_index("ci_builds", ["status", "type", "runner_id"], {:name=>"index_ci_builds_on_status_and_type_and_runner_id", :using=>:btree}) + -> 0.0031s +-- add_index("ci_builds", ["status"], {:name=>"index_ci_builds_on_status", :using=>:btree}) + -> 0.0032s +-- add_index("ci_builds", ["token"], {:name=>"index_ci_builds_on_token", :unique=>true, :using=>:btree}) + -> 0.0028s +-- add_index("ci_builds", ["updated_at"], {:name=>"index_ci_builds_on_updated_at", :using=>:btree}) + -> 0.0047s +-- add_index("ci_builds", ["user_id"], {:name=>"index_ci_builds_on_user_id", :using=>:btree}) + -> 0.0029s +-- create_table("ci_group_variables", {:force=>:cascade}) + -> 0.0055s +-- add_index("ci_group_variables", ["group_id", "key"], {:name=>"index_ci_group_variables_on_group_id_and_key", :unique=>true, :using=>:btree}) + -> 0.0028s +-- create_table("ci_job_artifacts", {:force=>:cascade}) + -> 0.0048s +-- add_index("ci_job_artifacts", ["job_id", "file_type"], {:name=>"index_ci_job_artifacts_on_job_id_and_file_type", :unique=>true, :using=>:btree}) + -> 0.0027s +-- add_index("ci_job_artifacts", ["project_id"], {:name=>"index_ci_job_artifacts_on_project_id", :using=>:btree}) + -> 0.0028s +-- create_table("ci_pipeline_schedule_variables", {:force=>:cascade}) + -> 0.0044s +-- add_index("ci_pipeline_schedule_variables", ["pipeline_schedule_id", "key"], {:name=>"index_ci_pipeline_schedule_variables_on_schedule_id_and_key", :unique=>true, :using=>:btree}) + -> 0.0032s +-- create_table("ci_pipeline_schedules", {:force=>:cascade}) + -> 0.0047s +-- add_index("ci_pipeline_schedules", ["next_run_at", "active"], {:name=>"index_ci_pipeline_schedules_on_next_run_at_and_active", :using=>:btree}) + -> 0.0029s +-- add_index("ci_pipeline_schedules", ["project_id"], {:name=>"index_ci_pipeline_schedules_on_project_id", :using=>:btree}) + -> 0.0028s +-- create_table("ci_pipeline_variables", {:force=>:cascade}) + -> 0.0045s +-- add_index("ci_pipeline_variables", ["pipeline_id", "key"], {:name=>"index_ci_pipeline_variables_on_pipeline_id_and_key", :unique=>true, :using=>:btree}) + -> 0.0030s +-- create_table("ci_pipelines", {:force=>:cascade}) + -> 0.0057s +-- add_index("ci_pipelines", ["auto_canceled_by_id"], {:name=>"index_ci_pipelines_on_auto_canceled_by_id", :using=>:btree}) + -> 0.0030s +-- add_index("ci_pipelines", ["pipeline_schedule_id"], {:name=>"index_ci_pipelines_on_pipeline_schedule_id", :using=>:btree}) + -> 0.0031s +-- add_index("ci_pipelines", ["project_id", "ref", "status", "id"], {:name=>"index_ci_pipelines_on_project_id_and_ref_and_status_and_id", :using=>:btree}) + -> 0.0032s +-- add_index("ci_pipelines", ["project_id", "sha"], {:name=>"index_ci_pipelines_on_project_id_and_sha", :using=>:btree}) + -> 0.0032s +-- add_index("ci_pipelines", ["project_id"], {:name=>"index_ci_pipelines_on_project_id", :using=>:btree}) + -> 0.0035s +-- add_index("ci_pipelines", ["status"], {:name=>"index_ci_pipelines_on_status", :using=>:btree}) + -> 0.0032s +-- add_index("ci_pipelines", ["user_id"], {:name=>"index_ci_pipelines_on_user_id", :using=>:btree}) + -> 0.0029s +-- create_table("ci_runner_projects", {:force=>:cascade}) + -> 0.0035s +-- add_index("ci_runner_projects", ["project_id"], {:name=>"index_ci_runner_projects_on_project_id", :using=>:btree}) + -> 0.0029s +-- add_index("ci_runner_projects", ["runner_id"], {:name=>"index_ci_runner_projects_on_runner_id", :using=>:btree}) + -> 0.0028s +-- create_table("ci_runners", {:force=>:cascade}) + -> 0.0059s +-- add_index("ci_runners", ["contacted_at"], {:name=>"index_ci_runners_on_contacted_at", :using=>:btree}) + -> 0.0030s +-- add_index("ci_runners", ["is_shared"], {:name=>"index_ci_runners_on_is_shared", :using=>:btree}) + -> 0.0030s +-- add_index("ci_runners", ["locked"], {:name=>"index_ci_runners_on_locked", :using=>:btree}) + -> 0.0030s +-- add_index("ci_runners", ["token"], {:name=>"index_ci_runners_on_token", :using=>:btree}) + -> 0.0029s +-- create_table("ci_stages", {:force=>:cascade}) + -> 0.0046s +-- add_index("ci_stages", ["pipeline_id", "name"], {:name=>"index_ci_stages_on_pipeline_id_and_name", :using=>:btree}) + -> 0.0031s +-- add_index("ci_stages", ["pipeline_id"], {:name=>"index_ci_stages_on_pipeline_id", :using=>:btree}) + -> 0.0030s +-- add_index("ci_stages", ["project_id"], {:name=>"index_ci_stages_on_project_id", :using=>:btree}) + -> 0.0028s +-- create_table("ci_trigger_requests", {:force=>:cascade}) + -> 0.0058s +-- add_index("ci_trigger_requests", ["commit_id"], {:name=>"index_ci_trigger_requests_on_commit_id", :using=>:btree}) + -> 0.0031s +-- create_table("ci_triggers", {:force=>:cascade}) + -> 0.0043s +-- add_index("ci_triggers", ["project_id"], {:name=>"index_ci_triggers_on_project_id", :using=>:btree}) + -> 0.0033s +-- create_table("ci_variables", {:force=>:cascade}) + -> 0.0059s +-- add_index("ci_variables", ["project_id", "key", "environment_scope"], {:name=>"index_ci_variables_on_project_id_and_key_and_environment_scope", :unique=>true, :using=>:btree}) + -> 0.0031s +-- create_table("cluster_platforms_kubernetes", {:force=>:cascade}) + -> 0.0053s +-- add_index("cluster_platforms_kubernetes", ["cluster_id"], {:name=>"index_cluster_platforms_kubernetes_on_cluster_id", :unique=>true, :using=>:btree}) + -> 0.0028s +-- create_table("cluster_projects", {:force=>:cascade}) + -> 0.0032s +-- add_index("cluster_projects", ["cluster_id"], {:name=>"index_cluster_projects_on_cluster_id", :using=>:btree}) + -> 0.0035s +-- add_index("cluster_projects", ["project_id"], {:name=>"index_cluster_projects_on_project_id", :using=>:btree}) + -> 0.0030s +-- create_table("cluster_providers_gcp", {:force=>:cascade}) + -> 0.0051s +-- add_index("cluster_providers_gcp", ["cluster_id"], {:name=>"index_cluster_providers_gcp_on_cluster_id", :unique=>true, :using=>:btree}) + -> 0.0034s +-- create_table("clusters", {:force=>:cascade}) + -> 0.0052s +-- add_index("clusters", ["enabled"], {:name=>"index_clusters_on_enabled", :using=>:btree}) + -> 0.0031s +-- add_index("clusters", ["user_id"], {:name=>"index_clusters_on_user_id", :using=>:btree}) + -> 0.0028s +-- create_table("clusters_applications_helm", {:force=>:cascade}) + -> 0.0045s +-- create_table("clusters_applications_ingress", {:force=>:cascade}) + -> 0.0044s +-- create_table("clusters_applications_prometheus", {:force=>:cascade}) + -> 0.0047s +-- create_table("container_repositories", {:force=>:cascade}) + -> 0.0050s +-- add_index("container_repositories", ["project_id", "name"], {:name=>"index_container_repositories_on_project_id_and_name", :unique=>true, :using=>:btree}) + -> 0.0032s +-- add_index("container_repositories", ["project_id"], {:name=>"index_container_repositories_on_project_id", :using=>:btree}) + -> 0.0032s +-- create_table("conversational_development_index_metrics", {:force=>:cascade}) + -> 0.0076s +-- create_table("deploy_keys_projects", {:force=>:cascade}) + -> 0.0037s +-- add_index("deploy_keys_projects", ["project_id"], {:name=>"index_deploy_keys_projects_on_project_id", :using=>:btree}) + -> 0.0032s +-- create_table("deployments", {:force=>:cascade}) + -> 0.0049s +-- add_index("deployments", ["created_at"], {:name=>"index_deployments_on_created_at", :using=>:btree}) + -> 0.0034s +-- add_index("deployments", ["environment_id", "id"], {:name=>"index_deployments_on_environment_id_and_id", :using=>:btree}) + -> 0.0028s +-- add_index("deployments", ["environment_id", "iid", "project_id"], {:name=>"index_deployments_on_environment_id_and_iid_and_project_id", :using=>:btree}) + -> 0.0029s +-- add_index("deployments", ["project_id", "iid"], {:name=>"index_deployments_on_project_id_and_iid", :unique=>true, :using=>:btree}) + -> 0.0032s +-- create_table("emails", {:force=>:cascade}) + -> 0.0046s +-- add_index("emails", ["confirmation_token"], {:name=>"index_emails_on_confirmation_token", :unique=>true, :using=>:btree}) + -> 0.0030s +-- add_index("emails", ["email"], {:name=>"index_emails_on_email", :unique=>true, :using=>:btree}) + -> 0.0035s +-- add_index("emails", ["user_id"], {:name=>"index_emails_on_user_id", :using=>:btree}) + -> 0.0028s +-- create_table("environments", {:force=>:cascade}) + -> 0.0052s +-- add_index("environments", ["project_id", "name"], {:name=>"index_environments_on_project_id_and_name", :unique=>true, :using=>:btree}) + -> 0.0031s +-- add_index("environments", ["project_id", "slug"], {:name=>"index_environments_on_project_id_and_slug", :unique=>true, :using=>:btree}) + -> 0.0028s +-- create_table("events", {:force=>:cascade}) + -> 0.0046s +-- add_index("events", ["action"], {:name=>"index_events_on_action", :using=>:btree}) + -> 0.0032s +-- add_index("events", ["author_id"], {:name=>"index_events_on_author_id", :using=>:btree}) + -> 0.0027s +-- add_index("events", ["project_id", "id"], {:name=>"index_events_on_project_id_and_id", :using=>:btree}) + -> 0.0027s +-- add_index("events", ["target_type", "target_id"], {:name=>"index_events_on_target_type_and_target_id", :using=>:btree}) + -> 0.0027s +-- create_table("feature_gates", {:force=>:cascade}) + -> 0.0046s +-- add_index("feature_gates", ["feature_key", "key", "value"], {:name=>"index_feature_gates_on_feature_key_and_key_and_value", :unique=>true, :using=>:btree}) + -> 0.0031s +-- create_table("features", {:force=>:cascade}) + -> 0.0041s +-- add_index("features", ["key"], {:name=>"index_features_on_key", :unique=>true, :using=>:btree}) + -> 0.0030s +-- create_table("fork_network_members", {:force=>:cascade}) + -> 0.0033s +-- add_index("fork_network_members", ["fork_network_id"], {:name=>"index_fork_network_members_on_fork_network_id", :using=>:btree}) + -> 0.0033s +-- add_index("fork_network_members", ["project_id"], {:name=>"index_fork_network_members_on_project_id", :unique=>true, :using=>:btree}) + -> 0.0029s +-- create_table("fork_networks", {:force=>:cascade}) + -> 0.0049s +-- add_index("fork_networks", ["root_project_id"], {:name=>"index_fork_networks_on_root_project_id", :unique=>true, :using=>:btree}) + -> 0.0029s +-- create_table("forked_project_links", {:force=>:cascade}) + -> 0.0032s +-- add_index("forked_project_links", ["forked_to_project_id"], {:name=>"index_forked_project_links_on_forked_to_project_id", :unique=>true, :using=>:btree}) + -> 0.0030s +-- create_table("gcp_clusters", {:force=>:cascade}) + -> 0.0074s +-- add_index("gcp_clusters", ["project_id"], {:name=>"index_gcp_clusters_on_project_id", :unique=>true, :using=>:btree}) + -> 0.0030s +-- create_table("gpg_key_subkeys", {:force=>:cascade}) + -> 0.0042s +-- add_index("gpg_key_subkeys", ["fingerprint"], {:name=>"index_gpg_key_subkeys_on_fingerprint", :unique=>true, :using=>:btree}) + -> 0.0029s +-- add_index("gpg_key_subkeys", ["gpg_key_id"], {:name=>"index_gpg_key_subkeys_on_gpg_key_id", :using=>:btree}) + -> 0.0032s +-- add_index("gpg_key_subkeys", ["keyid"], {:name=>"index_gpg_key_subkeys_on_keyid", :unique=>true, :using=>:btree}) + -> 0.0027s +-- create_table("gpg_keys", {:force=>:cascade}) + -> 0.0042s +-- add_index("gpg_keys", ["fingerprint"], {:name=>"index_gpg_keys_on_fingerprint", :unique=>true, :using=>:btree}) + -> 0.0032s +-- add_index("gpg_keys", ["primary_keyid"], {:name=>"index_gpg_keys_on_primary_keyid", :unique=>true, :using=>:btree}) + -> 0.0026s +-- add_index("gpg_keys", ["user_id"], {:name=>"index_gpg_keys_on_user_id", :using=>:btree}) + -> 0.0028s +-- create_table("gpg_signatures", {:force=>:cascade}) + -> 0.0054s +-- add_index("gpg_signatures", ["commit_sha"], {:name=>"index_gpg_signatures_on_commit_sha", :unique=>true, :using=>:btree}) + -> 0.0029s +-- add_index("gpg_signatures", ["gpg_key_id"], {:name=>"index_gpg_signatures_on_gpg_key_id", :using=>:btree}) + -> 0.0026s +-- add_index("gpg_signatures", ["gpg_key_primary_keyid"], {:name=>"index_gpg_signatures_on_gpg_key_primary_keyid", :using=>:btree}) + -> 0.0029s +-- add_index("gpg_signatures", ["gpg_key_subkey_id"], {:name=>"index_gpg_signatures_on_gpg_key_subkey_id", :using=>:btree}) + -> 0.0032s +-- add_index("gpg_signatures", ["project_id"], {:name=>"index_gpg_signatures_on_project_id", :using=>:btree}) + -> 0.0028s +-- create_table("group_custom_attributes", {:force=>:cascade}) + -> 0.0044s +-- add_index("group_custom_attributes", ["group_id", "key"], {:name=>"index_group_custom_attributes_on_group_id_and_key", :unique=>true, :using=>:btree}) + -> 0.0032s +-- add_index("group_custom_attributes", ["key", "value"], {:name=>"index_group_custom_attributes_on_key_and_value", :using=>:btree}) + -> 0.0028s +-- create_table("identities", {:force=>:cascade}) + -> 0.0043s +-- add_index("identities", ["user_id"], {:name=>"index_identities_on_user_id", :using=>:btree}) + -> 0.0034s +-- create_table("issue_assignees", {:id=>false, :force=>:cascade}) + -> 0.0013s +-- add_index("issue_assignees", ["issue_id", "user_id"], {:name=>"index_issue_assignees_on_issue_id_and_user_id", :unique=>true, :using=>:btree}) + -> 0.0028s +-- add_index("issue_assignees", ["user_id"], {:name=>"index_issue_assignees_on_user_id", :using=>:btree}) + -> 0.0029s +-- create_table("issue_metrics", {:force=>:cascade}) + -> 0.0032s +-- add_index("issue_metrics", ["issue_id"], {:name=>"index_issue_metrics", :using=>:btree}) + -> 0.0029s +-- create_table("issues", {:force=>:cascade}) + -> 0.0051s +-- add_index("issues", ["author_id"], {:name=>"index_issues_on_author_id", :using=>:btree}) + -> 0.0028s +-- add_index("issues", ["confidential"], {:name=>"index_issues_on_confidential", :using=>:btree}) + -> 0.0029s +-- add_index("issues", ["description"], {:name=>"index_issues_on_description_trigram", :using=>:gin, :opclasses=>{"description"=>"gin_trgm_ops"}}) + -> 0.0022s +-- add_index("issues", ["milestone_id"], {:name=>"index_issues_on_milestone_id", :using=>:btree}) + -> 0.0027s +-- add_index("issues", ["moved_to_id"], {:name=>"index_issues_on_moved_to_id", :where=>"(moved_to_id IS NOT NULL)", :using=>:btree}) + -> 0.0030s +-- add_index("issues", ["project_id", "created_at", "id", "state"], {:name=>"index_issues_on_project_id_and_created_at_and_id_and_state", :using=>:btree}) + -> 0.0039s +-- add_index("issues", ["project_id", "due_date", "id", "state"], {:name=>"idx_issues_on_project_id_and_due_date_and_id_and_state_partial", :where=>"(due_date IS NOT NULL)", :using=>:btree}) + -> 0.0031s +-- add_index("issues", ["project_id", "iid"], {:name=>"index_issues_on_project_id_and_iid", :unique=>true, :using=>:btree}) + -> 0.0032s +-- add_index("issues", ["project_id", "updated_at", "id", "state"], {:name=>"index_issues_on_project_id_and_updated_at_and_id_and_state", :using=>:btree}) + -> 0.0035s +-- add_index("issues", ["relative_position"], {:name=>"index_issues_on_relative_position", :using=>:btree}) + -> 0.0030s +-- add_index("issues", ["state"], {:name=>"index_issues_on_state", :using=>:btree}) + -> 0.0027s +-- add_index("issues", ["title"], {:name=>"index_issues_on_title_trigram", :using=>:gin, :opclasses=>{"title"=>"gin_trgm_ops"}}) + -> 0.0021s +-- add_index("issues", ["updated_at"], {:name=>"index_issues_on_updated_at", :using=>:btree}) + -> 0.0030s +-- add_index("issues", ["updated_by_id"], {:name=>"index_issues_on_updated_by_id", :where=>"(updated_by_id IS NOT NULL)", :using=>:btree}) + -> 0.0028s +-- create_table("keys", {:force=>:cascade}) + -> 0.0048s +-- add_index("keys", ["fingerprint"], {:name=>"index_keys_on_fingerprint", :unique=>true, :using=>:btree}) + -> 0.0028s +-- add_index("keys", ["user_id"], {:name=>"index_keys_on_user_id", :using=>:btree}) + -> 0.0029s +-- create_table("label_links", {:force=>:cascade}) + -> 0.0041s +-- add_index("label_links", ["label_id"], {:name=>"index_label_links_on_label_id", :using=>:btree}) + -> 0.0027s +-- add_index("label_links", ["target_id", "target_type"], {:name=>"index_label_links_on_target_id_and_target_type", :using=>:btree}) + -> 0.0028s +-- create_table("label_priorities", {:force=>:cascade}) + -> 0.0031s +-- add_index("label_priorities", ["priority"], {:name=>"index_label_priorities_on_priority", :using=>:btree}) + -> 0.0028s +-- add_index("label_priorities", ["project_id", "label_id"], {:name=>"index_label_priorities_on_project_id_and_label_id", :unique=>true, :using=>:btree}) + -> 0.0027s +-- create_table("labels", {:force=>:cascade}) + -> 0.0046s +-- add_index("labels", ["group_id", "project_id", "title"], {:name=>"index_labels_on_group_id_and_project_id_and_title", :unique=>true, :using=>:btree}) + -> 0.0028s +-- add_index("labels", ["project_id"], {:name=>"index_labels_on_project_id", :using=>:btree}) + -> 0.0032s +-- add_index("labels", ["template"], {:name=>"index_labels_on_template", :where=>"template", :using=>:btree}) + -> 0.0027s +-- add_index("labels", ["title"], {:name=>"index_labels_on_title", :using=>:btree}) + -> 0.0030s +-- add_index("labels", ["type", "project_id"], {:name=>"index_labels_on_type_and_project_id", :using=>:btree}) + -> 0.0028s +-- create_table("lfs_objects", {:force=>:cascade}) + -> 0.0040s +-- add_index("lfs_objects", ["oid"], {:name=>"index_lfs_objects_on_oid", :unique=>true, :using=>:btree}) + -> 0.0032s +-- create_table("lfs_objects_projects", {:force=>:cascade}) + -> 0.0035s +-- add_index("lfs_objects_projects", ["project_id"], {:name=>"index_lfs_objects_projects_on_project_id", :using=>:btree}) + -> 0.0025s +-- create_table("lists", {:force=>:cascade}) + -> 0.0033s +-- add_index("lists", ["board_id", "label_id"], {:name=>"index_lists_on_board_id_and_label_id", :unique=>true, :using=>:btree}) + -> 0.0026s +-- add_index("lists", ["label_id"], {:name=>"index_lists_on_label_id", :using=>:btree}) + -> 0.0026s +-- create_table("members", {:force=>:cascade}) + -> 0.0046s +-- add_index("members", ["access_level"], {:name=>"index_members_on_access_level", :using=>:btree}) + -> 0.0028s +-- add_index("members", ["invite_token"], {:name=>"index_members_on_invite_token", :unique=>true, :using=>:btree}) + -> 0.0027s +-- add_index("members", ["requested_at"], {:name=>"index_members_on_requested_at", :using=>:btree}) + -> 0.0025s +-- add_index("members", ["source_id", "source_type"], {:name=>"index_members_on_source_id_and_source_type", :using=>:btree}) + -> 0.0027s +-- add_index("members", ["user_id"], {:name=>"index_members_on_user_id", :using=>:btree}) + -> 0.0026s +-- create_table("merge_request_diff_commits", {:id=>false, :force=>:cascade}) + -> 0.0027s +-- add_index("merge_request_diff_commits", ["merge_request_diff_id", "relative_order"], {:name=>"index_merge_request_diff_commits_on_mr_diff_id_and_order", :unique=>true, :using=>:btree}) + -> 0.0032s +-- add_index("merge_request_diff_commits", ["sha"], {:name=>"index_merge_request_diff_commits_on_sha", :using=>:btree}) + -> 0.0029s +-- create_table("merge_request_diff_files", {:id=>false, :force=>:cascade}) + -> 0.0027s +-- add_index("merge_request_diff_files", ["merge_request_diff_id", "relative_order"], {:name=>"index_merge_request_diff_files_on_mr_diff_id_and_order", :unique=>true, :using=>:btree}) + -> 0.0027s +-- create_table("merge_request_diffs", {:force=>:cascade}) + -> 0.0042s +-- add_index("merge_request_diffs", ["merge_request_id", "id"], {:name=>"index_merge_request_diffs_on_merge_request_id_and_id", :using=>:btree}) + -> 0.0030s +-- create_table("merge_request_metrics", {:force=>:cascade}) + -> 0.0034s +-- add_index("merge_request_metrics", ["first_deployed_to_production_at"], {:name=>"index_merge_request_metrics_on_first_deployed_to_production_at", :using=>:btree}) + -> 0.0028s +-- add_index("merge_request_metrics", ["merge_request_id"], {:name=>"index_merge_request_metrics", :using=>:btree}) + -> 0.0025s +-- add_index("merge_request_metrics", ["pipeline_id"], {:name=>"index_merge_request_metrics_on_pipeline_id", :using=>:btree}) + -> 0.0026s +-- create_table("merge_requests", {:force=>:cascade}) + -> 0.0066s +-- add_index("merge_requests", ["assignee_id"], {:name=>"index_merge_requests_on_assignee_id", :using=>:btree}) + -> 0.0029s +-- add_index("merge_requests", ["author_id"], {:name=>"index_merge_requests_on_author_id", :using=>:btree}) + -> 0.0026s +-- add_index("merge_requests", ["created_at"], {:name=>"index_merge_requests_on_created_at", :using=>:btree}) + -> 0.0026s +-- add_index("merge_requests", ["description"], {:name=>"index_merge_requests_on_description_trigram", :using=>:gin, :opclasses=>{"description"=>"gin_trgm_ops"}}) + -> 0.0020s +-- add_index("merge_requests", ["head_pipeline_id"], {:name=>"index_merge_requests_on_head_pipeline_id", :using=>:btree}) + -> 0.0027s +-- add_index("merge_requests", ["latest_merge_request_diff_id"], {:name=>"index_merge_requests_on_latest_merge_request_diff_id", :using=>:btree}) + -> 0.0025s +-- add_index("merge_requests", ["merge_user_id"], {:name=>"index_merge_requests_on_merge_user_id", :where=>"(merge_user_id IS NOT NULL)", :using=>:btree}) + -> 0.0029s +-- add_index("merge_requests", ["milestone_id"], {:name=>"index_merge_requests_on_milestone_id", :using=>:btree}) + -> 0.0030s +-- add_index("merge_requests", ["source_branch"], {:name=>"index_merge_requests_on_source_branch", :using=>:btree}) + -> 0.0026s +-- add_index("merge_requests", ["source_project_id", "source_branch"], {:name=>"index_merge_requests_on_source_project_and_branch_state_opened", :where=>"((state)::text = 'opened'::text)", :using=>:btree}) + -> 0.0029s +-- add_index("merge_requests", ["source_project_id", "source_branch"], {:name=>"index_merge_requests_on_source_project_id_and_source_branch", :using=>:btree}) + -> 0.0031s +-- add_index("merge_requests", ["target_branch"], {:name=>"index_merge_requests_on_target_branch", :using=>:btree}) + -> 0.0028s +-- add_index("merge_requests", ["target_project_id", "iid"], {:name=>"index_merge_requests_on_target_project_id_and_iid", :unique=>true, :using=>:btree}) + -> 0.0027s +-- add_index("merge_requests", ["target_project_id", "merge_commit_sha", "id"], {:name=>"index_merge_requests_on_tp_id_and_merge_commit_sha_and_id", :using=>:btree}) + -> 0.0029s +-- add_index("merge_requests", ["title"], {:name=>"index_merge_requests_on_title", :using=>:btree}) + -> 0.0026s +-- add_index("merge_requests", ["title"], {:name=>"index_merge_requests_on_title_trigram", :using=>:gin, :opclasses=>{"title"=>"gin_trgm_ops"}}) + -> 0.0020s +-- add_index("merge_requests", ["updated_by_id"], {:name=>"index_merge_requests_on_updated_by_id", :where=>"(updated_by_id IS NOT NULL)", :using=>:btree}) + -> 0.0029s +-- create_table("merge_requests_closing_issues", {:force=>:cascade}) + -> 0.0031s +-- add_index("merge_requests_closing_issues", ["issue_id"], {:name=>"index_merge_requests_closing_issues_on_issue_id", :using=>:btree}) + -> 0.0026s +-- add_index("merge_requests_closing_issues", ["merge_request_id"], {:name=>"index_merge_requests_closing_issues_on_merge_request_id", :using=>:btree}) + -> 0.0028s +-- create_table("milestones", {:force=>:cascade}) + -> 0.0044s +-- add_index("milestones", ["description"], {:name=>"index_milestones_on_description_trigram", :using=>:gin, :opclasses=>{"description"=>"gin_trgm_ops"}}) + -> 0.0022s +-- add_index("milestones", ["due_date"], {:name=>"index_milestones_on_due_date", :using=>:btree}) + -> 0.0033s +-- add_index("milestones", ["group_id"], {:name=>"index_milestones_on_group_id", :using=>:btree}) + -> 0.0028s +-- add_index("milestones", ["project_id", "iid"], {:name=>"index_milestones_on_project_id_and_iid", :unique=>true, :using=>:btree}) + -> 0.0028s +-- add_index("milestones", ["title"], {:name=>"index_milestones_on_title", :using=>:btree}) + -> 0.0026s +-- add_index("milestones", ["title"], {:name=>"index_milestones_on_title_trigram", :using=>:gin, :opclasses=>{"title"=>"gin_trgm_ops"}}) + -> 0.0021s +-- create_table("namespaces", {:force=>:cascade}) + -> 0.0068s +-- add_index("namespaces", ["created_at"], {:name=>"index_namespaces_on_created_at", :using=>:btree}) + -> 0.0030s +-- add_index("namespaces", ["name", "parent_id"], {:name=>"index_namespaces_on_name_and_parent_id", :unique=>true, :using=>:btree}) + -> 0.0030s +-- add_index("namespaces", ["name"], {:name=>"index_namespaces_on_name_trigram", :using=>:gin, :opclasses=>{"name"=>"gin_trgm_ops"}}) + -> 0.0020s +-- add_index("namespaces", ["owner_id"], {:name=>"index_namespaces_on_owner_id", :using=>:btree}) + -> 0.0028s +-- add_index("namespaces", ["parent_id", "id"], {:name=>"index_namespaces_on_parent_id_and_id", :unique=>true, :using=>:btree}) + -> 0.0032s +-- add_index("namespaces", ["path"], {:name=>"index_namespaces_on_path", :using=>:btree}) + -> 0.0031s +-- add_index("namespaces", ["path"], {:name=>"index_namespaces_on_path_trigram", :using=>:gin, :opclasses=>{"path"=>"gin_trgm_ops"}}) + -> 0.0019s +-- add_index("namespaces", ["require_two_factor_authentication"], {:name=>"index_namespaces_on_require_two_factor_authentication", :using=>:btree}) + -> 0.0029s +-- add_index("namespaces", ["type"], {:name=>"index_namespaces_on_type", :using=>:btree}) + -> 0.0032s +-- create_table("notes", {:force=>:cascade}) + -> 0.0055s +-- add_index("notes", ["author_id"], {:name=>"index_notes_on_author_id", :using=>:btree}) + -> 0.0029s +-- add_index("notes", ["commit_id"], {:name=>"index_notes_on_commit_id", :using=>:btree}) + -> 0.0028s +-- add_index("notes", ["created_at"], {:name=>"index_notes_on_created_at", :using=>:btree}) + -> 0.0029s +-- add_index("notes", ["discussion_id"], {:name=>"index_notes_on_discussion_id", :using=>:btree}) + -> 0.0029s +-- add_index("notes", ["line_code"], {:name=>"index_notes_on_line_code", :using=>:btree}) + -> 0.0029s +-- add_index("notes", ["note"], {:name=>"index_notes_on_note_trigram", :using=>:gin, :opclasses=>{"note"=>"gin_trgm_ops"}}) + -> 0.0024s +-- add_index("notes", ["noteable_id", "noteable_type"], {:name=>"index_notes_on_noteable_id_and_noteable_type", :using=>:btree}) + -> 0.0029s +-- add_index("notes", ["noteable_type"], {:name=>"index_notes_on_noteable_type", :using=>:btree}) + -> 0.0030s +-- add_index("notes", ["project_id", "noteable_type"], {:name=>"index_notes_on_project_id_and_noteable_type", :using=>:btree}) + -> 0.0027s +-- add_index("notes", ["updated_at"], {:name=>"index_notes_on_updated_at", :using=>:btree}) + -> 0.0026s +-- create_table("notification_settings", {:force=>:cascade}) + -> 0.0053s +-- add_index("notification_settings", ["source_id", "source_type"], {:name=>"index_notification_settings_on_source_id_and_source_type", :using=>:btree}) + -> 0.0028s +-- add_index("notification_settings", ["user_id", "source_id", "source_type"], {:name=>"index_notifications_on_user_id_and_source_id_and_source_type", :unique=>true, :using=>:btree}) + -> 0.0030s +-- add_index("notification_settings", ["user_id"], {:name=>"index_notification_settings_on_user_id", :using=>:btree}) + -> 0.0031s +-- create_table("oauth_access_grants", {:force=>:cascade}) + -> 0.0042s +-- add_index("oauth_access_grants", ["token"], {:name=>"index_oauth_access_grants_on_token", :unique=>true, :using=>:btree}) + -> 0.0031s +-- create_table("oauth_access_tokens", {:force=>:cascade}) + -> 0.0051s +-- add_index("oauth_access_tokens", ["refresh_token"], {:name=>"index_oauth_access_tokens_on_refresh_token", :unique=>true, :using=>:btree}) + -> 0.0030s +-- add_index("oauth_access_tokens", ["resource_owner_id"], {:name=>"index_oauth_access_tokens_on_resource_owner_id", :using=>:btree}) + -> 0.0025s +-- add_index("oauth_access_tokens", ["token"], {:name=>"index_oauth_access_tokens_on_token", :unique=>true, :using=>:btree}) + -> 0.0026s +-- create_table("oauth_applications", {:force=>:cascade}) + -> 0.0049s +-- add_index("oauth_applications", ["owner_id", "owner_type"], {:name=>"index_oauth_applications_on_owner_id_and_owner_type", :using=>:btree}) + -> 0.0030s +-- add_index("oauth_applications", ["uid"], {:name=>"index_oauth_applications_on_uid", :unique=>true, :using=>:btree}) + -> 0.0032s +-- create_table("oauth_openid_requests", {:force=>:cascade}) + -> 0.0048s +-- create_table("pages_domains", {:force=>:cascade}) + -> 0.0052s +-- add_index("pages_domains", ["domain"], {:name=>"index_pages_domains_on_domain", :unique=>true, :using=>:btree}) + -> 0.0027s +-- add_index("pages_domains", ["project_id"], {:name=>"index_pages_domains_on_project_id", :using=>:btree}) + -> 0.0030s +-- create_table("personal_access_tokens", {:force=>:cascade}) + -> 0.0056s +-- add_index("personal_access_tokens", ["token"], {:name=>"index_personal_access_tokens_on_token", :unique=>true, :using=>:btree}) + -> 0.0032s +-- add_index("personal_access_tokens", ["user_id"], {:name=>"index_personal_access_tokens_on_user_id", :using=>:btree}) + -> 0.0028s +-- create_table("project_authorizations", {:id=>false, :force=>:cascade}) + -> 0.0018s +-- add_index("project_authorizations", ["project_id"], {:name=>"index_project_authorizations_on_project_id", :using=>:btree}) + -> 0.0033s +-- add_index("project_authorizations", ["user_id", "project_id", "access_level"], {:name=>"index_project_authorizations_on_user_id_project_id_access_level", :unique=>true, :using=>:btree}) + -> 0.0029s +-- create_table("project_auto_devops", {:force=>:cascade}) + -> 0.0043s +-- add_index("project_auto_devops", ["project_id"], {:name=>"index_project_auto_devops_on_project_id", :unique=>true, :using=>:btree}) + -> 0.0029s +-- create_table("project_custom_attributes", {:force=>:cascade}) + -> 0.0047s +-- add_index("project_custom_attributes", ["key", "value"], {:name=>"index_project_custom_attributes_on_key_and_value", :using=>:btree}) + -> 0.0030s +-- add_index("project_custom_attributes", ["project_id", "key"], {:name=>"index_project_custom_attributes_on_project_id_and_key", :unique=>true, :using=>:btree}) + -> 0.0028s +-- create_table("project_features", {:force=>:cascade}) + -> 0.0038s +-- add_index("project_features", ["project_id"], {:name=>"index_project_features_on_project_id", :using=>:btree}) + -> 0.0029s +-- create_table("project_group_links", {:force=>:cascade}) + -> 0.0036s +-- add_index("project_group_links", ["group_id"], {:name=>"index_project_group_links_on_group_id", :using=>:btree}) + -> 0.0028s +-- add_index("project_group_links", ["project_id"], {:name=>"index_project_group_links_on_project_id", :using=>:btree}) + -> 0.0030s +-- create_table("project_import_data", {:force=>:cascade}) + -> 0.0049s +-- add_index("project_import_data", ["project_id"], {:name=>"index_project_import_data_on_project_id", :using=>:btree}) + -> 0.0027s +-- create_table("project_statistics", {:force=>:cascade}) + -> 0.0046s +-- add_index("project_statistics", ["namespace_id"], {:name=>"index_project_statistics_on_namespace_id", :using=>:btree}) + -> 0.0027s +-- add_index("project_statistics", ["project_id"], {:name=>"index_project_statistics_on_project_id", :unique=>true, :using=>:btree}) + -> 0.0029s +-- create_table("projects", {:force=>:cascade}) + -> 0.0090s +-- add_index("projects", ["ci_id"], {:name=>"index_projects_on_ci_id", :using=>:btree}) + -> 0.0033s +-- add_index("projects", ["created_at"], {:name=>"index_projects_on_created_at", :using=>:btree}) + -> 0.0030s +-- add_index("projects", ["creator_id"], {:name=>"index_projects_on_creator_id", :using=>:btree}) + -> 0.0028s +-- add_index("projects", ["description"], {:name=>"index_projects_on_description_trigram", :using=>:gin, :opclasses=>{"description"=>"gin_trgm_ops"}}) + -> 0.0022s +-- add_index("projects", ["last_activity_at"], {:name=>"index_projects_on_last_activity_at", :using=>:btree}) + -> 0.0032s +-- add_index("projects", ["last_repository_check_failed"], {:name=>"index_projects_on_last_repository_check_failed", :using=>:btree}) + -> 0.0030s +-- add_index("projects", ["last_repository_updated_at"], {:name=>"index_projects_on_last_repository_updated_at", :using=>:btree}) + -> 0.0031s +-- add_index("projects", ["name"], {:name=>"index_projects_on_name_trigram", :using=>:gin, :opclasses=>{"name"=>"gin_trgm_ops"}}) + -> 0.0022s +-- add_index("projects", ["namespace_id"], {:name=>"index_projects_on_namespace_id", :using=>:btree}) + -> 0.0028s +-- add_index("projects", ["path"], {:name=>"index_projects_on_path", :using=>:btree}) + -> 0.0028s +-- add_index("projects", ["path"], {:name=>"index_projects_on_path_trigram", :using=>:gin, :opclasses=>{"path"=>"gin_trgm_ops"}}) + -> 0.0023s +-- add_index("projects", ["pending_delete"], {:name=>"index_projects_on_pending_delete", :using=>:btree}) + -> 0.0029s +-- add_index("projects", ["repository_storage"], {:name=>"index_projects_on_repository_storage", :using=>:btree}) + -> 0.0026s +-- add_index("projects", ["runners_token"], {:name=>"index_projects_on_runners_token", :using=>:btree}) + -> 0.0034s +-- add_index("projects", ["star_count"], {:name=>"index_projects_on_star_count", :using=>:btree}) + -> 0.0028s +-- add_index("projects", ["visibility_level"], {:name=>"index_projects_on_visibility_level", :using=>:btree}) + -> 0.0027s +-- create_table("protected_branch_merge_access_levels", {:force=>:cascade}) + -> 0.0042s +-- add_index("protected_branch_merge_access_levels", ["protected_branch_id"], {:name=>"index_protected_branch_merge_access", :using=>:btree}) + -> 0.0029s +-- create_table("protected_branch_push_access_levels", {:force=>:cascade}) + -> 0.0037s +-- add_index("protected_branch_push_access_levels", ["protected_branch_id"], {:name=>"index_protected_branch_push_access", :using=>:btree}) + -> 0.0030s +-- create_table("protected_branches", {:force=>:cascade}) + -> 0.0048s +-- add_index("protected_branches", ["project_id"], {:name=>"index_protected_branches_on_project_id", :using=>:btree}) + -> 0.0030s +-- create_table("protected_tag_create_access_levels", {:force=>:cascade}) + -> 0.0037s +-- add_index("protected_tag_create_access_levels", ["protected_tag_id"], {:name=>"index_protected_tag_create_access", :using=>:btree}) + -> 0.0029s +-- add_index("protected_tag_create_access_levels", ["user_id"], {:name=>"index_protected_tag_create_access_levels_on_user_id", :using=>:btree}) + -> 0.0029s +-- create_table("protected_tags", {:force=>:cascade}) + -> 0.0051s +-- add_index("protected_tags", ["project_id"], {:name=>"index_protected_tags_on_project_id", :using=>:btree}) + -> 0.0034s +-- create_table("push_event_payloads", {:id=>false, :force=>:cascade}) + -> 0.0030s +-- add_index("push_event_payloads", ["event_id"], {:name=>"index_push_event_payloads_on_event_id", :unique=>true, :using=>:btree}) + -> 0.0029s +-- create_table("redirect_routes", {:force=>:cascade}) + -> 0.0049s +-- add_index("redirect_routes", ["path"], {:name=>"index_redirect_routes_on_path", :unique=>true, :using=>:btree}) + -> 0.0031s +-- add_index("redirect_routes", ["source_type", "source_id"], {:name=>"index_redirect_routes_on_source_type_and_source_id", :using=>:btree}) + -> 0.0034s +-- create_table("releases", {:force=>:cascade}) + -> 0.0043s +-- add_index("releases", ["project_id", "tag"], {:name=>"index_releases_on_project_id_and_tag", :using=>:btree}) + -> 0.0032s +-- add_index("releases", ["project_id"], {:name=>"index_releases_on_project_id", :using=>:btree}) + -> 0.0030s +-- create_table("routes", {:force=>:cascade}) + -> 0.0055s +-- add_index("routes", ["path"], {:name=>"index_routes_on_path", :unique=>true, :using=>:btree}) + -> 0.0028s +-- add_index("routes", ["path"], {:name=>"index_routes_on_path_text_pattern_ops", :using=>:btree, :opclasses=>{"path"=>"varchar_pattern_ops"}}) + -> 0.0026s +-- add_index("routes", ["source_type", "source_id"], {:name=>"index_routes_on_source_type_and_source_id", :unique=>true, :using=>:btree}) + -> 0.0029s +-- create_table("sent_notifications", {:force=>:cascade}) + -> 0.0048s +-- add_index("sent_notifications", ["reply_key"], {:name=>"index_sent_notifications_on_reply_key", :unique=>true, :using=>:btree}) + -> 0.0029s +-- create_table("services", {:force=>:cascade}) + -> 0.0091s +-- add_index("services", ["project_id"], {:name=>"index_services_on_project_id", :using=>:btree}) + -> 0.0028s +-- add_index("services", ["template"], {:name=>"index_services_on_template", :using=>:btree}) + -> 0.0031s +-- create_table("snippets", {:force=>:cascade}) + -> 0.0050s +-- add_index("snippets", ["author_id"], {:name=>"index_snippets_on_author_id", :using=>:btree}) + -> 0.0030s +-- add_index("snippets", ["file_name"], {:name=>"index_snippets_on_file_name_trigram", :using=>:gin, :opclasses=>{"file_name"=>"gin_trgm_ops"}}) + -> 0.0020s +-- add_index("snippets", ["project_id"], {:name=>"index_snippets_on_project_id", :using=>:btree}) + -> 0.0028s +-- add_index("snippets", ["title"], {:name=>"index_snippets_on_title_trigram", :using=>:gin, :opclasses=>{"title"=>"gin_trgm_ops"}}) + -> 0.0020s +-- add_index("snippets", ["updated_at"], {:name=>"index_snippets_on_updated_at", :using=>:btree}) + -> 0.0026s +-- add_index("snippets", ["visibility_level"], {:name=>"index_snippets_on_visibility_level", :using=>:btree}) + -> 0.0026s +-- create_table("spam_logs", {:force=>:cascade}) + -> 0.0048s +-- create_table("subscriptions", {:force=>:cascade}) + -> 0.0041s +-- add_index("subscriptions", ["subscribable_id", "subscribable_type", "user_id", "project_id"], {:name=>"index_subscriptions_on_subscribable_and_user_id_and_project_id", :unique=>true, :using=>:btree}) + -> 0.0030s +-- create_table("system_note_metadata", {:force=>:cascade}) + -> 0.0040s +-- add_index("system_note_metadata", ["note_id"], {:name=>"index_system_note_metadata_on_note_id", :unique=>true, :using=>:btree}) + -> 0.0029s +-- create_table("taggings", {:force=>:cascade}) + -> 0.0047s +-- add_index("taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], {:name=>"taggings_idx", :unique=>true, :using=>:btree}) + -> 0.0030s +-- add_index("taggings", ["taggable_id", "taggable_type", "context"], {:name=>"index_taggings_on_taggable_id_and_taggable_type_and_context", :using=>:btree}) + -> 0.0025s +-- create_table("tags", {:force=>:cascade}) + -> 0.0044s +-- add_index("tags", ["name"], {:name=>"index_tags_on_name", :unique=>true, :using=>:btree}) + -> 0.0026s +-- create_table("timelogs", {:force=>:cascade}) + -> 0.0033s +-- add_index("timelogs", ["issue_id"], {:name=>"index_timelogs_on_issue_id", :using=>:btree}) + -> 0.0027s +-- add_index("timelogs", ["merge_request_id"], {:name=>"index_timelogs_on_merge_request_id", :using=>:btree}) + -> 0.0033s +-- add_index("timelogs", ["user_id"], {:name=>"index_timelogs_on_user_id", :using=>:btree}) + -> 0.0028s +-- create_table("todos", {:force=>:cascade}) + -> 0.0043s +-- add_index("todos", ["author_id"], {:name=>"index_todos_on_author_id", :using=>:btree}) + -> 0.0027s +-- add_index("todos", ["commit_id"], {:name=>"index_todos_on_commit_id", :using=>:btree}) + -> 0.0028s +-- add_index("todos", ["note_id"], {:name=>"index_todos_on_note_id", :using=>:btree}) + -> 0.0028s +-- add_index("todos", ["project_id"], {:name=>"index_todos_on_project_id", :using=>:btree}) + -> 0.0027s +-- add_index("todos", ["target_type", "target_id"], {:name=>"index_todos_on_target_type_and_target_id", :using=>:btree}) + -> 0.0028s +-- add_index("todos", ["user_id"], {:name=>"index_todos_on_user_id", :using=>:btree}) + -> 0.0026s +-- create_table("trending_projects", {:force=>:cascade}) + -> 0.0030s +-- add_index("trending_projects", ["project_id"], {:name=>"index_trending_projects_on_project_id", :using=>:btree}) + -> 0.0027s +-- create_table("u2f_registrations", {:force=>:cascade}) + -> 0.0048s +-- add_index("u2f_registrations", ["key_handle"], {:name=>"index_u2f_registrations_on_key_handle", :using=>:btree}) + -> 0.0029s +-- add_index("u2f_registrations", ["user_id"], {:name=>"index_u2f_registrations_on_user_id", :using=>:btree}) + -> 0.0028s +-- create_table("uploads", {:force=>:cascade}) + -> 0.0044s +-- add_index("uploads", ["checksum"], {:name=>"index_uploads_on_checksum", :using=>:btree}) + -> 0.0028s +-- add_index("uploads", ["model_id", "model_type"], {:name=>"index_uploads_on_model_id_and_model_type", :using=>:btree}) + -> 0.0027s +-- add_index("uploads", ["path"], {:name=>"index_uploads_on_path", :using=>:btree}) + -> 0.0028s +-- create_table("user_agent_details", {:force=>:cascade}) + -> 0.0051s +-- add_index("user_agent_details", ["subject_id", "subject_type"], {:name=>"index_user_agent_details_on_subject_id_and_subject_type", :using=>:btree}) + -> 0.0028s +-- create_table("user_custom_attributes", {:force=>:cascade}) + -> 0.0044s +-- add_index("user_custom_attributes", ["key", "value"], {:name=>"index_user_custom_attributes_on_key_and_value", :using=>:btree}) + -> 0.0027s +-- add_index("user_custom_attributes", ["user_id", "key"], {:name=>"index_user_custom_attributes_on_user_id_and_key", :unique=>true, :using=>:btree}) + -> 0.0026s +-- create_table("user_synced_attributes_metadata", {:force=>:cascade}) + -> 0.0056s +-- add_index("user_synced_attributes_metadata", ["user_id"], {:name=>"index_user_synced_attributes_metadata_on_user_id", :unique=>true, :using=>:btree}) + -> 0.0027s +-- create_table("users", {:force=>:cascade}) + -> 0.0134s +-- add_index("users", ["admin"], {:name=>"index_users_on_admin", :using=>:btree}) + -> 0.0030s +-- add_index("users", ["confirmation_token"], {:name=>"index_users_on_confirmation_token", :unique=>true, :using=>:btree}) + -> 0.0029s +-- add_index("users", ["created_at"], {:name=>"index_users_on_created_at", :using=>:btree}) + -> 0.0034s +-- add_index("users", ["email"], {:name=>"index_users_on_email", :unique=>true, :using=>:btree}) + -> 0.0030s +-- add_index("users", ["email"], {:name=>"index_users_on_email_trigram", :using=>:gin, :opclasses=>{"email"=>"gin_trgm_ops"}}) + -> 0.0431s +-- add_index("users", ["ghost"], {:name=>"index_users_on_ghost", :using=>:btree}) + -> 0.0051s +-- add_index("users", ["incoming_email_token"], {:name=>"index_users_on_incoming_email_token", :using=>:btree}) + -> 0.0044s +-- add_index("users", ["name"], {:name=>"index_users_on_name", :using=>:btree}) + -> 0.0044s +-- add_index("users", ["name"], {:name=>"index_users_on_name_trigram", :using=>:gin, :opclasses=>{"name"=>"gin_trgm_ops"}}) + -> 0.0034s +-- add_index("users", ["reset_password_token"], {:name=>"index_users_on_reset_password_token", :unique=>true, :using=>:btree}) + -> 0.0044s +-- add_index("users", ["rss_token"], {:name=>"index_users_on_rss_token", :using=>:btree}) + -> 0.0046s +-- add_index("users", ["state"], {:name=>"index_users_on_state", :using=>:btree}) + -> 0.0040s +-- add_index("users", ["username"], {:name=>"index_users_on_username", :using=>:btree}) + -> 0.0046s +-- add_index("users", ["username"], {:name=>"index_users_on_username_trigram", :using=>:gin, :opclasses=>{"username"=>"gin_trgm_ops"}}) + -> 0.0044s +-- create_table("users_star_projects", {:force=>:cascade}) + -> 0.0055s +-- add_index("users_star_projects", ["project_id"], {:name=>"index_users_star_projects_on_project_id", :using=>:btree}) + -> 0.0037s +-- add_index("users_star_projects", ["user_id", "project_id"], {:name=>"index_users_star_projects_on_user_id_and_project_id", :unique=>true, :using=>:btree}) + -> 0.0044s +-- create_table("web_hook_logs", {:force=>:cascade}) + -> 0.0060s +-- add_index("web_hook_logs", ["web_hook_id"], {:name=>"index_web_hook_logs_on_web_hook_id", :using=>:btree}) + -> 0.0034s +-- create_table("web_hooks", {:force=>:cascade}) + -> 0.0120s +-- add_index("web_hooks", ["project_id"], {:name=>"index_web_hooks_on_project_id", :using=>:btree}) + -> 0.0038s +-- add_index("web_hooks", ["type"], {:name=>"index_web_hooks_on_type", :using=>:btree}) + -> 0.0036s +-- add_foreign_key("boards", "projects", {:name=>"fk_f15266b5f9", :on_delete=>:cascade}) + -> 0.0030s +-- add_foreign_key("chat_teams", "namespaces", {:on_delete=>:cascade}) + -> 0.0021s +-- add_foreign_key("ci_build_trace_section_names", "projects", {:on_delete=>:cascade}) + -> 0.0022s +-- add_foreign_key("ci_build_trace_sections", "ci_build_trace_section_names", {:column=>"section_name_id", :name=>"fk_264e112c66", :on_delete=>:cascade}) + -> 0.0018s +-- add_foreign_key("ci_build_trace_sections", "ci_builds", {:column=>"build_id", :name=>"fk_4ebe41f502", :on_delete=>:cascade}) + -> 0.0024s +-- add_foreign_key("ci_build_trace_sections", "projects", {:on_delete=>:cascade}) + -> 0.0019s +-- add_foreign_key("ci_builds", "ci_pipelines", {:column=>"auto_canceled_by_id", :name=>"fk_a2141b1522", :on_delete=>:nullify}) + -> 0.0023s +-- add_foreign_key("ci_builds", "ci_stages", {:column=>"stage_id", :name=>"fk_3a9eaa254d", :on_delete=>:cascade}) + -> 0.0020s +-- add_foreign_key("ci_builds", "projects", {:name=>"fk_befce0568a", :on_delete=>:cascade}) + -> 0.0024s +-- add_foreign_key("ci_group_variables", "namespaces", {:column=>"group_id", :name=>"fk_33ae4d58d8", :on_delete=>:cascade}) + -> 0.0024s +-- add_foreign_key("ci_job_artifacts", "ci_builds", {:column=>"job_id", :on_delete=>:cascade}) + -> 0.0019s +-- add_foreign_key("ci_job_artifacts", "projects", {:on_delete=>:cascade}) + -> 0.0020s +-- add_foreign_key("ci_pipeline_schedule_variables", "ci_pipeline_schedules", {:column=>"pipeline_schedule_id", :name=>"fk_41c35fda51", :on_delete=>:cascade}) + -> 0.0027s +-- add_foreign_key("ci_pipeline_schedules", "projects", {:name=>"fk_8ead60fcc4", :on_delete=>:cascade}) + -> 0.0022s +-- add_foreign_key("ci_pipeline_schedules", "users", {:column=>"owner_id", :name=>"fk_9ea99f58d2", :on_delete=>:nullify}) + -> 0.0025s +-- add_foreign_key("ci_pipeline_variables", "ci_pipelines", {:column=>"pipeline_id", :name=>"fk_f29c5f4380", :on_delete=>:cascade}) + -> 0.0018s +-- add_foreign_key("ci_pipelines", "ci_pipeline_schedules", {:column=>"pipeline_schedule_id", :name=>"fk_3d34ab2e06", :on_delete=>:nullify}) + -> 0.0019s +-- add_foreign_key("ci_pipelines", "ci_pipelines", {:column=>"auto_canceled_by_id", :name=>"fk_262d4c2d19", :on_delete=>:nullify}) + -> 0.0029s +-- add_foreign_key("ci_pipelines", "projects", {:name=>"fk_86635dbd80", :on_delete=>:cascade}) + -> 0.0023s +-- add_foreign_key("ci_runner_projects", "projects", {:name=>"fk_4478a6f1e4", :on_delete=>:cascade}) + -> 0.0036s +-- add_foreign_key("ci_stages", "ci_pipelines", {:column=>"pipeline_id", :name=>"fk_fb57e6cc56", :on_delete=>:cascade}) + -> 0.0017s +-- add_foreign_key("ci_stages", "projects", {:name=>"fk_2360681d1d", :on_delete=>:cascade}) + -> 0.0020s +-- add_foreign_key("ci_trigger_requests", "ci_triggers", {:column=>"trigger_id", :name=>"fk_b8ec8b7245", :on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("ci_triggers", "projects", {:name=>"fk_e3e63f966e", :on_delete=>:cascade}) + -> 0.0021s +-- add_foreign_key("ci_triggers", "users", {:column=>"owner_id", :name=>"fk_e8e10d1964", :on_delete=>:cascade}) + -> 0.0019s +-- add_foreign_key("ci_variables", "projects", {:name=>"fk_ada5eb64b3", :on_delete=>:cascade}) + -> 0.0021s +-- add_foreign_key("cluster_platforms_kubernetes", "clusters", {:on_delete=>:cascade}) + -> 0.0019s +-- add_foreign_key("cluster_projects", "clusters", {:on_delete=>:cascade}) + -> 0.0018s +-- add_foreign_key("cluster_projects", "projects", {:on_delete=>:cascade}) + -> 0.0020s +-- add_foreign_key("cluster_providers_gcp", "clusters", {:on_delete=>:cascade}) + -> 0.0017s +-- add_foreign_key("clusters", "users", {:on_delete=>:nullify}) + -> 0.0018s +-- add_foreign_key("clusters_applications_helm", "clusters", {:on_delete=>:cascade}) + -> 0.0019s +-- add_foreign_key("container_repositories", "projects") + -> 0.0020s +-- add_foreign_key("deploy_keys_projects", "projects", {:name=>"fk_58a901ca7e", :on_delete=>:cascade}) + -> 0.0019s +-- add_foreign_key("deployments", "projects", {:name=>"fk_b9a3851b82", :on_delete=>:cascade}) + -> 0.0021s +-- add_foreign_key("environments", "projects", {:name=>"fk_d1c8c1da6a", :on_delete=>:cascade}) + -> 0.0019s +-- add_foreign_key("events", "projects", {:on_delete=>:cascade}) + -> 0.0020s +-- add_foreign_key("events", "users", {:column=>"author_id", :name=>"fk_edfd187b6f", :on_delete=>:cascade}) + -> 0.0020s +-- add_foreign_key("fork_network_members", "fork_networks", {:on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("fork_network_members", "projects", {:column=>"forked_from_project_id", :name=>"fk_b01280dae4", :on_delete=>:nullify}) + -> 0.0019s +-- add_foreign_key("fork_network_members", "projects", {:on_delete=>:cascade}) + -> 0.0018s +-- add_foreign_key("fork_networks", "projects", {:column=>"root_project_id", :name=>"fk_e7b436b2b5", :on_delete=>:nullify}) + -> 0.0018s +-- add_foreign_key("forked_project_links", "projects", {:column=>"forked_to_project_id", :name=>"fk_434510edb0", :on_delete=>:cascade}) + -> 0.0018s +-- add_foreign_key("gcp_clusters", "projects", {:on_delete=>:cascade}) + -> 0.0029s +-- add_foreign_key("gcp_clusters", "services", {:on_delete=>:nullify}) + -> 0.0022s +-- add_foreign_key("gcp_clusters", "users", {:on_delete=>:nullify}) + -> 0.0019s +-- add_foreign_key("gpg_key_subkeys", "gpg_keys", {:on_delete=>:cascade}) + -> 0.0017s +-- add_foreign_key("gpg_keys", "users", {:on_delete=>:cascade}) + -> 0.0019s +-- add_foreign_key("gpg_signatures", "gpg_key_subkeys", {:on_delete=>:nullify}) + -> 0.0016s +-- add_foreign_key("gpg_signatures", "gpg_keys", {:on_delete=>:nullify}) + -> 0.0016s +-- add_foreign_key("gpg_signatures", "projects", {:on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("group_custom_attributes", "namespaces", {:column=>"group_id", :on_delete=>:cascade}) + -> 0.0014s +-- add_foreign_key("issue_assignees", "issues", {:name=>"fk_b7d881734a", :on_delete=>:cascade}) + -> 0.0019s +-- add_foreign_key("issue_assignees", "users", {:name=>"fk_5e0c8d9154", :on_delete=>:cascade}) + -> 0.0015s +-- add_foreign_key("issue_metrics", "issues", {:on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("issues", "issues", {:column=>"moved_to_id", :name=>"fk_a194299be1", :on_delete=>:nullify}) + -> 0.0014s +-- add_foreign_key("issues", "milestones", {:name=>"fk_96b1dd429c", :on_delete=>:nullify}) + -> 0.0016s +-- add_foreign_key("issues", "projects", {:name=>"fk_899c8f3231", :on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("issues", "users", {:column=>"author_id", :name=>"fk_05f1e72feb", :on_delete=>:nullify}) + -> 0.0015s +-- add_foreign_key("issues", "users", {:column=>"updated_by_id", :name=>"fk_ffed080f01", :on_delete=>:nullify}) + -> 0.0017s +-- add_foreign_key("label_priorities", "labels", {:on_delete=>:cascade}) + -> 0.0015s +-- add_foreign_key("label_priorities", "projects", {:on_delete=>:cascade}) + -> 0.0015s +-- add_foreign_key("labels", "namespaces", {:column=>"group_id", :on_delete=>:cascade}) + -> 0.0015s +-- add_foreign_key("labels", "projects", {:name=>"fk_7de4989a69", :on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("lists", "boards", {:name=>"fk_0d3f677137", :on_delete=>:cascade}) + -> 0.0015s +-- add_foreign_key("lists", "labels", {:name=>"fk_7a5553d60f", :on_delete=>:cascade}) + -> 0.0014s +-- add_foreign_key("members", "users", {:name=>"fk_2e88fb7ce9", :on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("merge_request_diff_commits", "merge_request_diffs", {:on_delete=>:cascade}) + -> 0.0014s +-- add_foreign_key("merge_request_diff_files", "merge_request_diffs", {:on_delete=>:cascade}) + -> 0.0014s +-- add_foreign_key("merge_request_diffs", "merge_requests", {:name=>"fk_8483f3258f", :on_delete=>:cascade}) + -> 0.0019s +-- add_foreign_key("merge_request_metrics", "ci_pipelines", {:column=>"pipeline_id", :on_delete=>:cascade}) + -> 0.0017s +-- add_foreign_key("merge_request_metrics", "merge_requests", {:on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("merge_request_metrics", "users", {:column=>"latest_closed_by_id", :name=>"fk_ae440388cc", :on_delete=>:nullify}) + -> 0.0015s +-- add_foreign_key("merge_request_metrics", "users", {:column=>"merged_by_id", :name=>"fk_7f28d925f3", :on_delete=>:nullify}) + -> 0.0015s +-- add_foreign_key("merge_requests", "ci_pipelines", {:column=>"head_pipeline_id", :name=>"fk_fd82eae0b9", :on_delete=>:nullify}) + -> 0.0014s +-- add_foreign_key("merge_requests", "merge_request_diffs", {:column=>"latest_merge_request_diff_id", :name=>"fk_06067f5644", :on_delete=>:nullify}) + -> 0.0014s +-- add_foreign_key("merge_requests", "milestones", {:name=>"fk_6a5165a692", :on_delete=>:nullify}) + -> 0.0015s +-- add_foreign_key("merge_requests", "projects", {:column=>"source_project_id", :name=>"fk_3308fe130c", :on_delete=>:nullify}) + -> 0.0017s +-- add_foreign_key("merge_requests", "projects", {:column=>"target_project_id", :name=>"fk_a6963e8447", :on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("merge_requests", "users", {:column=>"assignee_id", :name=>"fk_6149611a04", :on_delete=>:nullify}) + -> 0.0016s +-- add_foreign_key("merge_requests", "users", {:column=>"author_id", :name=>"fk_e719a85f8a", :on_delete=>:nullify}) + -> 0.0017s +-- add_foreign_key("merge_requests", "users", {:column=>"merge_user_id", :name=>"fk_ad525e1f87", :on_delete=>:nullify}) + -> 0.0018s +-- add_foreign_key("merge_requests", "users", {:column=>"updated_by_id", :name=>"fk_641731faff", :on_delete=>:nullify}) + -> 0.0017s +-- add_foreign_key("merge_requests_closing_issues", "issues", {:on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("merge_requests_closing_issues", "merge_requests", {:on_delete=>:cascade}) + -> 0.0014s +-- add_foreign_key("milestones", "namespaces", {:column=>"group_id", :name=>"fk_95650a40d4", :on_delete=>:cascade}) + -> 0.0014s +-- add_foreign_key("milestones", "projects", {:name=>"fk_9bd0a0c791", :on_delete=>:cascade}) + -> 0.0017s +-- add_foreign_key("notes", "projects", {:name=>"fk_99e097b079", :on_delete=>:cascade}) + -> 0.0019s +-- add_foreign_key("oauth_openid_requests", "oauth_access_grants", {:column=>"access_grant_id", :name=>"fk_oauth_openid_requests_oauth_access_grants_access_grant_id"}) + -> 0.0014s +-- add_foreign_key("pages_domains", "projects", {:name=>"fk_ea2f6dfc6f", :on_delete=>:cascade}) + -> 0.0021s +-- add_foreign_key("personal_access_tokens", "users") + -> 0.0016s +-- add_foreign_key("project_authorizations", "projects", {:on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("project_authorizations", "users", {:on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("project_auto_devops", "projects", {:on_delete=>:cascade}) + -> 0.0026s +-- add_foreign_key("project_custom_attributes", "projects", {:on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("project_features", "projects", {:name=>"fk_18513d9b92", :on_delete=>:cascade}) + -> 0.0020s +-- add_foreign_key("project_group_links", "projects", {:name=>"fk_daa8cee94c", :on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("project_import_data", "projects", {:name=>"fk_ffb9ee3a10", :on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("project_statistics", "projects", {:on_delete=>:cascade}) + -> 0.0021s +-- add_foreign_key("protected_branch_merge_access_levels", "protected_branches", {:name=>"fk_8a3072ccb3", :on_delete=>:cascade}) + -> 0.0014s +-- add_foreign_key("protected_branch_push_access_levels", "protected_branches", {:name=>"fk_9ffc86a3d9", :on_delete=>:cascade}) + -> 0.0014s +-- add_foreign_key("protected_branches", "projects", {:name=>"fk_7a9c6d93e7", :on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("protected_tag_create_access_levels", "namespaces", {:column=>"group_id"}) + -> 0.0016s +-- add_foreign_key("protected_tag_create_access_levels", "protected_tags", {:name=>"fk_f7dfda8c51", :on_delete=>:cascade}) + -> 0.0013s +-- add_foreign_key("protected_tag_create_access_levels", "users") + -> 0.0018s +-- add_foreign_key("protected_tags", "projects", {:name=>"fk_8e4af87648", :on_delete=>:cascade}) + -> 0.0015s +-- add_foreign_key("push_event_payloads", "events", {:name=>"fk_36c74129da", :on_delete=>:cascade}) + -> 0.0013s +-- add_foreign_key("releases", "projects", {:name=>"fk_47fe2a0596", :on_delete=>:cascade}) + -> 0.0015s +-- add_foreign_key("services", "projects", {:name=>"fk_71cce407f9", :on_delete=>:cascade}) + -> 0.0015s +-- add_foreign_key("snippets", "projects", {:name=>"fk_be41fd4bb7", :on_delete=>:cascade}) + -> 0.0017s +-- add_foreign_key("subscriptions", "projects", {:on_delete=>:cascade}) + -> 0.0018s +-- add_foreign_key("system_note_metadata", "notes", {:name=>"fk_d83a918cb1", :on_delete=>:cascade}) + -> 0.0015s +-- add_foreign_key("timelogs", "issues", {:name=>"fk_timelogs_issues_issue_id", :on_delete=>:cascade}) + -> 0.0015s +-- add_foreign_key("timelogs", "merge_requests", {:name=>"fk_timelogs_merge_requests_merge_request_id", :on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("todos", "projects", {:name=>"fk_45054f9c45", :on_delete=>:cascade}) + -> 0.0018s +-- add_foreign_key("trending_projects", "projects", {:on_delete=>:cascade}) + -> 0.0015s +-- add_foreign_key("u2f_registrations", "users") + -> 0.0017s +-- add_foreign_key("user_custom_attributes", "users", {:on_delete=>:cascade}) + -> 0.0019s +-- add_foreign_key("user_synced_attributes_metadata", "users", {:on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("users_star_projects", "projects", {:name=>"fk_22cd27ddfc", :on_delete=>:cascade}) + -> 0.0016s +-- add_foreign_key("web_hook_logs", "web_hooks", {:on_delete=>:cascade}) + -> 0.0014s +-- add_foreign_key("web_hooks", "projects", {:name=>"fk_0c8ca6d9d1", :on_delete=>:cascade}) + -> 0.0017s +-- initialize_schema_migrations_table() + -> 0.0112s +$ JOB_NAME=( $CI_JOB_NAME ) +$ export CI_NODE_INDEX=${JOB_NAME[-2]} +$ export CI_NODE_TOTAL=${JOB_NAME[-1]} +$ export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json +$ export KNAPSACK_GENERATE_REPORT=true +$ export CACHE_CLASSES=true +$ cp ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH} +$ scripts/gitaly-test-spawn +Gem.path: ["/root/.gem/ruby/2.3.0", "/usr/local/lib/ruby/gems/2.3.0", "/usr/local/bundle"] +ENV['BUNDLE_GEMFILE']: nil +ENV['RUBYOPT']: nil +bundle config in /builds/gitlab-org/gitlab-ce +scripts/gitaly-test-spawn:10:in `
': undefined local variable or method `gitaly_dir' for main:Object (NameError) +Did you mean? gitaly_dir +Settings are listed in order of priority. The top value will be used. +retry +Set for your local app (/usr/local/bundle/config): 3 + +path +Set for your local app (/usr/local/bundle/config): "vendor" +Set via BUNDLE_PATH: "/usr/local/bundle" + +jobs +Set for your local app (/usr/local/bundle/config): "2" + +clean +Set for your local app (/usr/local/bundle/config): "true" + +without +Set for your local app (/usr/local/bundle/config): [:production] + +silence_root_warning +Set via BUNDLE_SILENCE_ROOT_WARNING: true + +app_config +Set via BUNDLE_APP_CONFIG: "/usr/local/bundle" + +install_flags +Set via BUNDLE_INSTALL_FLAGS: "--without=production --jobs=2 --path=vendor --retry=3 --quiet" + +bin +Set via BUNDLE_BIN: "/usr/local/bundle/bin" + +gemfile +Set via BUNDLE_GEMFILE: "/builds/gitlab-org/gitlab-ce/Gemfile" + +section_end:1517486961:build_script +section_start:1517486961:after_script +section_end:1517486962:after_script +section_start:1517486962:upload_artifacts +Uploading artifacts... +WARNING: coverage/: no matching files  +knapsack/: found 5 matching files  +WARNING: tmp/capybara/: no matching files  +Uploading artifacts to coordinator... ok  id=50551722 responseStatus=201 Created token=XkN753rp +section_end:1517486963:upload_artifacts +ERROR: Job failed: exit code 1 + \ No newline at end of file diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js index 7b38f6b785550186e1406a2f7da3bc161c1dabe9..a9e244e523d44d345edc36fc01ad9fb024b6fb9b 100644 --- a/spec/javascripts/clusters/clusters_bundle_spec.js +++ b/spec/javascripts/clusters/clusters_bundle_spec.js @@ -71,7 +71,8 @@ describe('Clusters', () => { helm: { status: APPLICATION_INSTALLABLE, title: 'Helm Tiller' }, }); - expect(document.querySelector('.js-cluster-application-notice .flash-text')).toBeNull(); + const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text'); + expect(flashMessage).toBeNull(); }); it('shows an alert when something gets newly installed', () => { @@ -83,8 +84,9 @@ describe('Clusters', () => { helm: { status: APPLICATION_INSTALLED, title: 'Helm Tiller' }, }); - expect(document.querySelector('.js-cluster-application-notice .flash-text')).toBeDefined(); - expect(document.querySelector('.js-cluster-application-notice .flash-text').textContent.trim()).toEqual('Helm Tiller was successfully installed on your cluster'); + const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text'); + expect(flashMessage).not.toBeNull(); + expect(flashMessage.textContent.trim()).toEqual('Helm Tiller was successfully installed on your Kubernetes cluster'); }); it('shows an alert when multiple things gets newly installed', () => { @@ -98,8 +100,9 @@ describe('Clusters', () => { ingress: { status: APPLICATION_INSTALLED, title: 'Ingress' }, }); - expect(document.querySelector('.js-cluster-application-notice .flash-text')).toBeDefined(); - expect(document.querySelector('.js-cluster-application-notice .flash-text').textContent.trim()).toEqual('Helm Tiller, Ingress was successfully installed on your cluster'); + const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text'); + expect(flashMessage).not.toBeNull(); + expect(flashMessage.textContent.trim()).toEqual('Helm Tiller, Ingress was successfully installed on your Kubernetes cluster'); }); }); diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js index ec2889355e61fa3638cb5c65dcb1ab6c59f724dd..726a4ed30de7c157b33df8ef5fffe77ed031fd79 100644 --- a/spec/javascripts/clusters/stores/clusters_store_spec.js +++ b/spec/javascripts/clusters/stores/clusters_store_spec.js @@ -58,6 +58,7 @@ describe('Clusters Store', () => { expect(store.state).toEqual({ helpPath: null, + ingressHelpPath: null, status: mockResponseData.status, statusReason: mockResponseData.status_reason, applications: { diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/javascripts/fixtures/jobs.rb index 87d131dfe28b255d9cc4d01dc4006b8bdcbef05b..6d5c6d5334f2ec0bf4b9c5511dbe4b97322a4a9c 100644 --- a/spec/javascripts/fixtures/jobs.rb +++ b/spec/javascripts/fixtures/jobs.rb @@ -7,7 +7,7 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} let(:project) { create(:project_empty_repo, namespace: namespace, path: 'builds-project') } let(:pipeline) { create(:ci_empty_pipeline, project: project) } - let!(:build_with_artifacts) { create(:ci_build, :success, :artifacts, :trace, pipeline: pipeline, stage: 'test', artifacts_expire_at: Time.now + 18.months) } + let!(:build_with_artifacts) { create(:ci_build, :success, :artifacts, :trace_artifact, pipeline: pipeline, stage: 'test', artifacts_expire_at: Time.now + 18.months) } let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline, stage: 'build') } let!(:pending_build) { create(:ci_build, :pending, pipeline: pipeline, stage: 'deploy') } diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb index 4cdb679c97f56bf0b5e247424c96e741dd2eef05..2b69e718e08b452f8769a266e3519d50f7f97786 100644 --- a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData, end after do - [MergeRequest, MergeRequestDiff].each(&:reset_column_information) + [Project, MergeRequest, MergeRequestDiff].each(&:reset_column_information) end describe '#perform' do diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb index 633e319f46dee5fe33f3739ae8ea24a74683bd65..a65012d2314d4ae1bb336571a373034675325cc1 100644 --- a/spec/lib/gitlab/checks/force_push_spec.rb +++ b/spec/lib/gitlab/checks/force_push_spec.rb @@ -2,18 +2,20 @@ require 'spec_helper' describe Gitlab::Checks::ForcePush do let(:project) { create(:project, :repository) } + let(:repository) { project.repository.raw } context "exit code checking", :skip_gitaly_mock do it "does not raise a runtime error if the `popen` call to git returns a zero exit code" do - allow_any_instance_of(Gitlab::Git::RevList).to receive(:popen).and_return(['normal output', 0]) + allow(repository).to receive(:popen).and_return(['normal output', 0]) expect { described_class.force_push?(project, 'oldrev', 'newrev') }.not_to raise_error end - it "raises a runtime error if the `popen` call to git returns a non-zero exit code" do - allow_any_instance_of(Gitlab::Git::RevList).to receive(:popen).and_return(['error', 1]) + it "raises a GitError error if the `popen` call to git returns a non-zero exit code" do + allow(repository).to receive(:popen).and_return(['error', 1]) - expect { described_class.force_push?(project, 'oldrev', 'newrev') }.to raise_error(RuntimeError) + expect { described_class.force_push?(project, 'oldrev', 'newrev') } + .to raise_error(Gitlab::Git::Repository::GitError) end end end diff --git a/spec/lib/gitlab/checks/project_created_spec.rb b/spec/lib/gitlab/checks/project_created_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..ac02007e1111e9b5d5a7ee44db5208adcd690bed --- /dev/null +++ b/spec/lib/gitlab/checks/project_created_spec.rb @@ -0,0 +1,46 @@ +require 'rails_helper' + +describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do + let(:user) { create(:user) } + let(:project) { create(:project) } + + describe '.fetch_message' do + context 'with a project created message queue' do + let(:project_created) { described_class.new(project, user, 'http') } + + before do + project_created.add_message + end + + it 'returns project created message' do + expect(described_class.fetch_message(user.id, project.id)).to eq(project_created.message) + end + + it 'deletes the project created message from redis' do + expect(Gitlab::Redis::SharedState.with { |redis| redis.get("project_created:#{user.id}:#{project.id}") }).not_to be_nil + described_class.fetch_message(user.id, project.id) + expect(Gitlab::Redis::SharedState.with { |redis| redis.get("project_created:#{user.id}:#{project.id}") }).to be_nil + end + end + + context 'with no project created message queue' do + it 'returns nil' do + expect(described_class.fetch_message(1, 2)).to be_nil + end + end + end + + describe '#add_message' do + it 'queues a project created message' do + project_created = described_class.new(project, user, 'http') + + expect(project_created.add_message).to eq('OK') + end + + it 'handles anonymous push' do + project_created = described_class.new(nil, user, 'http') + + expect(project_created.add_message).to be_nil + end + end +end diff --git a/spec/lib/gitlab/checks/project_moved_spec.rb b/spec/lib/gitlab/checks/project_moved_spec.rb index f90c2d6aded126e3a624fd0a6f7381340ce00e1f..e263d29656c140bc27ed0c10dc4bc8e8921bffd5 100644 --- a/spec/lib/gitlab/checks/project_moved_spec.rb +++ b/spec/lib/gitlab/checks/project_moved_spec.rb @@ -4,82 +4,82 @@ describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do let(:user) { create(:user) } let(:project) { create(:project) } - describe '.fetch_redirct_message' do + describe '.fetch_message' do context 'with a redirect message queue' do - it 'should return the redirect message' do - project_moved = described_class.new(project, user, 'foo/bar', 'http') - project_moved.add_redirect_message + it 'returns the redirect message' do + project_moved = described_class.new(project, user, 'http', 'foo/bar') + project_moved.add_message - expect(described_class.fetch_redirect_message(user.id, project.id)).to eq(project_moved.redirect_message) + expect(described_class.fetch_message(user.id, project.id)).to eq(project_moved.message) end - it 'should delete the redirect message from redis' do - project_moved = described_class.new(project, user, 'foo/bar', 'http') - project_moved.add_redirect_message + it 'deletes the redirect message from redis' do + project_moved = described_class.new(project, user, 'http', 'foo/bar') + project_moved.add_message expect(Gitlab::Redis::SharedState.with { |redis| redis.get("redirect_namespace:#{user.id}:#{project.id}") }).not_to be_nil - described_class.fetch_redirect_message(user.id, project.id) + described_class.fetch_message(user.id, project.id) expect(Gitlab::Redis::SharedState.with { |redis| redis.get("redirect_namespace:#{user.id}:#{project.id}") }).to be_nil end end context 'with no redirect message queue' do - it 'should return nil' do - expect(described_class.fetch_redirect_message(1, 2)).to be_nil + it 'returns nil' do + expect(described_class.fetch_message(1, 2)).to be_nil end end end - describe '#add_redirect_message' do - it 'should queue a redirect message' do - project_moved = described_class.new(project, user, 'foo/bar', 'http') - expect(project_moved.add_redirect_message).to eq("OK") + describe '#add_message' do + it 'queues a redirect message' do + project_moved = described_class.new(project, user, 'http', 'foo/bar') + expect(project_moved.add_message).to eq("OK") end - it 'should handle anonymous clones' do - project_moved = described_class.new(project, nil, 'foo/bar', 'http') + it 'handles anonymous clones' do + project_moved = described_class.new(project, nil, 'http', 'foo/bar') - expect(project_moved.add_redirect_message).to eq(nil) + expect(project_moved.add_message).to eq(nil) end end - describe '#redirect_message' do + describe '#message' do context 'when the push is rejected' do - it 'should return a redirect message telling the user to try again' do - project_moved = described_class.new(project, user, 'foo/bar', 'http') + it 'returns a redirect message telling the user to try again' do + project_moved = described_class.new(project, user, 'http', 'foo/bar') message = "Project 'foo/bar' was moved to '#{project.full_path}'." + "\n\nPlease update your Git remote:" + "\n\n git remote set-url origin #{project.http_url_to_repo} and try again.\n" - expect(project_moved.redirect_message(rejected: true)).to eq(message) + expect(project_moved.message(rejected: true)).to eq(message) end end context 'when the push is not rejected' do - it 'should return a redirect message' do - project_moved = described_class.new(project, user, 'foo/bar', 'http') + it 'returns a redirect message' do + project_moved = described_class.new(project, user, 'http', 'foo/bar') message = "Project 'foo/bar' was moved to '#{project.full_path}'." + "\n\nPlease update your Git remote:" + "\n\n git remote set-url origin #{project.http_url_to_repo}\n" - expect(project_moved.redirect_message).to eq(message) + expect(project_moved.message).to eq(message) end end end describe '#permanent_redirect?' do context 'with a permanent RedirectRoute' do - it 'should return true' do + it 'returns true' do project.route.create_redirect('foo/bar', permanent: true) - project_moved = described_class.new(project, user, 'foo/bar', 'http') + project_moved = described_class.new(project, user, 'http', 'foo/bar') expect(project_moved.permanent_redirect?).to be_truthy end end context 'without a permanent RedirectRoute' do - it 'should return false' do + it 'returns false' do project.route.create_redirect('foo/bar') - project_moved = described_class.new(project, user, 'foo/bar', 'http') + project_moved = described_class.new(project, user, 'http', 'foo/bar') expect(project_moved.permanent_redirect?).to be_falsy end end diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb index 3546532b9b463f1a549f5877e31e8f85f69ad383..91c9625ba0666bc1bd256b483699e9bc072fb2d5 100644 --- a/spec/lib/gitlab/ci/trace_spec.rb +++ b/spec/lib/gitlab/ci/trace_spec.rb @@ -238,11 +238,98 @@ describe Gitlab::Ci::Trace do end end + describe '#read' do + shared_examples 'read successfully with IO' do + it 'yields with source' do + trace.read do |stream| + expect(stream).to be_a(Gitlab::Ci::Trace::Stream) + expect(stream.stream).to be_a(IO) + end + end + end + + shared_examples 'read successfully with StringIO' do + it 'yields with source' do + trace.read do |stream| + expect(stream).to be_a(Gitlab::Ci::Trace::Stream) + expect(stream.stream).to be_a(StringIO) + end + end + end + + shared_examples 'failed to read' do + it 'yields without source' do + trace.read do |stream| + expect(stream).to be_a(Gitlab::Ci::Trace::Stream) + expect(stream.stream).to be_nil + end + end + end + + context 'when trace artifact exists' do + before do + create(:ci_job_artifact, :trace, job: build) + end + + it_behaves_like 'read successfully with IO' + end + + context 'when current_path (with project_id) exists' do + before do + expect(trace).to receive(:default_path) { expand_fixture_path('trace/sample_trace') } + end + + it_behaves_like 'read successfully with IO' + end + + context 'when current_path (with project_ci_id) exists' do + before do + expect(trace).to receive(:deprecated_path) { expand_fixture_path('trace/sample_trace') } + end + + it_behaves_like 'read successfully with IO' + end + + context 'when db trace exists' do + before do + build.send(:write_attribute, :trace, "data") + end + + it_behaves_like 'read successfully with StringIO' + end + + context 'when no sources exist' do + it_behaves_like 'failed to read' + end + end + describe 'trace handling' do + subject { trace.exist? } + context 'trace does not exist' do it { expect(trace.exist?).to be(false) } end + context 'when trace artifact exists' do + before do + create(:ci_job_artifact, :trace, job: build) + end + + it { is_expected.to be_truthy } + + context 'when the trace artifact has been erased' do + before do + trace.erase! + end + + it { is_expected.to be_falsy } + + it 'removes associations' do + expect(Ci::JobArtifact.exists?(job_id: build.id, file_type: :trace)).to be_falsy + end + end + end + context 'new trace path is used' do before do trace.send(:ensure_directory) diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index 8c79ef54c6cd0857e487585d12fe46fb8d94f1aa..28c679af12a27427f3c391d5da689d9d9671a688 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ClosingIssueExtractor do let(:project) { create(:project) } let(:project2) { create(:project) } - let(:forked_project) { Projects::ForkService.new(project, project.creator).execute } + let(:forked_project) { Projects::ForkService.new(project, project2.creator).execute } let(:issue) { create(:issue, project: project) } let(:issue2) { create(:issue, project: project2) } let(:reference) { issue.to_reference } @@ -14,6 +14,7 @@ describe Gitlab::ClosingIssueExtractor do before do project.add_developer(project.creator) + project.add_developer(project2.creator) project2.add_master(project.creator) end diff --git a/spec/lib/gitlab/git/lfs_pointer_file_spec.rb b/spec/lib/gitlab/git/lfs_pointer_file_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..d7f76737f3f6668e5ab7b382f004b8371df59f62 --- /dev/null +++ b/spec/lib/gitlab/git/lfs_pointer_file_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe Gitlab::Git::LfsPointerFile do + let(:data) { "1234\n" } + + subject { described_class.new(data) } + + describe '#size' do + it 'counts the bytes' do + expect(subject.size).to eq 5 + end + + it 'handles non ascii data' do + expect(described_class.new("ääää").size).to eq 8 + end + end + + describe '#sha256' do + it 'hashes the content correctly' do + expect(subject.sha256).to eq 'a883dafc480d466ee04e0d6da986bd78eb1fdd2178d04693723da3a8f95d42f4' + end + end + + describe '#pointer' do + it 'starts with the LFS version' do + expect(subject.pointer).to start_with('version https://git-lfs.github.com/spec/v1') + end + + it 'includes sha256' do + expect(subject.pointer).to match(/^oid sha256:[0-9a-fA-F]{64}/) + end + + it 'ends with the size' do + expect(subject.pointer).to end_with("\nsize 5\n") + end + end +end diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb index 90fbef9d24810312a64f8b22599b90fbe3baa76b..4e0ee206219e50852277f8d26be2123bae486aac 100644 --- a/spec/lib/gitlab/git/rev_list_spec.rb +++ b/spec/lib/gitlab/git/rev_list_spec.rb @@ -1,51 +1,42 @@ require 'spec_helper' describe Gitlab::Git::RevList do - let(:project) { create(:project, :repository) } - let(:rev_list) { described_class.new(newrev: 'newrev', path_to_repo: project.repository.path_to_repo) } + let(:repository) { create(:project, :repository).repository.raw } + let(:rev_list) { described_class.new(repository, newrev: 'newrev') } let(:env_hash) do { 'GIT_OBJECT_DIRECTORY' => 'foo', 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'bar' } end + let(:command_env) { { 'GIT_ALTERNATE_OBJECT_DIRECTORIES' => 'foo:bar' } } before do - allow(Gitlab::Git::Env).to receive(:all).and_return(env_hash.symbolize_keys) + allow(Gitlab::Git::Env).to receive(:all).and_return(env_hash) end def args_for_popen(args_list) - [ - Gitlab.config.git.bin_path, - "--git-dir=#{project.repository.path_to_repo}", - 'rev-list', - *args_list - ] - end - - def stub_popen_rev_list(*additional_args, output:) - args = args_for_popen(additional_args) - - expect(rev_list).to receive(:popen).with(args, nil, env_hash) - .and_return([output, 0]) + [Gitlab.config.git.bin_path, 'rev-list', *args_list] end - def stub_lazy_popen_rev_list(*additional_args, output:) + def stub_popen_rev_list(*additional_args, with_lazy_block: true, output:) params = [ args_for_popen(additional_args), - nil, - env_hash, - hash_including(lazy_block: anything) + repository.path, + command_env, + hash_including(lazy_block: with_lazy_block ? anything : nil) ] - expect(rev_list).to receive(:popen).with(*params) do |*_, lazy_block:| - lazy_block.call(output.lines.lazy.map(&:chomp)) + expect(repository).to receive(:popen).with(*params) do |*_, lazy_block:| + output = lazy_block.call(output.lines.lazy.map(&:chomp)) if with_lazy_block + + [output, 0] end end context "#new_refs" do it 'calls out to `popen`' do - stub_popen_rev_list('newrev', '--not', '--all', output: "sha1\nsha2") + stub_popen_rev_list('newrev', '--not', '--all', with_lazy_block: false, output: "sha1\nsha2") expect(rev_list.new_refs).to eq(%w[sha1 sha2]) end @@ -55,18 +46,18 @@ describe Gitlab::Git::RevList do it 'fetches list of newly pushed objects using rev-list' do stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") - expect(rev_list.new_objects).to eq(%w[sha1 sha2]) + expect { |b| rev_list.new_objects(&b) }.to yield_with_args(%w[sha1 sha2]) end it 'can skip pathless objects' do stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2 path/to/file") - expect(rev_list.new_objects(require_path: true)).to eq(%w[sha2]) + expect { |b| rev_list.new_objects(require_path: true, &b) }.to yield_with_args(%w[sha2]) end it 'can handle non utf-8 paths' do non_utf_char = [0x89].pack("c*").force_encoding("UTF-8") - stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha2 πå†h/†ø/ƒîlé#{non_utf_char}\nsha1") + stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha2 πå†h/†ø/ƒîlé#{non_utf_char}\nsha1") rev_list.new_objects(require_path: true) do |object_ids| expect(object_ids.force).to eq(%w[sha2]) @@ -74,7 +65,7 @@ describe Gitlab::Git::RevList do end it 'can yield a lazy enumerator' do - stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") + stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") rev_list.new_objects do |object_ids| expect(object_ids).to be_a Enumerator::Lazy @@ -82,7 +73,7 @@ describe Gitlab::Git::RevList do end it 'returns the result of the block when given' do - stub_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") + stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") objects = rev_list.new_objects do |object_ids| object_ids.first @@ -94,13 +85,13 @@ describe Gitlab::Git::RevList do it 'can accept list of references to exclude' do stub_popen_rev_list('newrev', '--not', 'master', '--objects', output: "sha1\nsha2") - expect(rev_list.new_objects(not_in: ['master'])).to eq(%w[sha1 sha2]) + expect { |b| rev_list.new_objects(not_in: ['master'], &b) }.to yield_with_args(%w[sha1 sha2]) end it 'handles empty list of references to exclude as listing all known objects' do stub_popen_rev_list('newrev', '--objects', output: "sha1\nsha2") - expect(rev_list.new_objects(not_in: [])).to eq(%w[sha1 sha2]) + expect { |b| rev_list.new_objects(not_in: [], &b) }.to yield_with_args(%w[sha1 sha2]) end end @@ -108,15 +99,15 @@ describe Gitlab::Git::RevList do it 'fetches list of all pushed objects using rev-list' do stub_popen_rev_list('--all', '--objects', output: "sha1\nsha2") - expect(rev_list.all_objects).to eq(%w[sha1 sha2]) + expect { |b| rev_list.all_objects(&b) }.to yield_with_args(%w[sha1 sha2]) end end context "#missed_ref" do - let(:rev_list) { described_class.new(oldrev: 'oldrev', newrev: 'newrev', path_to_repo: project.repository.path_to_repo) } + let(:rev_list) { described_class.new(repository, oldrev: 'oldrev', newrev: 'newrev') } it 'calls out to `popen`' do - stub_popen_rev_list('--max-count=1', 'oldrev', '^newrev', output: "sha1\nsha2") + stub_popen_rev_list('--max-count=1', 'oldrev', '^newrev', with_lazy_block: false, output: "sha1\nsha2") expect(rev_list.missed_ref).to eq(%w[sha1 sha2]) end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 2009a8ac48cd0cf7078dcc14d09d8ee5d6f72a69..3c3697e7aa9e50e74993b2a261e036ea50799663 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -5,11 +5,19 @@ describe Gitlab::GitAccess do let(:actor) { user } let(:project) { create(:project, :repository) } + let(:project_path) { project.path } + let(:namespace_path) { project&.namespace&.path } let(:protocol) { 'ssh' } let(:authentication_abilities) { %i[read_project download_code push_code] } let(:redirected_path) { nil } - let(:access) { described_class.new(actor, project, protocol, authentication_abilities: authentication_abilities, redirected_path: redirected_path) } + let(:access) do + described_class.new(actor, project, + protocol, authentication_abilities: authentication_abilities, + namespace_path: namespace_path, project_path: project_path, + redirected_path: redirected_path) + end + let(:push_access_check) { access.check('git-receive-pack', '_any') } let(:pull_access_check) { access.check('git-upload-pack', '_any') } @@ -111,7 +119,7 @@ describe Gitlab::GitAccess do end it 'does not block pushes with "not found"' do - expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:upload]) + expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload]) end end end @@ -145,6 +153,7 @@ describe Gitlab::GitAccess do context 'when the project is nil' do let(:project) { nil } + let(:project_path) { "new-project" } it 'blocks push and pull with "not found"' do aggregate_failures do @@ -152,6 +161,42 @@ describe Gitlab::GitAccess do expect { push_access_check }.to raise_not_found end end + + context 'when user is allowed to create project in namespace' do + let(:namespace_path) { user.namespace.path } + let(:access) do + described_class.new(actor, nil, + protocol, authentication_abilities: authentication_abilities, + project_path: project_path, namespace_path: namespace_path, + redirected_path: redirected_path) + end + + it 'blocks pull access with "not found"' do + expect { pull_access_check }.to raise_not_found + end + + it 'allows push access' do + expect { push_access_check }.not_to raise_error + end + end + + context 'when user is not allowed to create project in namespace' do + let(:user2) { create(:user) } + let(:namespace_path) { user2.namespace.path } + let(:access) do + described_class.new(actor, nil, + protocol, authentication_abilities: authentication_abilities, + project_path: project_path, namespace_path: namespace_path, + redirected_path: redirected_path) + end + + it 'blocks push and pull with "not found"' do + aggregate_failures do + expect { pull_access_check }.to raise_not_found + expect { push_access_check }.to raise_not_found + end + end + end end end @@ -197,7 +242,7 @@ describe Gitlab::GitAccess do it 'enqueues a redirected message' do push_access_check - expect(Gitlab::Checks::ProjectMoved.fetch_redirect_message(user.id, project.id)).not_to be_nil + expect(Gitlab::Checks::ProjectMoved.fetch_message(user.id, project.id)).not_to be_nil end end @@ -273,6 +318,52 @@ describe Gitlab::GitAccess do end end + describe '#check_authentication_abilities!' do + before do + project.add_master(user) + end + + context 'when download' do + let(:authentication_abilities) { [] } + + it 'raises unauthorized with download error' do + expect { pull_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_download]) + end + + context 'when authentication abilities include download code' do + let(:authentication_abilities) { [:download_code] } + + it 'does not raise any errors' do + expect { pull_access_check }.not_to raise_error + end + end + + context 'when authentication abilities include build download code' do + let(:authentication_abilities) { [:build_download_code] } + + it 'does not raise any errors' do + expect { pull_access_check }.not_to raise_error + end + end + end + + context 'when upload' do + let(:authentication_abilities) { [] } + + it 'raises unauthorized with push error' do + expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload]) + end + + context 'when authentication abilities include push code' do + let(:authentication_abilities) { [:push_code] } + + it 'does not raise any errors' do + expect { push_access_check }.not_to raise_error + end + end + end + end + describe '#check_command_disabled!' do before do project.add_master(user) @@ -311,6 +402,117 @@ describe Gitlab::GitAccess do end end + describe '#check_db_accessibility!' do + context 'when in a read-only GitLab instance' do + before do + create(:protected_branch, name: 'feature', project: project) + allow(Gitlab::Database).to receive(:read_only?) { true } + end + + it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:cannot_push_to_read_only]) } + end + end + + describe '#ensure_project_on_push!' do + let(:access) do + described_class.new(actor, project, + protocol, authentication_abilities: authentication_abilities, + project_path: project_path, namespace_path: namespace_path, + redirected_path: redirected_path) + end + + context 'when push' do + let(:cmd) { 'git-receive-pack' } + + context 'when project does not exist' do + let(:project_path) { "nonexistent" } + let(:project) { nil } + + context 'when changes is _any' do + let(:changes) { '_any' } + + context 'when authentication abilities include push code' do + let(:authentication_abilities) { [:push_code] } + + context 'when user can create project in namespace' do + let(:namespace_path) { user.namespace.path } + + it 'creates a new project' do + expect { access.send(:ensure_project_on_push!, cmd, changes) }.to change { Project.count }.by(1) + end + end + + context 'when user cannot create project in namespace' do + let(:user2) { create(:user) } + let(:namespace_path) { user2.namespace.path } + + it 'does not create a new project' do + expect { access.send(:ensure_project_on_push!, cmd, changes) }.not_to change { Project.count } + end + end + end + + context 'when authentication abilities do not include push code' do + let(:authentication_abilities) { [] } + + context 'when user can create project in namespace' do + let(:namespace_path) { user.namespace.path } + + it 'does not create a new project' do + expect { access.send(:ensure_project_on_push!, cmd, changes) }.not_to change { Project.count } + end + end + end + end + + context 'when check contains actual changes' do + let(:changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch" } + + it 'does not create a new project' do + expect { access.send(:ensure_project_on_push!, cmd, changes) }.not_to change { Project.count } + end + end + end + + context 'when project exists' do + let(:changes) { '_any' } + let!(:project) { create(:project) } + + it 'does not create a new project' do + expect { access.send(:ensure_project_on_push!, cmd, changes) }.not_to change { Project.count } + end + end + + context 'when deploy key is used' do + let(:key) { create(:deploy_key, user: user) } + let(:actor) { key } + let(:project_path) { "nonexistent" } + let(:project) { nil } + let(:namespace_path) { user.namespace.path } + let(:changes) { '_any' } + + it 'does not create a new project' do + expect { access.send(:ensure_project_on_push!, cmd, changes) }.not_to change { Project.count } + end + end + end + + context 'when pull' do + let(:cmd) { 'git-upload-pack' } + let(:changes) { '_any' } + + context 'when project does not exist' do + let(:project_path) { "new-project" } + let(:namespace_path) { user.namespace.path } + let(:project) { nil } + + it 'does not create a new project' do + expect { access.send(:ensure_project_on_push!, cmd, changes) }.not_to change { Project.count } + end + end + end + end + describe '#check_download_access!' do it 'allows masters to pull' do project.add_master(user) @@ -338,7 +540,9 @@ describe Gitlab::GitAccess do context 'when project is public' do let(:public_project) { create(:project, :public, :repository) } - let(:access) { described_class.new(nil, public_project, 'web', authentication_abilities: []) } + let(:project_path) { public_project.path } + let(:namespace_path) { public_project.namespace.path } + let(:access) { described_class.new(nil, public_project, 'web', authentication_abilities: [:download_code], project_path: project_path, namespace_path: namespace_path) } context 'when repository is enabled' do it 'give access to download code' do @@ -638,19 +842,6 @@ describe Gitlab::GitAccess do admin: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false })) end end - - context "when in a read-only GitLab instance" do - before do - create(:protected_branch, name: 'feature', project: project) - allow(Gitlab::Database).to receive(:read_only?) { true } - end - - # Only check admin; if an admin can't do it, other roles can't either - matrix = permissions_matrix[:admin].dup - matrix.each { |key, _| matrix[key] = false } - - run_permission_checks(admin: matrix) - end end describe 'build authentication abilities' do @@ -661,26 +852,26 @@ describe Gitlab::GitAccess do project.add_reporter(user) end - it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:upload]) } + it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload]) } end context 'when unauthorized' do context 'to public project' do let(:project) { create(:project, :public, :repository) } - it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:upload]) } + it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload]) } end context 'to internal project' do let(:project) { create(:project, :internal, :repository) } - it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:upload]) } + it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload]) } end context 'to private project' do let(:project) { create(:project, :private, :repository) } - it { expect { push_access_check }.to raise_not_found } + it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload]) } end end end @@ -767,8 +958,7 @@ describe Gitlab::GitAccess do end def raise_not_found - raise_error(Gitlab::GitAccess::NotFoundError, - Gitlab::GitAccess::ERROR_MESSAGES[:project_not_found]) + raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:project_not_found]) end def build_authentication_abilities diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb index 85991c38363e9ca8f726c61f0083e55d2991140f..a40330d853f1016683f913815098fc51991b9130 100644 --- a/spec/lib/gitlab/path_regex_spec.rb +++ b/spec/lib/gitlab/path_regex_spec.rb @@ -194,8 +194,8 @@ describe Gitlab::PathRegex do end end - describe '.root_namespace_path_regex' do - subject { described_class.root_namespace_path_regex } + describe '.root_namespace_route_regex' do + subject { %r{\A#{described_class.root_namespace_route_regex}/\z} } it 'rejects top level routes' do expect(subject).not_to match('admin/') @@ -318,8 +318,8 @@ describe Gitlab::PathRegex do end end - describe '.project_path_regex' do - subject { described_class.project_path_regex } + describe '.project_route_regex' do + subject { %r{\A#{described_class.project_route_regex}/\z} } it 'accepts top level routes' do expect(subject).to match('admin/') diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 2e7a0265a0bcf173010542d0e613579b85083d6b..dc2bb5b9747820c5b4c0d1e444467a25463295c5 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -465,4 +465,21 @@ describe Gitlab::Workhorse do end end end + + describe '.send_url' do + let(:url) { 'http://example.com' } + + subject { described_class.send_url(url) } + + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) + + expect(key).to eq("Gitlab-Workhorse-Send-Data") + expect(command).to eq("send-url") + expect(params).to eq({ + 'URL' => url, + 'AllowRedirects' => false + }.deep_stringify_keys) + end + end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index f5b3b4a9fc545f83ed65d411826fe314970e9274..0b3d5c6a0bdf62de78c8930dab274974069dc25f 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -675,7 +675,7 @@ describe Ci::Build do context 'build is erasable' do context 'new artifacts' do - let!(:build) { create(:ci_build, :trace, :success, :artifacts) } + let!(:build) { create(:ci_build, :trace_artifact, :success, :artifacts) } describe '#erase' do before do @@ -709,7 +709,7 @@ describe Ci::Build do end describe '#erased?' do - let!(:build) { create(:ci_build, :trace, :success, :artifacts) } + let!(:build) { create(:ci_build, :trace_artifact, :success, :artifacts) } subject { build.erased? } context 'job has not been erased' do @@ -744,7 +744,7 @@ describe Ci::Build do context 'old artifacts' do context 'build is erasable' do context 'new artifacts' do - let!(:build) { create(:ci_build, :trace, :success, :legacy_artifacts) } + let!(:build) { create(:ci_build, :trace_artifact, :success, :legacy_artifacts) } describe '#erase' do before do @@ -778,7 +778,7 @@ describe Ci::Build do end describe '#erased?' do - let!(:build) { create(:ci_build, :trace, :success, :legacy_artifacts) } + let!(:build) { create(:ci_build, :trace_artifact, :success, :legacy_artifacts) } subject { build.erased? } context 'job has not been erased' do diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index 0e18a326c68d774cc5b2dfd887301b2b00c5e85e..a2bd36537e60567762b9c2c471db9a95c33f445a 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -12,6 +12,9 @@ describe Ci::JobArtifact do it { is_expected.to respond_to(:created_at) } it { is_expected.to respond_to(:updated_at) } + it { is_expected.to delegate_method(:open).to(:file) } + it { is_expected.to delegate_method(:exists?).to(:file) } + describe '#set_size' do it 'sets the size' do expect(artifact.size).to eq(106365) diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index 3106207811a012c3a641772b9a40652d0d7383ae..8cb50d7465c0231e365debdb964b96264309e2f6 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -39,7 +39,7 @@ describe Group, 'Routable' do create(:group, parent: group, path: 'xyz') duplicate = build(:project, namespace: group, path: 'xyz') - expect { duplicate.save! }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Route path has already been taken, Route is invalid') + expect { duplicate.save! }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Path has already been taken') end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 5ea4acb668767dde38ef33409b4ac31b8ff6f747..338fb314ee937b4c10f6cd2a55f2009a1b492c2e 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -41,7 +41,6 @@ describe Group do describe 'validations' do it { is_expected.to validate_presence_of :name } - it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) } it { is_expected.to validate_presence_of :path } it { is_expected.not_to validate_presence_of :owner } it { is_expected.to validate_presence_of :two_factor_grace_period } diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 5e126bc4beafe183d94bb8a0a1d8be5f26d3fa4e..191b60e4383952b5267f72cb79a65e5e3c14a633 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -15,7 +15,6 @@ describe Namespace do describe 'validations' do it { is_expected.to validate_presence_of(:name) } - it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) } it { is_expected.to validate_length_of(:name).is_at_most(255) } it { is_expected.to validate_length_of(:description).is_at_most(255) } it { is_expected.to validate_presence_of(:path) } diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 6980ba335b80a3306137a9c9d95935c5e091e11a..622d8844a72ecb298a81e56fbbc5a896d31b994e 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -408,7 +408,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do context 'if the services is active' do it 'should return a message' do - expect(kubernetes_service.deprecation_message).to match(/Your cluster information on this page is still editable/) + expect(kubernetes_service.deprecation_message).to match(/Your Kubernetes cluster information on this page is still editable/) end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index da940571bc1fd24ab678dbcce917eff5c07001ba..a63f5d6d5a1f6ebeacc1898cc4f713c9e4cf3a3e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -129,7 +129,6 @@ describe Project do it { is_expected.to validate_length_of(:name).is_at_most(255) } it { is_expected.to validate_presence_of(:path) } - it { is_expected.to validate_uniqueness_of(:path).scoped_to(:namespace_id) } it { is_expected.to validate_length_of(:path).is_at_most(255) } it { is_expected.to validate_length_of(:description).is_at_most(2000) } diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb index 88f54fd10e5ce8eb1af09163202128192b2301eb..dfac82b327a70fe846ae536656633bc74093eb98 100644 --- a/spec/models/route_spec.rb +++ b/spec/models/route_spec.rb @@ -27,7 +27,7 @@ describe Route do redirect.save!(validate: false) expect(new_route.valid?).to be_falsey - expect(new_route.errors.first[1]).to eq('foo has been taken before. Please use another one') + expect(new_route.errors.first[1]).to eq('has been taken before') end end @@ -49,7 +49,7 @@ describe Route do redirect.save!(validate: false) expect(route.valid?).to be_falsey - expect(route.errors.first[1]).to eq('foo has been taken before. Please use another one') + expect(route.errors.first[1]).to eq('has been taken before') end end @@ -368,7 +368,7 @@ describe Route do group2.path = 'foo' group2.valid? - expect(group2.errors["route.path"].first).to eq('foo has been taken before. Please use another one') + expect(group2.errors[:path]).to eq(['has been taken before']) end end diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb index 0dcaa0263321e872813a46f4baf0409ee7cbd19b..36b8e5d304f145f7401322b305902261f91941e3 100644 --- a/spec/models/upload_spec.rb +++ b/spec/models/upload_spec.rb @@ -43,6 +43,18 @@ describe Upload do .to(a_string_matching(/\A\h{64}\z/)) end end + + describe 'after_destroy' do + context 'uploader is FileUploader-based' do + subject { create(:upload, :issuable_upload) } + + it 'calls delete_file!' do + is_expected.to receive(:delete_file!) + + subject.destroy + end + end + end end describe '#absolute_path' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 568aab8530ef2d9a9f5ac58237395792abe6eb97..cb02d526a98e581df9355245cec732e455e939e5 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -101,7 +101,7 @@ describe User do user = build(:user, username: 'dashboard') expect(user).not_to be_valid - expect(user.errors.values).to eq [['dashboard is a reserved name']] + expect(user.errors.messages[:username]).to eq ['dashboard is a reserved name'] end it 'allows child names' do @@ -116,12 +116,6 @@ describe User do expect(user).to be_valid end - it 'validates uniqueness' do - user = build(:user) - - expect(user).to validate_uniqueness_of(:username).case_insensitive - end - context 'when username is changed' do let(:user) { build_stubbed(:user, username: 'old_path', namespace: build_stubbed(:namespace)) } @@ -132,6 +126,35 @@ describe User do expect(user.errors.messages[:username].first).to match('cannot be changed if a personal project has container registry tags') end end + + context 'when the username was used by another user before' do + let(:username) { 'foo' } + let!(:other_user) { create(:user, username: username) } + + before do + other_user.username = 'bar' + other_user.save! + end + + it 'is invalid' do + user = build(:user, username: username) + + expect(user).not_to be_valid + expect(user.errors.full_messages).to eq(['Username has been taken before']) + end + end + + context 'when the username is in use by another user' do + let(:username) { 'foo' } + let!(:other_user) { create(:user, username: username) } + + it 'is invalid' do + user = build(:user, username: username) + + expect(user).not_to be_valid + expect(user.errors.full_messages).to eq(['Username has already been taken']) + end + end end it 'has a DB-level NOT NULL constraint on projects_limit' do @@ -1433,28 +1456,34 @@ describe User do describe '#sort' do before do described_class.delete_all - @user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha' - @user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega' - @user2 = create :user, created_at: Date.today - 2, last_sign_in_at: nil, name: 'Beta' + @user = create :user, created_at: Date.today, current_sign_in_at: Date.today, name: 'Alpha' + @user1 = create :user, created_at: Date.today - 1, current_sign_in_at: Date.today - 1, name: 'Omega' + @user2 = create :user, created_at: Date.today - 2, name: 'Beta' end context 'when sort by recent_sign_in' do - it 'sorts users by the recent sign-in time' do - expect(described_class.sort('recent_sign_in').first).to eq(@user) + let(:users) { described_class.sort('recent_sign_in') } + + it 'sorts users by recent sign-in time' do + expect(users.first).to eq(@user) + expect(users.second).to eq(@user1) end it 'pushes users who never signed in to the end' do - expect(described_class.sort('recent_sign_in').third).to eq(@user2) + expect(users.third).to eq(@user2) end end context 'when sort by oldest_sign_in' do + let(:users) { described_class.sort('oldest_sign_in') } + it 'sorts users by the oldest sign-in time' do - expect(described_class.sort('oldest_sign_in').first).to eq(@user1) + expect(users.first).to eq(@user1) + expect(users.second).to eq(@user) end it 'pushes users who never signed in to the end' do - expect(described_class.sort('oldest_sign_in').third).to eq(@user2) + expect(users.third).to eq(@user2) end end @@ -2264,17 +2293,17 @@ describe User do end context 'when there is a validation error (namespace name taken) while updating namespace' do - let!(:conflicting_namespace) { create(:group, name: new_username, path: 'quz') } + let!(:conflicting_namespace) { create(:group, path: new_username) } it 'causes the user save to fail' do expect(user.update_attributes(username: new_username)).to be_falsey - expect(user.namespace.errors.messages[:name].first).to eq('has already been taken') + expect(user.namespace.errors.messages[:path].first).to eq('has already been taken') end it 'adds the namespace errors to the user' do user.update_attributes(username: new_username) - expect(user.errors.full_messages.first).to eq('Namespace name has already been taken') + expect(user.errors.full_messages.first).to eq('Username has already been taken') end end end @@ -2617,7 +2646,7 @@ describe User do it 'should raise an ActiveRecord::RecordInvalid exception' do user2 = build(:user, username: 'foo') - expect { user2.save! }.to raise_error(ActiveRecord::RecordInvalid, /Route path foo has been taken before. Please use another one, Route is invalid/) + expect { user2.save! }.to raise_error(ActiveRecord::RecordInvalid, /Username has been taken before/) end end diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index 4a1d12cd448963a7b8e16618e2f6a3880c127fff..d53ba497ed19b911102864b2313c821880044ce8 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -364,18 +364,34 @@ describe WikiPage do end describe "#versions" do - before do - create_page("Update", "content") - @page = wiki.find_page("Update") + shared_examples 'wiki page versions' do + let(:page) { wiki.find_page("Update") } + + before do + create_page("Update", "content") + end + + after do + destroy_page("Update") + end + + it "returns an array of all commits for the page" do + 3.times { |i| page.update(content: "content #{i}") } + + expect(page.versions.count).to eq(4) + end + + it 'returns instances of WikiPageVersion' do + expect(page.versions).to all( be_a(Gitlab::Git::WikiPageVersion) ) + end end - after do - destroy_page("Update") + context 'when Gitaly is enabled' do + it_behaves_like 'wiki page versions' end - it "returns an array of all commits for the page" do - 3.times { |i| @page.update(content: "content #{i}") } - expect(@page.versions.count).to eq(4) + context 'when Gitaly is disabled', :disable_gitaly do + it_behaves_like 'wiki page versions' end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 884a258fd12820508f3c876eb77bdfdb6fcb350e..ea6b0a7184942e6d62df4bf7d3a66811a80b2056 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -368,7 +368,7 @@ describe API::Internal do context 'project as /namespace/project' do it do - pull(key, project_with_repo_path('/' + project.full_path)) + push(key, project_with_repo_path('/' + project.full_path)) expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy @@ -379,7 +379,7 @@ describe API::Internal do context 'project as namespace/project' do it do - pull(key, project_with_repo_path(project.full_path)) + push(key, project_with_repo_path(project.full_path)) expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy @@ -807,14 +807,27 @@ describe API::Internal do context 'with a redirected data' do it 'returns redirected message on the response' do - project_moved = Gitlab::Checks::ProjectMoved.new(project, user, 'foo/baz', 'http') - project_moved.add_redirect_message + project_moved = Gitlab::Checks::ProjectMoved.new(project, user, 'http', 'foo/baz') + project_moved.add_message post api("/internal/post_receive"), valid_params expect(response).to have_gitlab_http_status(200) expect(json_response["redirected_message"]).to be_present - expect(json_response["redirected_message"]).to eq(project_moved.redirect_message) + expect(json_response["redirected_message"]).to eq(project_moved.message) + end + end + + context 'with new project data' do + it 'returns new project message on the response' do + project_created = Gitlab::Checks::ProjectCreated.new(project, user, 'http') + project_created.add_message + + post api("/internal/post_receive"), valid_params + + expect(response).to have_gitlab_http_status(200) + expect(json_response["project_created_message"]).to be_present + expect(json_response["project_created_message"]).to eq(project_created.message) end end diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index f8d0b63afecec797a8eca819fcdb247afb0d2ed3..6192bbd4abb84dc8083d1f7c0e7712b7e7cc8559 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -446,16 +446,27 @@ describe API::Jobs do end describe 'GET /projects/:id/jobs/:job_id/trace' do - let(:job) { create(:ci_build, :trace, pipeline: pipeline) } - before do get api("/projects/#{project.id}/jobs/#{job.id}/trace", api_user) end context 'authorized user' do - it 'returns specific job trace' do - expect(response).to have_gitlab_http_status(200) - expect(response.body).to eq(job.trace.raw) + context 'when trace is artifact' do + let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) } + + it 'returns specific job trace' do + expect(response).to have_gitlab_http_status(200) + expect(response.body).to eq(job.trace.raw) + end + end + + context 'when trace is file' do + let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) } + + it 'returns specific job trace' do + expect(response).to have_gitlab_http_status(200) + expect(response.body).to eq(job.trace.raw) + end end end @@ -543,11 +554,11 @@ describe API::Jobs do end context 'job is erasable' do - let(:job) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline) } + let(:job) { create(:ci_build, :trace_artifact, :artifacts, :success, project: project, pipeline: pipeline) } it 'erases job content' do expect(response).to have_gitlab_http_status(201) - expect(job).not_to have_trace + expect(job.trace.exist?).to be_falsy expect(job.artifacts_file.exists?).to be_falsy expect(job.artifacts_metadata.exists?).to be_falsy end @@ -561,7 +572,7 @@ describe API::Jobs do end context 'job is not erasable' do - let(:job) { create(:ci_build, :trace, project: project, pipeline: pipeline) } + let(:job) { create(:ci_build, :trace_live, project: project, pipeline: pipeline) } it 'responds with forbidden' do expect(response).to have_gitlab_http_status(403) @@ -570,7 +581,7 @@ describe API::Jobs do context 'when a developer erases a build' do let(:role) { :developer } - let(:job) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline, user: owner) } + let(:job) { create(:ci_build, :trace_artifact, :artifacts, :success, project: project, pipeline: pipeline, user: owner) } context 'when the build was created by the developer' do let(:owner) { user } @@ -593,7 +604,7 @@ describe API::Jobs do context 'artifacts did not expire' do let(:job) do - create(:ci_build, :trace, :artifacts, :success, + create(:ci_build, :trace_artifact, :artifacts, :success, project: project, pipeline: pipeline, artifacts_expire_at: Time.now + 7.days) end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index c5c0b0c28675a473a29e836fe164ff44fb0b3cbc..0bd887484790af6fe27e7e4cb6c8e6199b62ef66 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -638,7 +638,7 @@ describe API::Runner do end describe 'PUT /api/v4/jobs/:id' do - let(:job) { create(:ci_build, :pending, :trace, pipeline: pipeline, runner_id: runner.id) } + let(:job) { create(:ci_build, :pending, :trace_live, pipeline: pipeline, runner_id: runner.id) } before do job.run! @@ -680,11 +680,17 @@ describe API::Runner do end context 'when tace is given' do - it 'updates a running build' do - update_job(trace: 'BUILD TRACE UPDATED') + it 'creates a trace artifact' do + allow_any_instance_of(BuildFinishedWorker).to receive(:perform).with(job.id) do + CreateTraceArtifactWorker.new.perform(job.id) + end + + update_job(state: 'success', trace: 'BUILD TRACE UPDATED') + job.reload expect(response).to have_gitlab_http_status(200) - expect(job.reload.trace.raw).to eq 'BUILD TRACE UPDATED' + expect(job.trace.raw).to eq 'BUILD TRACE UPDATED' + expect(job.job_artifacts_trace.open.read).to eq 'BUILD TRACE UPDATED' end end @@ -713,7 +719,7 @@ describe API::Runner do end describe 'PATCH /api/v4/jobs/:id/trace' do - let(:job) { create(:ci_build, :running, :trace, runner_id: runner.id, pipeline: pipeline) } + let(:job) { create(:ci_build, :running, :trace_live, runner_id: runner.id, pipeline: pipeline) } let(:headers) { { API::Helpers::Runner::JOB_TOKEN_HEADER => job.token, 'Content-Type' => 'text/plain' } } let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) } let(:update_interval) { 10.seconds.to_i } @@ -774,7 +780,7 @@ describe API::Runner do context 'when project for the build has been deleted' do let(:job) do - create(:ci_build, :running, :trace, runner_id: runner.id, pipeline: pipeline) do |job| + create(:ci_build, :running, :trace_live, runner_id: runner.id, pipeline: pipeline) do |job| job.project.update(pending_delete: true) end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 2428e63e14914d5ee15525845a68bbe998357b09..f406d2ffb22b9a070a4d3dee28a0d30b6edc6a39 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -199,6 +199,24 @@ describe API::Users do expect(json_response.size).to eq(1) expect(json_response.first['username']).to eq(user.username) end + + it 'returns the correct order when sorted by id' do + admin + user + + get api('/users', admin), { order_by: 'id', sort: 'asc' } + + expect(response).to match_response_schema('public_api/v4/user/admins') + expect(json_response.size).to eq(2) + expect(json_response.first['id']).to eq(admin.id) + expect(json_response.last['id']).to eq(user.id) + end + + it 'returns 400 when provided incorrect sort params' do + get api('/users', admin), { order_by: 'magic', sort: 'asc' } + + expect(response).to have_gitlab_http_status(400) + end end end diff --git a/spec/requests/api/v3/builds_spec.rb b/spec/requests/api/v3/builds_spec.rb index 3f92288fef03466dd96f53fe9fa6ca74994d632b..79041c6a79217e6d9e01f4d2795dbfaee1520557 100644 --- a/spec/requests/api/v3/builds_spec.rb +++ b/spec/requests/api/v3/builds_spec.rb @@ -352,7 +352,7 @@ describe API::V3::Builds do end describe 'GET /projects/:id/builds/:build_id/trace' do - let(:build) { create(:ci_build, :trace, pipeline: pipeline) } + let(:build) { create(:ci_build, :trace_live, pipeline: pipeline) } before do get v3_api("/projects/#{project.id}/builds/#{build.id}/trace", api_user) @@ -447,7 +447,7 @@ describe API::V3::Builds do end context 'job is erasable' do - let(:build) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline) } + let(:build) { create(:ci_build, :trace_artifact, :artifacts, :success, project: project, pipeline: pipeline) } it 'erases job content' do expect(response.status).to eq 201 @@ -463,7 +463,7 @@ describe API::V3::Builds do end context 'job is not erasable' do - let(:build) { create(:ci_build, :trace, project: project, pipeline: pipeline) } + let(:build) { create(:ci_build, :trace_live, project: project, pipeline: pipeline) } it 'responds with forbidden' do expect(response.status).to eq 403 @@ -478,7 +478,7 @@ describe API::V3::Builds do context 'artifacts did not expire' do let(:build) do - create(:ci_build, :trace, :artifacts, :success, + create(:ci_build, :trace_artifact, :artifacts, :success, project: project, pipeline: pipeline, artifacts_expire_at: Time.now + 7.days) end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 27bd22d6bca4de5dfdfa8c7a022be66064ac5e88..2e2dccdafad6bd9f4fe4649c0b78e7dea582339f 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -107,15 +107,39 @@ describe 'Git HTTP requests' do let(:user) { create(:user) } context "when the project doesn't exist" do - let(:path) { 'doesnt/exist.git' } + context "when namespace doesn't exist" do + let(:path) { 'doesnt/exist.git' } - it_behaves_like 'pulls require Basic HTTP Authentication' - it_behaves_like 'pushes require Basic HTTP Authentication' + it_behaves_like 'pulls require Basic HTTP Authentication' + it_behaves_like 'pushes require Basic HTTP Authentication' - context 'when authenticated' do - it 'rejects downloads and uploads with 404 Not Found' do - download_or_upload(path, user: user.username, password: user.password) do |response| - expect(response).to have_gitlab_http_status(:not_found) + context 'when authenticated' do + it 'rejects downloads and uploads with 404 Not Found' do + download_or_upload(path, user: user.username, password: user.password) do |response| + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + end + + context 'when namespace exists' do + let(:path) { "#{user.namespace.path}/new-project.git"} + + context 'when authenticated' do + it 'creates a new project under the existing namespace' do + expect do + upload(path, user: user.username, password: user.password) do |response| + expect(response).to have_gitlab_http_status(:ok) + end + end.to change { user.projects.count }.by(1) + end + + it 'rejects push with 422 Unprocessable Entity when project is invalid' do + path = "#{user.namespace.path}/new.git" + + push_get(path, user: user.username, password: user.password) + + expect(response).to have_gitlab_http_status(:unprocessable_entity) end end end @@ -596,7 +620,7 @@ describe 'Git HTTP requests' do push_get(path, env) expect(response).to have_gitlab_http_status(:forbidden) - expect(response.body).to eq(git_access_error(:upload)) + expect(response.body).to eq(git_access_error(:auth_upload)) end # We are "authenticated" as CI using a valid token here. But we are @@ -636,7 +660,7 @@ describe 'Git HTTP requests' do push_get path, env expect(response).to have_gitlab_http_status(:forbidden) - expect(response.body).to eq(git_access_error(:upload)) + expect(response.body).to eq(git_access_error(:auth_upload)) end end diff --git a/spec/services/ci/create_trace_artifact_service_spec.rb b/spec/services/ci/create_trace_artifact_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..847a88920fee4780b78c373206f627621082f4fe --- /dev/null +++ b/spec/services/ci/create_trace_artifact_service_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe Ci::CreateTraceArtifactService do + describe '#execute' do + subject { described_class.new(nil, nil).execute(job) } + + let(:job) { create(:ci_build) } + + context 'when the job does not have trace artifact' do + context 'when the job has a trace file' do + before do + allow_any_instance_of(Gitlab::Ci::Trace) + .to receive(:default_path) { expand_fixture_path('trace/sample_trace') } + + allow_any_instance_of(JobArtifactUploader).to receive(:move_to_cache) { false } + allow_any_instance_of(JobArtifactUploader).to receive(:move_to_store) { false } + end + + it 'creates trace artifact' do + expect { subject }.to change { Ci::JobArtifact.count }.by(1) + + expect(job.job_artifacts_trace.read_attribute(:file)).to eq('sample_trace') + end + + context 'when the job has already had trace artifact' do + before do + create(:ci_job_artifact, :trace, job: job) + end + + it 'does not create trace artifact' do + expect { subject }.not_to change { Ci::JobArtifact.count } + end + end + end + + context 'when the job does not have a trace file' do + it 'does not create trace artifact' do + expect { subject }.not_to change { Ci::JobArtifact.count } + end + end + end + end +end diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index cea5c8a6c051868c9b6d417955697ded82286e64..db9c216d3f40886a6857638c437e18de39dafcc4 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -21,7 +21,8 @@ describe Ci::RetryBuildService do %i[id status user token coverage trace runner artifacts_expire_at artifacts_file artifacts_metadata artifacts_size created_at updated_at started_at finished_at queued_at erased_by - erased_at auto_canceled_by job_artifacts job_artifacts_archive job_artifacts_metadata].freeze + erased_at auto_canceled_by job_artifacts job_artifacts_archive + job_artifacts_metadata job_artifacts_trace].freeze IGNORE_ACCESSORS = %i[type lock_version target_url base_tags trace_sections @@ -35,7 +36,7 @@ describe Ci::RetryBuildService do let(:build) do create(:ci_build, :failed, :artifacts, :expired, :erased, :queued, :coverage, :tags, :allowed_to_fail, :on_tag, - :triggered, :trace, :teardown_environment, + :triggered, :trace_artifact, :teardown_environment, description: 'my-job', stage: 'test', stage_id: stage.id, pipeline: pipeline, auto_canceled_by: another_pipeline) end diff --git a/spec/services/files/create_service_spec.rb b/spec/services/files/create_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..030263b1502763277e39f13828f725633c4e2cff --- /dev/null +++ b/spec/services/files/create_service_spec.rb @@ -0,0 +1,78 @@ +require "spec_helper" + +describe Files::CreateService do + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + let(:user) { create(:user) } + let(:file_content) { 'Test file content' } + let(:branch_name) { project.default_branch } + let(:start_branch) { branch_name } + + let(:commit_params) do + { + file_path: file_path, + commit_message: "Update File", + file_content: file_content, + file_content_encoding: "text", + start_project: project, + start_branch: start_branch, + branch_name: branch_name + } + end + + subject { described_class.new(project, user, commit_params) } + + before do + project.add_master(user) + end + + describe "#execute" do + context 'when file matches LFS filter' do + let(:file_path) { 'test_file.lfs' } + let(:branch_name) { 'lfs' } + + context 'with LFS disabled' do + it 'skips gitattributes check' do + expect(repository).not_to receive(:attributes_at) + + subject.execute + end + + it "doesn't create LFS pointers" do + subject.execute + + blob = repository.blob_at('lfs', file_path) + + expect(blob.data).not_to start_with('version https://git-lfs.github.com/spec/v1') + expect(blob.data).to eq(file_content) + end + end + + context 'with LFS enabled' do + before do + allow(project).to receive(:lfs_enabled?).and_return(true) + end + + it 'creates an LFS pointer' do + subject.execute + + blob = repository.blob_at('lfs', file_path) + + expect(blob.data).to start_with('version https://git-lfs.github.com/spec/v1') + end + + it "creates an LfsObject with the file's content" do + subject.execute + + expect(LfsObject.last.file.read).to eq file_content + end + + it 'links the LfsObject to the project' do + expect do + subject.execute + end.to change { project.lfs_objects.count }.by(1) + end + end + end + end +end diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb index bcc01b087f3a3bf02dd0b0ef48b1fd8af967fc52..e1c873f8c1eb2571cf483173ad501a3f96315904 100644 --- a/spec/services/groups/transfer_service_spec.rb +++ b/spec/services/groups/transfer_service_spec.rb @@ -177,7 +177,7 @@ describe Groups::TransferService, :postgresql do it 'should add an error on group' do transfer_service.execute(new_parent_group) - expect(transfer_service.error).to eq('Transfer failed: Validation failed: Route path has already been taken, Route is invalid') + expect(transfer_service.error).to eq('Transfer failed: Validation failed: Path has already been taken') end end diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb index fc1c3d67203801f8aee779ab84d2586dceab9561..757c31ab692c4902325ed6579563633d7d161fda 100644 --- a/spec/services/merge_requests/rebase_service_spec.rb +++ b/spec/services/merge_requests/rebase_service_spec.rb @@ -108,7 +108,7 @@ describe MergeRequests::RebaseService do context 'git commands', :disable_gitaly do it 'sets GL_REPOSITORY env variable when calling git commands' do expect(repository).to receive(:popen).exactly(3) - .with(anything, anything, hash_including('GL_REPOSITORY')) + .with(anything, anything, hash_including('GL_REPOSITORY'), anything) .and_return(['', 0]) service.execute(merge_request) diff --git a/spec/services/projects/gitlab_projects_import_service_spec.rb b/spec/services/projects/gitlab_projects_import_service_spec.rb index bb0e274c93e6850a4a757cf4a5bd5c15dce068bc..6b8f9619bc4ee4b969e9ab00fc232ea4698edfe8 100644 --- a/spec/services/projects/gitlab_projects_import_service_spec.rb +++ b/spec/services/projects/gitlab_projects_import_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Projects::GitlabProjectsImportService do - set(:namespace) { build(:namespace) } + set(:namespace) { create(:namespace) } let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } subject { described_class.new(namespace.owner, { namespace_id: namespace.id, path: path, file: file }) } diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb index f8d4a47b212083f0cff17bf0f0d6712edf2f24ea..a4b7fe4674f92989260440abe827e3a24b30004b 100644 --- a/spec/services/users/update_service_spec.rb +++ b/spec/services/users/update_service_spec.rb @@ -21,13 +21,13 @@ describe Users::UpdateService do end it 'includes namespace error messages' do - create(:group, name: 'taken', path: 'something_else') + create(:group, path: 'taken') result = {} expect do result = update_user(user, { username: 'taken' }) end.not_to change { user.reload.username } expect(result[:status]).to eq(:error) - expect(result[:message]).to eq('Namespace name has already been taken') + expect(result[:message]).to eq('Username has already been taken') end def update_user(user, opts) diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb index a559681a0798b654d79b560dfea9778c44700004..6a92e7fae519234e831c5e961e50f927923382da 100644 --- a/spec/uploaders/file_uploader_spec.rb +++ b/spec/uploaders/file_uploader_spec.rb @@ -48,6 +48,29 @@ describe FileUploader do end end + describe 'callbacks' do + describe '#prune_store_dir after :remove' do + before do + uploader.store!(fixture_file_upload('spec/fixtures/doc_sample.txt')) + end + + def store_dir + File.expand_path(uploader.store_dir, uploader.root) + end + + it 'is called' do + expect(uploader).to receive(:prune_store_dir).once + + uploader.remove! + end + + it 'prune the store directory' do + expect { uploader.remove! } + .to change { File.exist?(store_dir) }.from(true).to(false) + end + end + end + describe '#secret' do it 'generates a secret if none is provided' do expect(described_class).to receive(:generate_secret).and_return('secret') diff --git a/spec/uploaders/job_artifact_uploader_spec.rb b/spec/uploaders/job_artifact_uploader_spec.rb index d606404e95df41ce9ae88555e0f901698818cd4d..5612ec7e661f12bfb60d25f4403485671342aec4 100644 --- a/spec/uploaders/job_artifact_uploader_spec.rb +++ b/spec/uploaders/job_artifact_uploader_spec.rb @@ -11,6 +11,33 @@ describe JobArtifactUploader do cache_dir: %r[artifacts/tmp/cache], work_dir: %r[artifacts/tmp/work] + describe '#open' do + subject { uploader.open } + + context 'when trace is stored in File storage' do + context 'when file exists' do + let(:file) do + fixture_file_upload( + Rails.root.join('spec/fixtures/trace/sample_trace'), 'text/plain') + end + + before do + uploader.store!(file) + end + + it 'returns io stream' do + is_expected.to be_a(IO) + end + end + + context 'when file does not exist' do + it 'returns nil' do + is_expected.to be_nil + end + end + end + end + context 'file is stored in valid local_path' do let(:file) do fixture_file_upload( diff --git a/spec/validators/user_path_validator_spec.rb b/spec/validators/user_path_validator_spec.rb deleted file mode 100644 index a46089cc24f3623e61da258e7a31056c8a46e389..0000000000000000000000000000000000000000 --- a/spec/validators/user_path_validator_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'spec_helper' - -describe UserPathValidator do - let(:validator) { described_class.new(attributes: [:username]) } - - describe '.valid_path?' do - it 'handles invalid utf8' do - expect(described_class.valid_path?("a\0weird\255path")).to be_falsey - end - end - - describe '#validates_each' do - it 'adds a message when the path is not in the correct format' do - user = build(:user) - - validator.validate_each(user, :username, "Path with spaces, and comma's!") - - expect(user.errors[:username]).to include(Gitlab::PathRegex.namespace_format_message) - end - - it 'adds a message when the path is reserved when creating' do - user = build(:user, username: 'help') - - validator.validate_each(user, :username, 'help') - - expect(user.errors[:username]).to include('help is a reserved name') - end - - it 'adds a message when the path is reserved when updating' do - user = create(:user) - user.username = 'help' - - validator.validate_each(user, :username, 'help') - - expect(user.errors[:username]).to include('help is a reserved name') - end - end -end diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb index 1a7ffd5cdbff11e85ba5a4f07a8a3f83e94895cb..c7ff8cf3b92c390675e3d7223495ee9e3376b7f0 100644 --- a/spec/workers/build_finished_worker_spec.rb +++ b/spec/workers/build_finished_worker_spec.rb @@ -6,17 +6,15 @@ describe BuildFinishedWorker do let!(:build) { create(:ci_build) } it 'calculates coverage and calls hooks' do - expect(BuildCoverageWorker) + expect(BuildTraceSectionsWorker) .to receive(:new).ordered.and_call_original - expect(BuildHooksWorker) + expect(BuildCoverageWorker) .to receive(:new).ordered.and_call_original - expect(BuildTraceSectionsWorker) - .to receive(:perform_async) - expect_any_instance_of(BuildCoverageWorker) - .to receive(:perform) - expect_any_instance_of(BuildHooksWorker) - .to receive(:perform) + expect_any_instance_of(BuildTraceSectionsWorker).to receive(:perform) + expect_any_instance_of(BuildCoverageWorker).to receive(:perform) + expect(BuildHooksWorker).to receive(:perform_async) + expect(CreateTraceArtifactWorker).to receive(:perform_async) described_class.new.perform(build.id) end diff --git a/spec/workers/create_trace_artifact_worker_spec.rb b/spec/workers/create_trace_artifact_worker_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..854abd9cca77eb0aa35fc983d27c125b69cb856f --- /dev/null +++ b/spec/workers/create_trace_artifact_worker_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe CreateTraceArtifactWorker do + describe '#perform' do + subject { described_class.new.perform(job&.id) } + + context 'when job is found' do + let(:job) { create(:ci_build) } + + it 'executes service' do + expect_any_instance_of(Ci::CreateTraceArtifactService) + .to receive(:execute).with(job) + + subject + end + end + + context 'when job is not found' do + let(:job) { nil } + + it 'does not execute service' do + expect_any_instance_of(Ci::CreateTraceArtifactService) + .not_to receive(:execute) + + subject + end + end + end +end