Commit 2cce501f authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge commit '4457cf9d' into fix/gb/fix-redundant-pipeline-stages

* commit '4457cf9d': (76 commits)

Conflicts:
	spec/services/ci/retry_build_service_spec.rb
parents adb6dd06 4457cf9d
...@@ -411,6 +411,8 @@ end ...@@ -411,6 +411,8 @@ end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.83.0', require: 'gitaly' 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 gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -340,7 +340,7 @@ GEM ...@@ -340,7 +340,7 @@ GEM
mime-types (~> 3.0) mime-types (~> 3.0)
representable (~> 3.0) representable (~> 3.0)
retriable (>= 2.0, < 4.0) retriable (>= 2.0, < 4.0)
google-protobuf (3.5.1.1) google-protobuf (3.5.1)
googleapis-common-protos-types (1.0.1) googleapis-common-protos-types (1.0.1)
google-protobuf (~> 3.0) google-protobuf (~> 3.0)
googleauth (0.5.3) googleauth (0.5.3)
...@@ -1066,6 +1066,7 @@ DEPENDENCIES ...@@ -1066,6 +1066,7 @@ DEPENDENCIES
gollum-rugged_adapter (~> 0.4.4) gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0) gon (~> 6.1.0)
google-api-client (~> 0.13.6) google-api-client (~> 0.13.6)
google-protobuf (= 3.5.1)
gpgme gpgme
grape (~> 1.0) grape (~> 1.0)
grape-entity (~> 0.6.0) grape-entity (~> 0.6.0)
......
...@@ -35,10 +35,11 @@ export default class Clusters { ...@@ -35,10 +35,11 @@ export default class Clusters {
clusterStatus, clusterStatus,
clusterStatusReason, clusterStatusReason,
helpPath, helpPath,
ingressHelpPath,
} = document.querySelector('.js-edit-cluster-form').dataset; } = document.querySelector('.js-edit-cluster-form').dataset;
this.store = new ClustersStore(); this.store = new ClustersStore();
this.store.setHelpPath(helpPath); this.store.setHelpPaths(helpPath, ingressHelpPath);
this.store.updateStatus(clusterStatus); this.store.updateStatus(clusterStatus);
this.store.updateStatusReason(clusterStatusReason); this.store.updateStatusReason(clusterStatusReason);
this.service = new ClustersService({ this.service = new ClustersService({
...@@ -93,6 +94,7 @@ export default class Clusters { ...@@ -93,6 +94,7 @@ export default class Clusters {
props: { props: {
applications: this.state.applications, applications: this.state.applications,
helpPath: this.state.helpPath, helpPath: this.state.helpPath,
ingressHelpPath: this.state.ingressHelpPath,
}, },
}); });
}, },
...@@ -172,7 +174,7 @@ export default class Clusters { ...@@ -172,7 +174,7 @@ export default class Clusters {
.map(appId => newApplicationMap[appId].title); .map(appId => newApplicationMap[appId].title);
if (appTitles.length > 0) { 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(', '), appList: appTitles.join(', '),
}); });
Flash(text, 'notice', this.successApplicationContainer); Flash(text, 'notice', this.successApplicationContainer);
......
...@@ -18,11 +18,16 @@ ...@@ -18,11 +18,16 @@
required: false, required: false,
default: '', default: '',
}, },
ingressHelpPath: {
type: String,
required: false,
default: '',
},
}, },
computed: { computed: {
generalApplicationDescription() { generalApplicationDescription() {
return sprintf( return sprintf(
_.escape(s__(`ClusterIntegration|Install applications on your cluster. _.escape(s__(`ClusterIntegration|Install applications on your Kubernetes cluster.
Read more about %{helpLink}`)), Read more about %{helpLink}`)),
{ {
helpLink: `<a href="${this.helpPath}"> helpLink: `<a href="${this.helpPath}">
...@@ -34,7 +39,7 @@ ...@@ -34,7 +39,7 @@
}, },
helmTillerDescription() { helmTillerDescription() {
return _.escape(s__( 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 Tiller runs inside of your Kubernetes Cluster, and manages
releases of your charts.`, releases of your charts.`,
)); ));
...@@ -49,7 +54,7 @@ ...@@ -49,7 +54,7 @@
_.escape(s__( _.escape(s__(
`ClusterIntegration|%{boldNotice} This will add some extra resources `ClusterIntegration|%{boldNotice} This will add some extra resources
like a load balancer, which may incur additional costs depending on 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}.`, you can %{pricingLink}.`,
)), { )), {
boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`, boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
...@@ -59,13 +64,28 @@ ...@@ -59,13 +64,28 @@
false, 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: `<a href="${this.ingressHelpPath}">
${_.escape(s__('ClusterIntegration|More information'))}
</a>`,
},
false,
);
return ` return `
<p> <p>
${descriptionParagraph} ${descriptionParagraph}
</p> </p>
<p class="append-bottom-0"> <p>
${extraCostParagraph} ${extraCostParagraph}
</p> </p>
<p class="settings-message append-bottom-0">
${externalIpParagraph}
</p>
`; `;
}, },
gitlabRunnerDescription() { gitlabRunnerDescription() {
......
...@@ -4,6 +4,7 @@ export default class ClusterStore { ...@@ -4,6 +4,7 @@ export default class ClusterStore {
constructor() { constructor() {
this.state = { this.state = {
helpPath: null, helpPath: null,
ingressHelpPath: null,
status: null, status: null,
statusReason: null, statusReason: null,
applications: { applications: {
...@@ -39,8 +40,9 @@ export default class ClusterStore { ...@@ -39,8 +40,9 @@ export default class ClusterStore {
}; };
} }
setHelpPath(helpPath) { setHelpPaths(helpPath, ingressHelpPath) {
this.state.helpPath = helpPath; this.state.helpPath = helpPath;
this.state.ingressHelpPath = ingressHelpPath;
} }
updateStatus(status) { updateStatus(status) {
......
...@@ -8,7 +8,7 @@ import { convertPermissionToBoolean } from './lib/utils/common_utils'; ...@@ -8,7 +8,7 @@ import { convertPermissionToBoolean } from './lib/utils/common_utils';
``` ```
%button.js-project-feature-toggle.project-feature-toggle{ type: "button", %button.js-project-feature-toggle.project-feature-toggle{ type: "button",
class: "#{'is-checked' if enabled?}", 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? } %input{ type: "hidden", class: 'js-project-feature-toggle-input', value: enabled? }
``` ```
*/ */
......
...@@ -4,6 +4,11 @@ ...@@ -4,6 +4,11 @@
.page-title { .page-title {
margin-top: 0; margin-top: 0;
.color-label {
font-size: $gl-font-size;
padding: $gl-vert-padding $label-padding-modal;
}
} }
} }
......
...@@ -558,6 +558,7 @@ $jq-ui-default-color: #777; ...@@ -558,6 +558,7 @@ $jq-ui-default-color: #777;
* Label * Label
*/ */
$label-padding: 7px; $label-padding: 7px;
$label-padding-modal: 10px;
$label-gray-bg: #f8fafc; $label-gray-bg: #f8fafc;
$label-inverse-bg: #333; $label-inverse-bg: #333;
$label-remove-border: rgba(0, 0, 0, .1); $label-remove-border: rgba(0, 0, 0, .1);
......
...@@ -58,13 +58,13 @@ ...@@ -58,13 +58,13 @@
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
width: 200px; width: 200px;
margin-left: $gl-padding * 2;
margin-bottom: 0; margin-bottom: 0;
} }
.label { .label {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
vertical-align: middle;
max-width: 100%; max-width: 100%;
} }
} }
...@@ -79,26 +79,33 @@ ...@@ -79,26 +79,33 @@
width: 100px; width: 100px;
margin-left: 10px; margin-left: 10px;
margin-bottom: 0; margin-bottom: 0;
vertical-align: middle; vertical-align: top;
} }
} }
.label-description { .label-description {
display: block; display: block;
margin-bottom: 10px; margin-bottom: 10px;
margin-left: 50px;
.description-text {
margin-bottom: $gl-padding;
}
a {
color: $blue-600;
}
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
display: inline-block; display: inline-block;
width: 30%; max-width: 50%;
margin-left: 10px; margin-left: 10px;
margin-bottom: 0; margin-bottom: 0;
vertical-align: middle; vertical-align: top;
} }
} }
.label { .label {
padding: 8px 9px 9px; padding: 8px 12px;
font-size: 14px; font-size: 14px;
} }
} }
...@@ -116,6 +123,12 @@ ...@@ -116,6 +123,12 @@
} }
.manage-labels-list { .manage-labels-list {
@media(min-width: $screen-md-min) {
&.content-list li {
padding: $gl-padding 0;
}
}
> li:not(.empty-message):not(.is-not-draggable) { > li:not(.empty-message):not(.is-not-draggable) {
background-color: $white-light; background-color: $white-light;
cursor: move; cursor: move;
...@@ -133,8 +146,6 @@ ...@@ -133,8 +146,6 @@
} }
.btn-action { .btn-action {
color: $gl-text-color;
.fa { .fa {
font-size: 18px; font-size: 18px;
vertical-align: middle; vertical-align: middle;
...@@ -155,10 +166,18 @@ ...@@ -155,10 +166,18 @@
float: right; float: right;
} }
} }
@media (max-width: $screen-xs-max) {
.dropdown-menu {
min-width: 100%;
}
}
} }
.draggable-handler { .draggable-handler {
display: inline-block; display: inline-block;
vertical-align: top;
margin: 5px 0;
opacity: 0; opacity: 0;
transition: opacity .3s; transition: opacity .3s;
color: $gray-darkest; color: $gray-darkest;
...@@ -188,7 +207,7 @@ ...@@ -188,7 +207,7 @@
.toggle-priority { .toggle-priority {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: top;
button { button {
border-color: transparent; border-color: transparent;
...@@ -255,6 +274,11 @@ ...@@ -255,6 +274,11 @@
} }
.label-subscribe-button { .label-subscribe-button {
@media(min-width: $screen-md-min) {
min-width: 105px;
margin-left: $gl-padding;
}
.label-subscribe-button-icon { .label-subscribe-button-icon {
&[disabled] { &[disabled] {
opacity: 0.5; opacity: 0.5;
......
...@@ -54,7 +54,7 @@ class Groups::LabelsController < Groups::ApplicationController ...@@ -54,7 +54,7 @@ class Groups::LabelsController < Groups::ApplicationController
respond_to do |format| respond_to do |format|
format.html do 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 end
format.js format.js
end end
......
...@@ -42,7 +42,7 @@ class Projects::Clusters::GcpController < Projects::ApplicationController ...@@ -42,7 +42,7 @@ class Projects::Clusters::GcpController < Projects::ApplicationController
when 'true' when 'true'
return return
when 'false' when 'false'
flash[:alert] = _('Please <a href=%{link_to_billing} target="_blank" rel="noopener noreferrer">enable billing for one of your projects to be able to create a cluster</a>, 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 <a href=%{link_to_billing} target="_blank" rel="noopener noreferrer">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, 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 else
flash[:alert] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.') flash[:alert] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.')
end end
......
...@@ -41,7 +41,7 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -41,7 +41,7 @@ class Projects::ClustersController < Projects::ApplicationController
head :no_content head :no_content
end end
format.html do format.html do
flash[:notice] = "Cluster was successfully updated." flash[:notice] = _('Kubernetes cluster was successfully updated.')
redirect_to project_cluster_path(project, cluster) redirect_to project_cluster_path(project, cluster)
end end
end end
...@@ -55,10 +55,10 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -55,10 +55,10 @@ class Projects::ClustersController < Projects::ApplicationController
def destroy def destroy
if cluster.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 redirect_to project_clusters_path(project), status: 302
else else
flash[:notice] = "Cluster integration was not removed." flash[:notice] = _('Kubernetes cluster integration was not removed.')
render :show render :show
end end
end end
......
...@@ -5,6 +5,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController ...@@ -5,6 +5,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
rescue_from Gitlab::GitAccess::UnauthorizedError, with: :render_403 rescue_from Gitlab::GitAccess::UnauthorizedError, with: :render_403
rescue_from Gitlab::GitAccess::NotFoundError, with: :render_404 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-upload-pack (git pull)
# GET /foo/bar.git/info/refs?service=git-receive-pack (git push) # GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
...@@ -55,8 +56,15 @@ class Projects::GitHttpController < Projects::GitHttpClientController ...@@ -55,8 +56,15 @@ class Projects::GitHttpController < Projects::GitHttpClientController
render plain: exception.message, status: :not_found render plain: exception.message, status: :not_found
end end
def render_422(exception)
render plain: exception.message, status: :unprocessable_entity
end
def access 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 end
def access_actor def access_actor
...@@ -68,12 +76,17 @@ class Projects::GitHttpController < Projects::GitHttpClientController ...@@ -68,12 +76,17 @@ class Projects::GitHttpController < Projects::GitHttpClientController
# Use the magic string '_any' to indicate we do not know what the # Use the magic string '_any' to indicate we do not know what the
# changes are. This is also what gitlab-shell does. # changes are. This is also what gitlab-shell does.
access.check(git_command, '_any') access.check(git_command, '_any')
@project ||= access.project
end end
def access_klass def access_klass
@access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess @access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
end end
def project_path
@project_path ||= params[:project_id].sub(/\.git$/, '')
end
def log_user_activity def log_user_activity
Users::ActivityService.new(user, 'pull').execute Users::ActivityService.new(user, 'pull').execute
end end
......
...@@ -76,7 +76,7 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -76,7 +76,7 @@ class Projects::WikisController < Projects::ApplicationController
@page = @project_wiki.find_page(params[:id]) @page = @project_wiki.find_page(params[:id])
if @page 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) total_count: @page.count_versions)
.page(params[:page]) .page(params[:page])
else else
......
module GroupsHelper 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) def can_change_group_visibility_level?(group)
can?(current_user, :change_visibility_level, group) can?(current_user, :change_visibility_level, group)
end end
......
...@@ -21,6 +21,7 @@ module Ci ...@@ -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_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_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_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 # The "environment" field for builds is a String, and is the unexpanded name
def persisted_environment def persisted_environment
......
...@@ -9,9 +9,12 @@ module Ci ...@@ -9,9 +9,12 @@ module Ci
mount_uploader :file, JobArtifactUploader mount_uploader :file, JobArtifactUploader
delegate :open, :exists?, to: :file
enum file_type: { enum file_type: {
archive: 1, archive: 1,
metadata: 2 metadata: 2,
trace: 3
} }
def self.artifacts_size_for(project) def self.artifacts_size_for(project)
......
...@@ -180,7 +180,7 @@ module Clusters ...@@ -180,7 +180,7 @@ module Clusters
return unless managed? return unless managed?
if api_url_changed? || token_changed? || ca_pem_changed? 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 return false
end end
......
...@@ -39,7 +39,6 @@ module ArtifactMigratable ...@@ -39,7 +39,6 @@ module ArtifactMigratable
end end
def artifacts_size def artifacts_size
read_attribute(:artifacts_size).to_i + read_attribute(:artifacts_size).to_i + job_artifacts.sum(:size).to_i
job_artifacts_archive&.size.to_i + job_artifacts_metadata&.size.to_i
end end
end end
...@@ -7,11 +7,12 @@ module Routable ...@@ -7,11 +7,12 @@ module Routable
has_one :route, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 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 has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
validates_associated :route
validates :route, presence: true validates :route, presence: true
scope :with_route, -> { includes(:route) } scope :with_route, -> { includes(:route) }
after_validation :set_path_errors
before_validation do before_validation do
if full_path_changed? || full_name_changed? if full_path_changed? || full_name_changed?
prepare_route prepare_route
...@@ -125,6 +126,11 @@ module Routable ...@@ -125,6 +126,11 @@ module Routable
private 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 def uncached_full_path
if route && route.path.present? if route && route.path.present?
@full_path ||= route.path # rubocop:disable Gitlab/ModuleWithInstanceVariables @full_path ||= route.path # rubocop:disable Gitlab/ModuleWithInstanceVariables
......
...@@ -20,6 +20,9 @@ class Namespace < ActiveRecord::Base ...@@ -20,6 +20,9 @@ class Namespace < ActiveRecord::Base
has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :project_statistics 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 :owner, class_name: "User"
belongs_to :parent, class_name: "Namespace" belongs_to :parent, class_name: "Namespace"
...@@ -29,7 +32,6 @@ class Namespace < ActiveRecord::Base ...@@ -29,7 +32,6 @@ class Namespace < ActiveRecord::Base
validates :owner, presence: true, unless: ->(n) { n.type == "Group" } validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
validates :name, validates :name,
presence: true, presence: true,
uniqueness: { scope: :parent_id },
length: { maximum: 255 }, length: { maximum: 255 },
namespace_name: true namespace_name: true
......
...@@ -245,8 +245,7 @@ class Project < ActiveRecord::Base ...@@ -245,8 +245,7 @@ class Project < ActiveRecord::Base
validates :path, validates :path,
presence: true, presence: true,
project_path: true, project_path: true,
length: { maximum: 255 }, length: { maximum: 255 }
uniqueness: { scope: :namespace_id }
validates :namespace, presence: true validates :namespace, presence: true
validates :name, uniqueness: { scope: :namespace_id } validates :name, uniqueness: { scope: :namespace_id }
...@@ -511,10 +510,13 @@ class Project < ActiveRecord::Base ...@@ -511,10 +510,13 @@ class Project < ActiveRecord::Base
@repository ||= Repository.new(full_path, self, disk_path: disk_path) @repository ||= Repository.new(full_path, self, disk_path: disk_path)
end end
def reload_repository! def cleanup
@repository&.cleanup
@repository = nil @repository = nil
end end
alias_method :reload_repository!, :cleanup
def container_registry_url def container_registry_url
if Gitlab.config.registry.enabled if Gitlab.config.registry.enabled
"#{Gitlab.config.registry.host_port}/#{full_path.downcase}" "#{Gitlab.config.registry.host_port}/#{full_path.downcase}"
......
...@@ -150,9 +150,10 @@ class KubernetesService < DeploymentService ...@@ -150,9 +150,10 @@ class KubernetesService < DeploymentService
end end
def deprecation_message def deprecation_message
content = <<-MESSAGE.strip_heredoc content = _("Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page") % {
Kubernetes service integration has been deprecated. #{deprecated_message_content} your clusters using the new <a href=\'#{Gitlab::Routing.url_helpers.project_clusters_path(project)}'/>Clusters</a> page deprecated_message_content: deprecated_message_content,
MESSAGE url: Gitlab::Routing.url_helpers.project_clusters_path(project)
}
content.html_safe content.html_safe
end end
...@@ -248,9 +249,9 @@ class KubernetesService < DeploymentService ...@@ -248,9 +249,9 @@ class KubernetesService < DeploymentService
def deprecated_message_content def deprecated_message_content
if active? 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 else
"Fields on this page are now uneditable, you can configure" _("Fields on this page are now uneditable, you can configure")
end end
end end
end end
...@@ -93,6 +93,10 @@ class Repository ...@@ -93,6 +93,10 @@ class Repository
alias_method :raw, :raw_repository alias_method :raw, :raw_repository
def cleanup
@raw_repository&.cleanup
end
# Return absolute path to repository # Return absolute path to repository
def path_to_repo def path_to_repo
@path_to_repo ||= File.expand_path( @path_to_repo ||= File.expand_path(
......
...@@ -75,7 +75,7 @@ class Route < ActiveRecord::Base ...@@ -75,7 +75,7 @@ class Route < ActiveRecord::Base
def ensure_permanent_paths def ensure_permanent_paths
return if path.nil? 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 end
def conflicting_redirect_exists? def conflicting_redirect_exists?
......
...@@ -12,6 +12,10 @@ class Upload < ActiveRecord::Base ...@@ -12,6 +12,10 @@ class Upload < ActiveRecord::Base
before_save :calculate_checksum!, if: :foreground_checksummable? before_save :calculate_checksum!, if: :foreground_checksummable?
after_commit :schedule_checksum, if: :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) def self.hexdigest(path)
Digest::SHA256.file(path).hexdigest Digest::SHA256.file(path).hexdigest
end end
...@@ -49,6 +53,10 @@ class Upload < ActiveRecord::Base ...@@ -49,6 +53,10 @@ class Upload < ActiveRecord::Base
private private
def delete_file!
build_uploader.remove!
end
def checksummable? def checksummable?
checksum.nil? && local? && exist? checksum.nil? && local? && exist?
end end
......
...@@ -77,7 +77,7 @@ class User < ActiveRecord::Base ...@@ -77,7 +77,7 @@ class User < ActiveRecord::Base
# #
# Namespace for personal projects # 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 # Profile
has_many :keys, -> do has_many :keys, -> do
...@@ -151,12 +151,9 @@ class User < ActiveRecord::Base ...@@ -151,12 +151,9 @@ class User < ActiveRecord::Base
validates :projects_limit, validates :projects_limit,
presence: true, presence: true,
numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: Gitlab::Database::MAX_INT_VALUE } numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: Gitlab::Database::MAX_INT_VALUE }
validates :username, validates :username, presence: true
user_path: true,
presence: true,
uniqueness: { case_sensitive: false }
validate :namespace_uniq, if: :username_changed? validates :namespace, presence: true
validate :namespace_move_dir_allowed, if: :username_changed? validate :namespace_move_dir_allowed, if: :username_changed?
validate :unique_email, if: :email_changed? validate :unique_email, if: :email_changed?
...@@ -171,7 +168,8 @@ class User < ActiveRecord::Base ...@@ -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 :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 :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? } 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_update :username_changed_hook, if: :username_changed?
after_destroy :post_destroy_hook after_destroy :post_destroy_hook
after_destroy :remove_key_cache after_destroy :remove_key_cache
...@@ -230,8 +228,8 @@ class User < ActiveRecord::Base ...@@ -230,8 +228,8 @@ class User < ActiveRecord::Base
scope :active, -> { with_state(:active).non_internal } 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 :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 :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_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('last_sign_in_at', 'ASC')) } scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) }
def self.with_two_factor def self.with_two_factor
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id") joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
...@@ -505,17 +503,6 @@ class User < ActiveRecord::Base ...@@ -505,17 +503,6 @@ class User < ActiveRecord::Base
end end
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 def namespace_move_dir_allowed
if namespace&.any_project_has_container_registry_tags? if namespace&.any_project_has_container_registry_tags?
errors.add(:username, 'cannot be changed if a personal project has container registry tags.') errors.add(:username, 'cannot be changed if a personal project has container registry tags.')
...@@ -884,17 +871,16 @@ class User < ActiveRecord::Base ...@@ -884,17 +871,16 @@ class User < ActiveRecord::Base
end end
def ensure_namespace_correct def ensure_namespace_correct
# Ensure user has namespace if namespace
create_namespace!(path: username, name: username) unless namespace namespace.path = namespace.name = username if username_changed?
else
if username_changed? build_namespace(path: username, name: username)
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 end
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 end
def username_changed_hook def username_changed_hook
......
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
...@@ -5,7 +5,7 @@ module Clusters ...@@ -5,7 +5,7 @@ module Clusters
def execute(access_token = nil) def execute(access_token = nil)
@access_token = access_token @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| create_cluster.tap do |cluster|
ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted? ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted?
......
...@@ -28,7 +28,7 @@ module Clusters ...@@ -28,7 +28,7 @@ module Clusters
if elapsed_time_from_creation(operation) < TIMEOUT if elapsed_time_from_creation(operation) < TIMEOUT
WaitForClusterCreationWorker.perform_in(EAGER_INTERVAL, provider.cluster_id) WaitForClusterCreationWorker.perform_in(EAGER_INTERVAL, provider.cluster_id)
else else
provider.make_errored!("Cluster creation time exceeds timeout; #{TIMEOUT}") provider.make_errored!(_('Kubernetes cluster creation time exceeds timeout; %{timeout}') % { timeout: TIMEOUT })
end end
end end
......
module Files module Files
class CreateService < Files::BaseService class CreateService < Files::BaseService
def create_commit! 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( repository.create_file(
current_user, current_user,
@file_path, @file_path,
@file_content, content_or_lfs_pointer,
message: @commit_message, message: @commit_message,
branch_name: @branch_name, branch_name: @branch_name,
author_email: @author_email, author_email: @author_email,
......
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
...@@ -15,6 +15,8 @@ class FileUploader < GitlabUploader ...@@ -15,6 +15,8 @@ class FileUploader < GitlabUploader
storage :file storage :file
after :remove, :prune_store_dir
def self.root def self.root
File.join(options.storage_path, 'uploads') File.join(options.storage_path, 'uploads')
end end
...@@ -140,6 +142,10 @@ class FileUploader < GitlabUploader ...@@ -140,6 +142,10 @@ class FileUploader < GitlabUploader
end end
end end
def prune_store_dir
storage.delete_dir!(store_dir) # only remove when empty
end
def markdown_name def markdown_name
(image_or_video? ? File.basename(filename, File.extname(filename)) : filename).gsub("]", "\\]") (image_or_video? ? File.basename(filename, File.extname(filename)) : filename).gsub("]", "\\]")
end end
......
...@@ -13,6 +13,12 @@ class JobArtifactUploader < GitlabUploader ...@@ -13,6 +13,12 @@ class JobArtifactUploader < GitlabUploader
dynamic_segment dynamic_segment
end end
def open
raise 'Only File System is supported' unless file_storage?
File.open(path, "rb") if path
end
private private
def dynamic_segment def dynamic_segment
......
...@@ -13,10 +13,6 @@ class AbstractPathValidator < ActiveModel::EachValidator ...@@ -13,10 +13,6 @@ class AbstractPathValidator < ActiveModel::EachValidator
raise NotImplementedError raise NotImplementedError
end end
def self.full_path(record, value)
value
end
def self.valid_path?(path) def self.valid_path?(path)
encode!(path) encode!(path)
"#{path}/" =~ path_regex "#{path}/" =~ path_regex
...@@ -28,7 +24,7 @@ class AbstractPathValidator < ActiveModel::EachValidator ...@@ -28,7 +24,7 @@ class AbstractPathValidator < ActiveModel::EachValidator
return return
end end
full_path = self.class.full_path(record, value) full_path = record.build_full_path
return unless full_path return unless full_path
unless self.class.valid_path?(full_path) unless self.class.valid_path?(full_path)
......
...@@ -12,8 +12,4 @@ class NamespacePathValidator < AbstractPathValidator ...@@ -12,8 +12,4 @@ class NamespacePathValidator < AbstractPathValidator
def self.format_error_message def self.format_error_message
Gitlab::PathRegex.namespace_format_message Gitlab::PathRegex.namespace_format_message
end end
def self.full_path(record, value)
record.build_full_path
end
end end
...@@ -12,8 +12,4 @@ class ProjectPathValidator < AbstractPathValidator ...@@ -12,8 +12,4 @@ class ProjectPathValidator < AbstractPathValidator
def self.format_error_message def self.format_error_message
Gitlab::PathRegex.project_path_format_message Gitlab::PathRegex.project_path_format_message
end end
def self.full_path(record, value)
record.build_full_path
end
end end
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
...@@ -88,7 +88,7 @@ ...@@ -88,7 +88,7 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
#{ _('Members') } #{ _('Members') }
- if current_user && can?(current_user, :admin_group, @group) - 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 = link_to edit_group_path(@group) do
.nav-icon-container .nav-icon-container
= sprite_icon('settings') = sprite_icon('settings')
......
...@@ -186,9 +186,9 @@ ...@@ -186,9 +186,9 @@
- if project_nav_tab? :clusters - if project_nav_tab? :clusters
- show_cluster_hint = show_gke_cluster_integration_callout?(@project) - show_cluster_hint = show_gke_cluster_integration_callout?(@project)
= nav_link(controller: [:clusters, :user, :gcp]) do = 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 %span
Clusters = _('Kubernetes')
- if show_cluster_hint - if show_cluster_hint
.feature-highlight.js-feature-highlight{ disabled: true, .feature-highlight.js-feature-highlight{ disabled: true,
data: { trigger: 'manual', data: { trigger: 'manual',
...@@ -206,13 +206,12 @@ ...@@ -206,13 +206,12 @@
%p %p
= _('Protip:') = _('Protip:')
= link_to 'Auto DevOps', help_page_path('topics/autodevops/index.md') = 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 %hr
%button.btn.btn-create.btn-xs.dismiss-feature-highlight{ type: 'button' } %button.btn.btn-create.btn-xs.dismiss-feature-highlight{ type: 'button' }
%span= _("Got it!") %span= _("Got it!")
= sprite_icon('thumb-up') = sprite_icon('thumb-up')
- if @project.feature_available?(:builds, current_user) && !@project.empty_repo? - if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
= nav_link(path: 'pipelines#charts') do = nav_link(path: 'pipelines#charts') do
= link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
......
...@@ -5,11 +5,11 @@ ...@@ -5,11 +5,11 @@
= s_('ClusterIntegration|Google Kubernetes Engine') = s_('ClusterIntegration|Google Kubernetes Engine')
%p %p
- link_gke = link_to(s_('ClusterIntegration|Google Kubernetes Engine'), @cluster.gke_cluster_url, target: '_blank', rel: 'noopener noreferrer') - 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 .well.form-group
%label.text-danger %label.text-danger
= s_('ClusterIntegration|Remove cluster integration') = s_('ClusterIntegration|Remove Kubernetes cluster integration')
%p %p
= s_("ClusterIntegration|Remove this cluster's configuration from this project. 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 cluster's integration? 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 Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster.")})
%h4= s_('ClusterIntegration|Cluster integration') %h4= s_('ClusterIntegration|Kubernetes cluster integration')
.settings-content .settings-content
.hidden.js-cluster-error.alert.alert-danger.alert-block.append-bottom-10{ role: 'alert' } .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 %p.js-error-reason
.hidden.js-cluster-creating.alert.alert-info.alert-block.append-bottom-10{ role: 'alert' } .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' } .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')
.gl-responsive-table-row .gl-responsive-table-row
.table-section.section-30 .table-section.section-30
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Cluster") .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Kubernetes cluster")
.table-mobile-content .table-mobile-content
= link_to cluster.name, namespace_project_cluster_path(@project.namespace, @project, cluster) = link_to cluster.name, namespace_project_cluster_path(@project.namespace, @project, cluster)
.table-section.section-30 .table-section.section-30
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
.table-mobile-content .table-mobile-content
%button.js-project-feature-toggle.project-feature-toggle{ type: "button", %button.js-project-feature-toggle.project-feature-toggle{ type: "button",
class: "#{'is-checked' if cluster.enabled?} #{'is-disabled' if !cluster.can_toggle_cluster?}", 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?, disabled: !cluster.can_toggle_cluster?,
data: { endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } } data: { endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } }
%input.js-project-feature-toggle-input{ type: "hidden", value: cluster.enabled? } %input.js-project-feature-toggle-input{ type: "hidden", value: cluster.enabled? }
......
%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 .dropdown.clusters-dropdown
%button.dropdown-menu-toggle.dropdown-menu-full-width{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false } %button.dropdown-menu-toggle.dropdown-menu-full-width{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
...@@ -7,6 +7,6 @@ ...@@ -7,6 +7,6 @@
= icon('chevron-down') = icon('chevron-down')
%ul.dropdown-menu.clusters-dropdown-menu.dropdown-menu-full-width %ul.dropdown-menu.clusters-dropdown-menu.dropdown-menu-full-width
%li %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 %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))
...@@ -3,10 +3,9 @@ ...@@ -3,10 +3,9 @@
.svg-content= image_tag 'illustrations/clusters_empty.svg' .svg-content= image_tag 'illustrations/clusters_empty.svg'
.col-xs-12 .col-xs-12
.text-content .text-content
%h4.text-center= s_('ClusterIntegration|Integrate cluster automation') %h4.text-center= s_('ClusterIntegration|Integrate Kubernetes 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') - 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|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} %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 .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'
...@@ -5,15 +5,15 @@ ...@@ -5,15 +5,15 @@
%p %p
- if @cluster.enabled? - if @cluster.enabled?
- if can?(current_user, :update_cluster, @cluster) - 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 - else
= s_('ClusterIntegration|Cluster integration is enabled for this project.') = s_('ClusterIntegration|Kubernetes cluster integration is enabled for this project.')
- else - 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 %label.append-bottom-10.js-cluster-enable-toggle-area
%button{ type: 'button', %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)}", 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) } disabled: !can?(current_user, :update_cluster, @cluster) }
= field.hidden_field :enabled, { class: 'js-project-feature-toggle-input'} = field.hidden_field :enabled, { class: 'js-project-feature-toggle-input'}
%span.toggle-icon %span.toggle-icon
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
.form-group .form-group
%h5= s_('ClusterIntegration|Environment scope') %h5= s_('ClusterIntegration|Environment scope')
%p %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') = 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') = field.text_field :environment_scope, class: 'form-control js-select-on-focus', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
......
%h4.prepend-top-0 %h4.prepend-top-0
= s_('ClusterIntegration|Cluster integration') = s_('ClusterIntegration|Kubernetes cluster integration')
%p %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 %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 } = s_('ClusterIntegration|Learn more about %{link_to_documentation}').html_safe % { link_to_documentation: link }
%p %p
- link_to_help_page = link_to(s_('ClusterIntegration|help page'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') - 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_for @cluster, html: { class: 'prepend-top-20' }, url: gcp_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field|
= form_errors(@cluster) = form_errors(@cluster)
.form-group .form-group
= field.label :name, s_('ClusterIntegration|Cluster name') = field.label :name, s_('ClusterIntegration|Kubernetes cluster name')
= field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name') = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name')
.form-group .form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope') = 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') = field.text_field :environment_scope, class: 'form-control', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
...@@ -32,4 +32,4 @@ ...@@ -32,4 +32,4 @@
= provider_gcp_field.text_field :machine_type, class: 'form-control', placeholder: 'n1-standard-4' = provider_gcp_field.text_field :machine_type, class: 'form-control', placeholder: 'n1-standard-4'
.form-group .form-group
= field.submit s_('ClusterIntegration|Create cluster'), class: 'btn btn-success' = field.submit s_('ClusterIntegration|Create Kubernetes cluster'), class: 'btn btn-success'
%h4.prepend-top-20 %h4.prepend-top-20
= s_('ClusterIntegration|Enter the details for your cluster') = s_('ClusterIntegration|Enter the details for your Kubernetes cluster')
%p %p
= s_('ClusterIntegration|Please make sure that your Google account meets the following requirements:') = s_('ClusterIntegration|Please make sure that your Google account meets the following requirements:')
%ul %ul
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= s_('ClusterIntegration|Your account must have %{link_to_kubernetes_engine}').html_safe % { link_to_kubernetes_engine: link_to_kubernetes_engine } = s_('ClusterIntegration|Your account must have %{link_to_kubernetes_engine}').html_safe % { link_to_kubernetes_engine: link_to_kubernetes_engine }
%li %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') - 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 %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') - 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 }
.form-group .form-group
%label.append-bottom-10{ for: 'cluster-name' } %label.append-bottom-10{ for: 'cluster-name' }
= s_('ClusterIntegration|Cluster name') = s_('ClusterIntegration|Kubernetes cluster name')
.input-group .input-group
%input.form-control.cluster-name.js-select-on-focus{ value: @cluster.name, readonly: true } %input.form-control.cluster-name.js-select-on-focus{ value: @cluster.name, readonly: true }
%span.input-group-btn %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_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field|
= form_errors(@cluster) = form_errors(@cluster)
......
- breadcrumb_title "Cluster" - breadcrumb_title 'Kubernetes'
- page_title _("Login") - page_title _("Login")
.row.prepend-top-default .row.prepend-top-default
.col-sm-4 .col-sm-4
= render 'projects/clusters/sidebar' = render 'projects/clusters/sidebar'
.col-sm-8 .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 'header'
.row .row
.col-sm-8.col-sm-offset-4.signin-with-google .col-sm-8.col-sm-offset-4.signin-with-google
......
- breadcrumb_title "Cluster" - breadcrumb_title 'Kubernetes'
- page_title _("New Cluster") - page_title _("New Kubernetes Cluster")
.row.prepend-top-default .row.prepend-top-default
.col-sm-4 .col-sm-4
= render 'projects/clusters/sidebar' = render 'projects/clusters/sidebar'
.col-sm-8 .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 'header'
= render 'form' = render 'form'
- breadcrumb_title "Clusters" - breadcrumb_title 'Kubernetes'
- page_title "Clusters" - page_title "Kubernetes Clusters"
.clusters-container .clusters-container
- if @clusters.empty? - if @clusters.empty?
...@@ -7,11 +7,11 @@ ...@@ -7,11 +7,11 @@
- else - else
.top-area.adjust .top-area.adjust
.nav-text .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 .ci-table.js-clusters-list
.gl-responsive-table-row.table-row-header{ role: "row" } .gl-responsive-table-row.table-row-header{ role: "row" }
.table-section.section-30{ role: "rowheader" } .table-section.section-30{ role: "rowheader" }
= s_("ClusterIntegration|Cluster") = s_("ClusterIntegration|Kubernetes cluster")
.table-section.section-30{ role: "rowheader" } .table-section.section-30{ role: "rowheader" }
= s_("ClusterIntegration|Environment scope") = s_("ClusterIntegration|Environment scope")
.table-section.section-30{ role: "rowheader" } .table-section.section-30{ role: "rowheader" }
......
- breadcrumb_title "Cluster" - breadcrumb_title 'Kubernetes'
- page_title _("Cluster") - page_title _("Kubernetes Cluster")
.row.prepend-top-default .row.prepend-top-default
.col-sm-4 .col-sm-4
= render 'sidebar' = render 'sidebar'
.col-sm-8 .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' = 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') %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'
- @content_class = "limit-container-width" unless fluid_layout - @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 - breadcrumb_title @cluster.name
- page_title _("Cluster") - page_title _("Kubernetes Cluster")
- expanded = Rails.env.test? - expanded = Rails.env.test?
...@@ -13,7 +13,8 @@ ...@@ -13,7 +13,8 @@
toggle_status: @cluster.enabled? ? 'true': 'false', toggle_status: @cluster.enabled? ? 'true': 'false',
cluster_status: @cluster.status_name, cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason, 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 .js-cluster-application-notice
.flash-container .flash-container
...@@ -26,10 +27,10 @@ ...@@ -26,10 +27,10 @@
%section.settings#js-cluster-details{ class: ('expanded' if expanded) } %section.settings#js-cluster-details{ class: ('expanded' if expanded) }
.settings-header .settings-header
%h4= s_('ClusterIntegration|Cluster details') %h4= s_('ClusterIntegration|Kubernetes cluster details')
%button.btn.js-settings-toggle %button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand' = 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 .settings-content
- if @cluster.managed? - if @cluster.managed?
= render 'projects/clusters/gcp/show' = render 'projects/clusters/gcp/show'
...@@ -41,6 +42,6 @@ ...@@ -41,6 +42,6 @@
%h4= _('Advanced settings') %h4= _('Advanced settings')
%button.btn.js-settings-toggle %button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand' = 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 .settings-content
= render 'advanced_settings' = render 'advanced_settings'
= form_for @cluster, url: user_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field| = form_for @cluster, url: user_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field|
= form_errors(@cluster) = form_errors(@cluster)
.form-group .form-group
= field.label :name, s_('ClusterIntegration|Cluster name') = field.label :name, s_('ClusterIntegration|Kubernetes cluster name')
= field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name') = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name')
.form-group .form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope') = 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') = field.text_field :environment_scope, class: 'form-control', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
...@@ -25,4 +25,4 @@ ...@@ -25,4 +25,4 @@
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
.form-group .form-group
= field.submit s_('ClusterIntegration|Add cluster'), class: 'btn btn-success' = field.submit s_('ClusterIntegration|Add Kubernetes cluster'), class: 'btn btn-success'
%h4.prepend-top-20 %h4.prepend-top-20
= s_('ClusterIntegration|Enter the details for your cluster') = s_('ClusterIntegration|Enter the details for your Kubernetes cluster')
%p %p
- link_to_help_page = link_to(s_('ClusterIntegration|documentation'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') - 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 }
= form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| = form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field|
= form_errors(@cluster) = form_errors(@cluster)
.form-group .form-group
= field.label :name, s_('ClusterIntegration|Cluster name') = field.label :name, s_('ClusterIntegration|Kubernetes cluster name')
= field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|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| = field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field|
.form-group .form-group
......
- breadcrumb_title "Cluster" - breadcrumb_title 'Kubernetes'
- page_title _("New Cluster") - page_title _("New Kubernetes cluster")
.row.prepend-top-default .row.prepend-top-default
.col-sm-4 .col-sm-4
= render 'projects/clusters/sidebar' = render 'projects/clusters/sidebar'
.col-sm-8 .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' = render 'header'
.prepend-top-20 .prepend-top-20
= render 'form' = render 'form'
.modal{ id: "modal-delete-label-#{label.id}", tabindex: -1 }
.modal-dialog
.modal-content
.modal-header
%button.close{ data: {dismiss: 'modal' } } &times;
%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'
...@@ -5,10 +5,10 @@ ...@@ -5,10 +5,10 @@
- show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project) - 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) - 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 = 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" } } %button.btn.btn-default.label-options-toggle{ type: 'button', data: { toggle: "dropdown" } }
Options Options
= icon('caret-down') = icon('caret-down')
...@@ -46,14 +46,19 @@ ...@@ -46,14 +46,19 @@
data: {confirm: 'Remove this label? Are you sure?'}, data: {confirm: 'Remove this label? Are you sure?'},
class: 'text-danger' class: 'text-danger'
.pull-right.hidden-xs.hidden-sm.hidden-md .pull-right.hidden-xs.hidden-sm
- if show_label_merge_requests_link - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
= link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action btn-link') do = 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
view merge requests %span.sr-only Promote to Group
- if show_label_issues_link = sprite_icon('level-up')
= link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action btn-link') do - if can?(current_user, :admin_label, label)
view open issues = 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 - if current_user
.label-subscription.inline .label-subscription.inline
- if can_subscribe_to_label_in_different_levels?(label) - if can_subscribe_to_label_in_different_levels?(label)
...@@ -76,14 +81,4 @@ ...@@ -76,14 +81,4 @@
%span= label_subscription_toggle_button_text(label, @project) %span= label_subscription_toggle_button_text(label, @project)
= icon('spinner spin', class: 'label-subscribe-button-loading') = icon('spinner spin', class: 'label-subscribe-button-loading')
- if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group) = render 'shared/delete_label_modal', label: label
= 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')
- 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 %span.label-row
- if can?(current_user, :admin_label, @project) - if can?(current_user, :admin_label, @project)
.draggable-handler .draggable-handler
...@@ -13,6 +17,14 @@ ...@@ -13,6 +17,14 @@
- if defined?(@project) && @project.group.present? - if defined?(@project) && @project.group.present?
%span.label-type %span.label-type
= label.model_name.human.titleize = label.model_name.human.titleize
- if label.description
%span.label-description %span.label-description
- if label.description.present?
.description-text
= markdown_field(label, :description) = 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
&middot;
= link_to_label(label, subject: subject, type: :merge_request) { 'Merge requests' }
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
- pipeline_creation:run_pipeline_schedule - pipeline_creation:run_pipeline_schedule
- pipeline_default:build_coverage - pipeline_default:build_coverage
- pipeline_default:build_trace_sections - pipeline_default:build_trace_sections
- pipeline_default:create_trace_artifact
- pipeline_default:pipeline_metrics - pipeline_default:pipeline_metrics
- pipeline_default:pipeline_notification - pipeline_default:pipeline_notification
- pipeline_default:update_head_pipeline_for_merge_request - pipeline_default:update_head_pipeline_for_merge_request
......
...@@ -6,9 +6,13 @@ class BuildFinishedWorker ...@@ -6,9 +6,13 @@ class BuildFinishedWorker
def perform(build_id) def perform(build_id)
Ci::Build.find_by(id: build_id).try do |build| 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) 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 end
end end
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
...@@ -18,6 +18,8 @@ class ProjectCacheWorker ...@@ -18,6 +18,8 @@ class ProjectCacheWorker
update_statistics(project, statistics.map(&:to_sym)) update_statistics(project, statistics.map(&:to_sym))
project.repository.refresh_method_caches(files.map(&:to_sym)) project.repository.refresh_method_caches(files.map(&:to_sym))
project.cleanup
end end
def update_statistics(project, statistics = []) def update_statistics(project, statistics = [])
......
---
title: Deleting an upload will correctly clean up the filesystem.
merge_request: 16799
author:
type: fixed
---
title: User can now git push to create a new project
merge_request: 16547
author:
type: added
---
title: Fix Sort by Recent Sign-in in Admin Area
merge_request: 13852
author: Poornima M
---
title: Add sorting options for /users API (admin only)
merge_request: 16945
author:
type: added
---
title: Add a link to documentation on how to get external ip in the Kubernetes cluster details page
merge_request: 16937
author:
type: added
---
title: Close low level rugged repository in project cache worker
merge_request: 16930
author: Bastian Blank
type: fixed
---
title: Upgrade GitLab Workhorse to v3.6.0
merge_request:
author:
type: other
---
title: Validate user, group and project paths consistently, and only once
merge_request:
author:
type: fixed
---
title: Validate user namespace before saving so that errors persist on model
merge_request:
author:
type: fixed
---
title: Save traces as artifacts
merge_request: 16702
author:
type: changed
---
title: Override group sidebar links
merge_request: 16942
author: George Tsiolis
type: fixed
---
title: File Upload UI can create LFS pointers based on .gitattributes
merge_request: 16412
author:
type: fixed
---
title: Replace "cluster" with "Kubernetes cluster"
merge_request: 16778
author:
type: changed
---
title: Downgrade google-protobuf gem
merge_request: 16941
author:
type: other
# 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
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
......
...@@ -51,6 +51,11 @@ GET /users?blocked=true ...@@ -51,6 +51,11 @@ GET /users?blocked=true
GET /users 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 ```json
[ [
{ {
......
...@@ -16,7 +16,7 @@ There are many places where file uploading is used, according to contexts: ...@@ -16,7 +16,7 @@ There are many places where file uploading is used, according to contexts:
- Project avatars - Project avatars
- Issues/MR/Notes Markdown attachments - Issues/MR/Notes Markdown attachments
- Issues/MR/Notes Legacy Markdown attachments - Issues/MR/Notes Legacy Markdown attachments
- CI Build Artifacts - CI Artifacts (archive, metadata, trace)
- LFS Objects - LFS Objects
...@@ -35,7 +35,7 @@ they are still not 100% standardized. You can see them below: ...@@ -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 | | 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 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 | | 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 | | 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` CI Artifacts and LFS Objects behave differently in CE and EE. In CE they inherit the `GitlabUploader`
......
...@@ -33,5 +33,40 @@ ...@@ -33,5 +33,40 @@
1. Click **Create project**. 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 [import it]: ../workflow/importing/README.md
[reserved]: ../user/reserved_names.md [reserved]: ../user/reserved_names.md
# GitLab Helm Chart # GitLab Helm Chart
> **Note**: > **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). * This chart has been tested on Google Kubernetes Engine and Azure Container Service.
* 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).
**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 ## 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. 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: 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 - 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
......
# GitLab-Omnibus Helm Chart # GitLab-Omnibus Helm Chart
> **Note:** > **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). * This chart has been tested on Google Kubernetes Engine and Azure Container Service.
* 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).
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). 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 ## 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 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: The deployment includes:
......
...@@ -154,8 +154,8 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps ...@@ -154,8 +154,8 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
step 'commit has ci status' do step 'commit has ci status' do
@project.enable_ci @project.enable_ci
pipeline = create :ci_pipeline, project: @project, sha: sample_commit.id @pipeline = create(:ci_pipeline, project: @project, sha: sample_commit.id)
create :ci_build, pipeline: pipeline create(:ci_build, pipeline: @pipeline)
end end
step 'repository contains ".gitlab-ci.yml" file' do step 'repository contains ".gitlab-ci.yml" file' do
...@@ -163,7 +163,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps ...@@ -163,7 +163,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end end
step 'I see commit ci info' do 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 end
step 'I search "submodules" commits' do step 'I search "submodules" commits' do
......
...@@ -15,8 +15,9 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps ...@@ -15,8 +15,9 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
step 'I delete all labels' do step 'I delete all labels' do
page.within '.labels' do page.within '.labels' do
page.all('.remove-row').each do page.all('.label-list-item').each do
accept_confirm { first('.remove-row').click } first('.remove-row').click
first(:link, 'Delete label').click
end end
end end
end end
......
...@@ -60,8 +60,20 @@ module API ...@@ -60,8 +60,20 @@ module API
false false
end 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 private
def project_path_match
@project_path_match ||= params[:project].match(Gitlab::PathRegex.full_project_git_path_regex) || {}
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables # rubocop:disable Gitlab/ModuleWithInstanceVariables
def set_project def set_project
if params[:gl_repository] if params[:gl_repository]
......
...@@ -42,11 +42,14 @@ module API ...@@ -42,11 +42,14 @@ module API
end end
access_checker_klass = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess access_checker_klass = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
access_checker = access_checker_klass access_checker = access_checker_klass.new(actor, project,
.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities, redirected_path: redirected_path) protocol, authentication_abilities: ssh_authentication_abilities,
namespace_path: namespace_path, project_path: project_path,
redirected_path: redirected_path)
begin begin
access_checker.check(params[:action], params[:changes]) access_checker.check(params[:action], params[:changes])
@project ||= access_checker.project
rescue Gitlab::GitAccess::UnauthorizedError, Gitlab::GitAccess::NotFoundError => e rescue Gitlab::GitAccess::UnauthorizedError, Gitlab::GitAccess::NotFoundError => e
return { status: false, message: e.message } return { status: false, message: e.message }
end end
...@@ -207,8 +210,11 @@ module API ...@@ -207,8 +210,11 @@ module API
# A user is not guaranteed to be returned; an orphaned write deploy # A user is not guaranteed to be returned; an orphaned write deploy
# key could be used # key could be used
if user 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[:redirected_message] = redirect_message if redirect_message
output[:project_created_message] = project_created_message if project_created_message
end end
output output
......
...@@ -18,6 +18,14 @@ module API ...@@ -18,6 +18,14 @@ module API
User.find_by(id: id) || not_found!('User') User.find_by(id: id) || not_found!('User')
end 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 params :optional_attributes do
optional :skype, type: String, desc: 'The Skype username' optional :skype, type: String, desc: 'The Skype username'
optional :linkedin, type: String, desc: 'The LinkedIn username' optional :linkedin, type: String, desc: 'The LinkedIn username'
...@@ -35,6 +43,13 @@ module API ...@@ -35,6 +43,13 @@ module API
optional :avatar, type: File, desc: 'Avatar image for user' optional :avatar, type: File, desc: 'Avatar image for user'
all_or_none_of :extern_uid, :provider all_or_none_of :extern_uid, :provider
end 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 end
desc 'Get the list of users' do desc 'Get the list of users' do
...@@ -53,16 +68,18 @@ module API ...@@ -53,16 +68,18 @@ module API
optional :created_before, type: DateTime, desc: 'Return users created before the specified time' optional :created_before, type: DateTime, desc: 'Return users created before the specified time'
all_or_none_of :extern_uid, :provider all_or_none_of :extern_uid, :provider
use :sort_params
use :pagination use :pagination
end end
get do get do
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?) authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)
unless current_user&.admin? unless current_user&.admin?
params.except!(:created_after, :created_before) params.except!(:created_after, :created_before, :order_by, :sort)
end end
users = UsersFinder.new(current_user, params).execute users = UsersFinder.new(current_user, params).execute
users = reorder_users(users)
authorized = can?(current_user, :read_users_list) authorized = can?(current_user, :read_users_list)
......
class CarrierWaveStringFile < StringIO
def original_filename
""
end
end
...@@ -2,7 +2,7 @@ class UserUrlConstrainer ...@@ -2,7 +2,7 @@ class UserUrlConstrainer
def matches?(request) def matches?(request)
full_path = request.params[:username] 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? User.find_by_full_path(full_path, follow_redirects: request.get?).present?
end end
......
...@@ -15,8 +15,8 @@ module Gitlab ...@@ -15,8 +15,8 @@ module Gitlab
.ancestor?(oldrev, newrev) .ancestor?(oldrev, newrev)
else else
Gitlab::Git::RevList.new( Gitlab::Git::RevList.new(
path_to_repo: project.repository.path_to_repo, project.repository.raw, oldrev: oldrev, newrev: newrev
oldrev: oldrev, newrev: newrev).missed_ref.present? ).missed_ref.present?
end end
end end
end end
......
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
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
module Gitlab module Gitlab
module Checks module Checks
class ProjectMoved class ProjectMoved < PostPushMessage
REDIRECT_NAMESPACE = "redirect_namespace".freeze REDIRECT_NAMESPACE = "redirect_namespace".freeze
def initialize(project, user, redirected_path, protocol) def initialize(project, user, protocol, redirected_path)
@project = project
@user = user
@redirected_path = 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| super(project, user, protocol)
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
end end
def redirect_message(rejected: false) def message(rejected: false)
<<~MESSAGE.strip_heredoc <<~MESSAGE
Project '#{redirected_path}' was moved to '#{project.full_path}'. Project '#{redirected_path}' was moved to '#{project.full_path}'.
Please update your Git remote: Please update your Git remote:
...@@ -47,17 +25,17 @@ module Gitlab ...@@ -47,17 +25,17 @@ module Gitlab
private 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}" "#{REDIRECT_NAMESPACE}:#{user_id}:#{project_id}"
end end
def remote_url_message(rejected) def remote_url_message(rejected)
if rejected if rejected
"git remote set-url origin #{url} and try again." "git remote set-url origin #{url_to_repo} and try again."
else else
"git remote set-url origin #{url}" "git remote set-url origin #{url_to_repo}"
end end
end end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment