Commit c51eb79b authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into '26388-push-to-create-a-new-project'

# Conflicts:
#   lib/gitlab/path_regex.rb
parents 8b4280cb 3f3b84e0
...@@ -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? }
``` ```
*/ */
......
...@@ -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
......
...@@ -76,9 +76,9 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -76,9 +76,9 @@ 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
redirect_to( redirect_to(
project_wiki_path(@project, :home), project_wiki_path(@project, :home),
......
...@@ -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
...@@ -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,19 +871,18 @@ class User < ActiveRecord::Base ...@@ -884,19 +871,18 @@ 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 end
def set_username_errors
namespace_path_errors = self.errors.delete(:"namespace.path")
self.errors[:username].concat(namespace_path_errors) if namespace_path_errors
end
def username_changed_hook def username_changed_hook
system_hook_service.execute_hooks_for(self, :rename) system_hook_service.execute_hooks_for(self, :rename)
end end
......
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
...@@ -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'
...@@ -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: 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: 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
...@@ -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`
......
# 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:
......
...@@ -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
......
...@@ -52,12 +52,14 @@ module Gitlab ...@@ -52,12 +52,14 @@ module Gitlab
end end
def exist? def exist?
current_path.present? || old_trace.present? trace_artifact&.exists? || current_path.present? || old_trace.present?
end end
def read def read
stream = Gitlab::Ci::Trace::Stream.new do stream = Gitlab::Ci::Trace::Stream.new do
if current_path if trace_artifact
trace_artifact.open
elsif current_path
File.open(current_path, "rb") File.open(current_path, "rb")
elsif old_trace elsif old_trace
StringIO.new(old_trace) StringIO.new(old_trace)
...@@ -82,6 +84,8 @@ module Gitlab ...@@ -82,6 +84,8 @@ module Gitlab
end end
def erase! def erase!
trace_artifact&.destroy
paths.each do |trace_path| paths.each do |trace_path|
FileUtils.rm(trace_path, force: true) FileUtils.rm(trace_path, force: true)
end end
...@@ -137,6 +141,10 @@ module Gitlab ...@@ -137,6 +141,10 @@ module Gitlab
"#{job.id}.log" "#{job.id}.log"
) if job.project&.ci_id ) if job.project&.ci_id
end end
def trace_artifact
job.job_artifacts_trace
end
end end
end end
end end
...@@ -25,8 +25,7 @@ module Gitlab ...@@ -25,8 +25,7 @@ module Gitlab
private private
def rev_list def rev_list
::Gitlab::Git::RevList.new(path_to_repo: @repository.path_to_repo, Gitlab::Git::RevList.new(@repository, newrev: @newrev)
newrev: @newrev)
end end
end end
end end
......
module Gitlab
module Git
class LfsPointerFile
def initialize(data)
@data = data
end
def pointer
@pointer ||= <<~FILE
version https://git-lfs.github.com/spec/v1
oid sha256:#{sha256}
size #{size}
FILE
end
def size
@size ||= @data.bytesize
end
def sha256
@sha256 ||= Digest::SHA256.hexdigest(@data)
end
end
end
end
...@@ -25,7 +25,7 @@ module Gitlab ...@@ -25,7 +25,7 @@ module Gitlab
stdin.close stdin.close
if lazy_block if lazy_block
return lazy_block.call(stdout.lazy) return [lazy_block.call(stdout.lazy), 0]
else else
cmd_output << stdout.read cmd_output << stdout.read
end end
......
...@@ -128,6 +128,10 @@ module Gitlab ...@@ -128,6 +128,10 @@ module Gitlab
raise NoRepository.new('no repository for such path') raise NoRepository.new('no repository for such path')
end end
def cleanup
@rugged&.close
end
def circuit_breaker def circuit_breaker
@circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(storage) @circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(storage)
end end
...@@ -1427,6 +1431,26 @@ module Gitlab ...@@ -1427,6 +1431,26 @@ module Gitlab
end end
end end
def rev_list(including: [], excluding: [], objects: false, &block)
args = ['rev-list']
args.push(*rev_list_param(including))
exclude_param = *rev_list_param(excluding)
if exclude_param.any?
args.push('--not')
args.push(*exclude_param)
end
args.push('--objects') if objects
run_git!(args, lazy_block: block)
end
def missed_ref(oldrev, newrev)
run_git!(['rev-list', '--max-count=1', oldrev, "^#{newrev}"])
end
private private
def local_write_ref(ref_path, ref, old_ref: nil, shell: true) def local_write_ref(ref_path, ref, old_ref: nil, shell: true)
...@@ -1475,7 +1499,7 @@ module Gitlab ...@@ -1475,7 +1499,7 @@ module Gitlab
Rails.logger.error "Unable to create #{ref_path} reference for repository #{path}: #{ex}" Rails.logger.error "Unable to create #{ref_path} reference for repository #{path}: #{ex}"
end end
def run_git(args, chdir: path, env: {}, nice: false, &block) def run_git(args, chdir: path, env: {}, nice: false, lazy_block: nil, &block)
cmd = [Gitlab.config.git.bin_path, *args] cmd = [Gitlab.config.git.bin_path, *args]
cmd.unshift("nice") if nice cmd.unshift("nice") if nice
...@@ -1485,12 +1509,12 @@ module Gitlab ...@@ -1485,12 +1509,12 @@ module Gitlab
end end
circuit_breaker.perform do circuit_breaker.perform do
popen(cmd, chdir, env, &block) popen(cmd, chdir, env, lazy_block: lazy_block, &block)
end end
end end
def run_git!(args, chdir: path, env: {}, nice: false, &block) def run_git!(args, chdir: path, env: {}, nice: false, lazy_block: nil, &block)
output, status = run_git(args, chdir: chdir, env: env, nice: nice, &block) output, status = run_git(args, chdir: chdir, env: env, nice: nice, lazy_block: lazy_block, &block)
raise GitError, output unless status.zero? raise GitError, output unless status.zero?
...@@ -2372,6 +2396,10 @@ module Gitlab ...@@ -2372,6 +2396,10 @@ module Gitlab
rescue Rugged::ReferenceError rescue Rugged::ReferenceError
0 0
end end
def rev_list_param(spec)
spec == :all ? ['--all'] : spec
end
end end
end end
end end
...@@ -5,17 +5,17 @@ module Gitlab ...@@ -5,17 +5,17 @@ module Gitlab
class RevList class RevList
include Gitlab::Git::Popen include Gitlab::Git::Popen
attr_reader :oldrev, :newrev, :path_to_repo attr_reader :oldrev, :newrev, :repository
def initialize(path_to_repo:, newrev:, oldrev: nil) def initialize(repository, newrev:, oldrev: nil)
@oldrev = oldrev @oldrev = oldrev
@newrev = newrev @newrev = newrev
@path_to_repo = path_to_repo @repository = repository
end end
# This method returns an array of new commit references # This method returns an array of new commit references
def new_refs def new_refs
execute([*base_args, newrev, '--not', '--all']) repository.rev_list(including: newrev, excluding: :all).split("\n")
end end
# Finds newly added objects # Finds newly added objects
...@@ -28,66 +28,39 @@ module Gitlab ...@@ -28,66 +28,39 @@ module Gitlab
# When given a block it will yield objects as a lazy enumerator so # When given a block it will yield objects as a lazy enumerator so
# the caller can limit work done instead of processing megabytes of data # the caller can limit work done instead of processing megabytes of data
def new_objects(require_path: nil, not_in: nil, &lazy_block) def new_objects(require_path: nil, not_in: nil, &lazy_block)
args = [*base_args, newrev, *not_in_refs(not_in), '--objects'] opts = {
including: newrev,
excluding: not_in.nil? ? :all : not_in,
require_path: require_path
}
get_objects(args, require_path: require_path, &lazy_block) get_objects(opts, &lazy_block)
end end
def all_objects(require_path: nil, &lazy_block) def all_objects(require_path: nil, &lazy_block)
args = [*base_args, '--all', '--objects'] get_objects(including: :all, require_path: require_path, &lazy_block)
get_objects(args, require_path: require_path, &lazy_block)
end end
# This methods returns an array of missed references # This methods returns an array of missed references
# #
# Should become obsolete after https://gitlab.com/gitlab-org/gitaly/issues/348. # Should become obsolete after https://gitlab.com/gitlab-org/gitaly/issues/348.
def missed_ref def missed_ref
execute([*base_args, '--max-count=1', oldrev, "^#{newrev}"]) repository.missed_ref(oldrev, newrev).split("\n")
end end
private private
def not_in_refs(references)
return ['--not', '--all'] unless references
return [] if references.empty?
references.prepend('--not')
end
def execute(args) def execute(args)
output, status = popen(args, nil, Gitlab::Git::Env.to_env_hash) repository.rev_list(args).split("\n")
unless status.zero?
raise "Got a non-zero exit code while calling out `#{args.join(' ')}`: #{output}"
end
output.split("\n")
end
def lazy_execute(args, &lazy_block)
popen(args, nil, Gitlab::Git::Env.to_env_hash, lazy_block: lazy_block)
end
def base_args
[
Gitlab.config.git.bin_path,
"--git-dir=#{path_to_repo}",
'rev-list'
]
end end
def get_objects(args, require_path: nil) def get_objects(including: [], excluding: [], require_path: nil)
if block_given? opts = { including: including, excluding: excluding, objects: true }
lazy_execute(args) do |lazy_output|
objects = objects_from_output(lazy_output, require_path: require_path)
yield(objects) repository.rev_list(opts) do |lazy_output|
end objects = objects_from_output(lazy_output, require_path: require_path)
else
object_output = execute(args)
objects_from_output(object_output, require_path: require_path) yield(objects)
end end
end end
......
...@@ -96,11 +96,23 @@ module Gitlab ...@@ -96,11 +96,23 @@ module Gitlab
# :per_page - The number of items per page. # :per_page - The number of items per page.
# :limit - Total number of items to return. # :limit - Total number of items to return.
def page_versions(page_path, options = {}) def page_versions(page_path, options = {})
current_page = gollum_page_by_path(page_path) @repository.gitaly_migrate(:wiki_page_versions) do |is_enabled|
if is_enabled
versions = gitaly_wiki_client.page_versions(page_path, options)
# Gitaly uses gollum-lib to get the versions. Gollum defaults to 20
# per page, but also fetches 20 if `limit` or `per_page` < 20.
# Slicing returns an array with the expected number of items.
slice_bound = options[:limit] || options[:per_page] || Gollum::Page.per_page
versions[0..slice_bound]
else
current_page = gollum_page_by_path(page_path)
commits_from_page(current_page, options).map do |gitlab_git_commit| commits_from_page(current_page, options).map do |gitlab_git_commit|
gollum_page = gollum_wiki.page(current_page.title, gitlab_git_commit.id) gollum_page = gollum_wiki.page(current_page.title, gitlab_git_commit.id)
Gitlab::Git::WikiPageVersion.new(gitlab_git_commit, gollum_page&.format) Gitlab::Git::WikiPageVersion.new(gitlab_git_commit, gollum_page&.format)
end
end
end end
end end
......
...@@ -101,6 +101,30 @@ module Gitlab ...@@ -101,6 +101,30 @@ module Gitlab
pages pages
end end
# options:
# :page - The Integer page number.
# :per_page - The number of items per page.
# :limit - Total number of items to return.
def page_versions(page_path, options)
request = Gitaly::WikiGetPageVersionsRequest.new(
repository: @gitaly_repo,
page_path: encode_binary(page_path),
page: options[:page] || 1,
per_page: options[:per_page] || Gollum::Page.per_page
)
stream = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_page_versions, request)
versions = []
stream.each do |message|
message.versions.each do |version|
versions << new_wiki_page_version(version)
end
end
versions
end
def find_file(name, revision) def find_file(name, revision)
request = Gitaly::WikiFindFileRequest.new( request = Gitaly::WikiFindFileRequest.new(
repository: @gitaly_repo, repository: @gitaly_repo,
...@@ -141,7 +165,7 @@ module Gitlab ...@@ -141,7 +165,7 @@ module Gitlab
private private
# If a block is given and the yielded value is true, iteration will be # If a block is given and the yielded value is truthy, iteration will be
# stopped early at that point; else the iterator is consumed entirely. # stopped early at that point; else the iterator is consumed entirely.
# The iterator is traversed with `next` to allow resuming the iteration. # The iterator is traversed with `next` to allow resuming the iteration.
def wiki_page_from_iterator(iterator) def wiki_page_from_iterator(iterator)
...@@ -158,10 +182,7 @@ module Gitlab ...@@ -158,10 +182,7 @@ module Gitlab
else else
wiki_page = GitalyClient::WikiPage.new(page.to_h) wiki_page = GitalyClient::WikiPage.new(page.to_h)
version = Gitlab::Git::WikiPageVersion.new( version = new_wiki_page_version(page.version)
Gitlab::Git::Commit.decorate(@repository, page.version.commit),
page.version.format
)
end end
end end
...@@ -170,6 +191,13 @@ module Gitlab ...@@ -170,6 +191,13 @@ module Gitlab
[wiki_page, version] [wiki_page, version]
end end
def new_wiki_page_version(version)
Gitlab::Git::WikiPageVersion.new(
Gitlab::Git::Commit.decorate(@repository, version.commit),
version.format
)
end
def gitaly_commit_details(commit_details) def gitaly_commit_details(commit_details)
Gitaly::WikiCommitDetails.new( Gitaly::WikiCommitDetails.new(
name: encode_binary(commit_details.name), name: encode_binary(commit_details.name),
......
...@@ -178,7 +178,7 @@ module Gitlab ...@@ -178,7 +178,7 @@ module Gitlab
valid_username = ::Namespace.clean_path(username) valid_username = ::Namespace.clean_path(username)
uniquify = Uniquify.new uniquify = Uniquify.new
valid_username = uniquify.string(valid_username) { |s| !UserPathValidator.valid_path?(s) } valid_username = uniquify.string(valid_username) { |s| !NamespacePathValidator.valid_path?(s) }
name = auth_hash.name name = auth_hash.name
name = valid_username if name.strip.empty? name = valid_username if name.strip.empty?
......
...@@ -171,18 +171,10 @@ module Gitlab ...@@ -171,18 +171,10 @@ module Gitlab
@project_git_route_regex ||= /#{project_route_regex}\.git/.freeze @project_git_route_regex ||= /#{project_route_regex}\.git/.freeze
end end
def root_namespace_path_regex
@root_namespace_path_regex ||= %r{\A#{root_namespace_route_regex}/\z}
end
def full_namespace_path_regex def full_namespace_path_regex
@full_namespace_path_regex ||= %r{\A#{full_namespace_route_regex}/\z} @full_namespace_path_regex ||= %r{\A#{full_namespace_route_regex}/\z}
end end
def project_path_regex
@project_path_regex ||= %r{\A#{project_route_regex}/\z}
end
def full_project_path_regex def full_project_path_regex
@full_project_path_regex ||= %r{\A#{full_namespace_route_regex}/#{project_route_regex}/\z} @full_project_path_regex ||= %r{\A#{full_namespace_route_regex}/#{project_route_regex}/\z}
end end
...@@ -191,10 +183,6 @@ module Gitlab ...@@ -191,10 +183,6 @@ module Gitlab
@full_project_git_path_regex ||= %r{\A\/?(?<namespace_path>#{full_namespace_route_regex})\/(?<project_path>#{project_route_regex})\.git\z} @full_project_git_path_regex ||= %r{\A\/?(?<namespace_path>#{full_namespace_route_regex})\/(?<project_path>#{project_route_regex})\.git\z}
end end
def full_namespace_format_regex
@namespace_format_regex ||= /A#{FULL_NAMESPACE_FORMAT_REGEX}\z/.freeze
end
def namespace_format_regex def namespace_format_regex
@namespace_format_regex ||= /\A#{NAMESPACE_FORMAT_REGEX}\z/.freeze @namespace_format_regex ||= /\A#{NAMESPACE_FORMAT_REGEX}\z/.freeze
end end
......
...@@ -45,7 +45,7 @@ module Gitlab ...@@ -45,7 +45,7 @@ module Gitlab
private private
def get_rss def get_rss
output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{pid})) output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{pid}), Rails.root.to_s)
return 0 unless status.zero? return 0 unless status.zero?
output.to_i output.to_i
......
...@@ -161,6 +161,18 @@ module Gitlab ...@@ -161,6 +161,18 @@ module Gitlab
] ]
end end
def send_url(url, allow_redirects: false)
params = {
'URL' => url,
'AllowRedirects' => allow_redirects
}
[
SEND_DATA_HEADER,
"send-url:#{encode(params)}"
]
end
def terminal_websocket(terminal) def terminal_websocket(terminal)
details = { details = {
'Terminal' => { 'Terminal' => {
......
This diff is collapsed.
...@@ -177,7 +177,7 @@ describe Projects::ClustersController do ...@@ -177,7 +177,7 @@ describe Projects::ClustersController do
cluster.reload cluster.reload
expect(response).to redirect_to(project_cluster_path(project, cluster)) expect(response).to redirect_to(project_cluster_path(project, cluster))
expect(flash[:notice]).to eq('Cluster was successfully updated.') expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.')
expect(cluster.enabled).to be_falsey expect(cluster.enabled).to be_falsey
end end
...@@ -276,7 +276,7 @@ describe Projects::ClustersController do ...@@ -276,7 +276,7 @@ describe Projects::ClustersController do
cluster.reload cluster.reload
expect(response).to redirect_to(project_cluster_path(project, cluster)) expect(response).to redirect_to(project_cluster_path(project, cluster))
expect(flash[:notice]).to eq('Cluster was successfully updated.') expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.')
expect(cluster.enabled).to be_falsey expect(cluster.enabled).to be_falsey
expect(cluster.name).to eq('my-new-cluster-name') expect(cluster.name).to eq('my-new-cluster-name')
expect(cluster.platform_kubernetes.namespace).to eq('my-namespace') expect(cluster.platform_kubernetes.namespace).to eq('my-namespace')
...@@ -336,7 +336,7 @@ describe Projects::ClustersController do ...@@ -336,7 +336,7 @@ describe Projects::ClustersController do
.and change { Clusters::Providers::Gcp.count }.by(-1) .and change { Clusters::Providers::Gcp.count }.by(-1)
expect(response).to redirect_to(project_clusters_path(project)) expect(response).to redirect_to(project_clusters_path(project))
expect(flash[:notice]).to eq('Cluster integration was successfully removed.') expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.')
end end
end end
...@@ -349,7 +349,7 @@ describe Projects::ClustersController do ...@@ -349,7 +349,7 @@ describe Projects::ClustersController do
.and change { Clusters::Providers::Gcp.count }.by(-1) .and change { Clusters::Providers::Gcp.count }.by(-1)
expect(response).to redirect_to(project_clusters_path(project)) expect(response).to redirect_to(project_clusters_path(project))
expect(flash[:notice]).to eq('Cluster integration was successfully removed.') expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.')
end end
end end
end end
...@@ -364,7 +364,7 @@ describe Projects::ClustersController do ...@@ -364,7 +364,7 @@ describe Projects::ClustersController do
.and change { Clusters::Providers::Gcp.count }.by(0) .and change { Clusters::Providers::Gcp.count }.by(0)
expect(response).to redirect_to(project_clusters_path(project)) expect(response).to redirect_to(project_clusters_path(project))
expect(flash[:notice]).to eq('Cluster integration was successfully removed.') expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.')
end end
end end
end end
......
...@@ -159,8 +159,19 @@ describe Projects::JobsController do ...@@ -159,8 +159,19 @@ describe Projects::JobsController do
get_trace get_trace
end end
context 'when job has a trace artifact' do
let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
it 'returns a trace' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq job.id
expect(json_response['status']).to eq job.status
expect(json_response['html']).to eq(job.trace.html)
end
end
context 'when job has a trace' do context 'when job has a trace' do
let(:job) { create(:ci_build, :trace, pipeline: pipeline) } let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) }
it 'returns a trace' do it 'returns a trace' do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
...@@ -182,7 +193,7 @@ describe Projects::JobsController do ...@@ -182,7 +193,7 @@ describe Projects::JobsController do
end end
context 'when job has a trace with ANSI sequence and Unicode' do context 'when job has a trace with ANSI sequence and Unicode' do
let(:job) { create(:ci_build, :unicode_trace, pipeline: pipeline) } let(:job) { create(:ci_build, :unicode_trace_live, pipeline: pipeline) }
it 'returns a trace with Unicode' do it 'returns a trace with Unicode' do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
...@@ -381,7 +392,7 @@ describe Projects::JobsController do ...@@ -381,7 +392,7 @@ describe Projects::JobsController do
end end
context 'when job is erasable' do context 'when job is erasable' do
let(:job) { create(:ci_build, :erasable, :trace, pipeline: pipeline) } let(:job) { create(:ci_build, :erasable, :trace_artifact, pipeline: pipeline) }
it 'redirects to the erased job page' do it 'redirects to the erased job page' do
expect(response).to have_gitlab_http_status(:found) expect(response).to have_gitlab_http_status(:found)
...@@ -408,7 +419,7 @@ describe Projects::JobsController do ...@@ -408,7 +419,7 @@ describe Projects::JobsController do
context 'when user is developer' do context 'when user is developer' do
let(:role) { :developer } let(:role) { :developer }
let(:job) { create(:ci_build, :erasable, :trace, pipeline: pipeline, user: triggered_by) } let(:job) { create(:ci_build, :erasable, :trace_artifact, pipeline: pipeline, user: triggered_by) }
context 'when triggered by same user' do context 'when triggered by same user' do
let(:triggered_by) { user } let(:triggered_by) { user }
...@@ -439,8 +450,18 @@ describe Projects::JobsController do ...@@ -439,8 +450,18 @@ describe Projects::JobsController do
get_raw get_raw
end end
context 'when job has a trace artifact' do
let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
it 'returns a trace' do
expect(response).to have_gitlab_http_status(:ok)
expect(response.content_type).to eq 'text/plain; charset=utf-8'
expect(response.body).to eq job.job_artifacts_trace.open.read
end
end
context 'when job has a trace file' do context 'when job has a trace file' do
let(:job) { create(:ci_build, :trace, pipeline: pipeline) } let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) }
it 'send a trace file' do it 'send a trace file' do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
......
...@@ -135,13 +135,19 @@ FactoryBot.define do ...@@ -135,13 +135,19 @@ FactoryBot.define do
coverage_regex '/(d+)/' coverage_regex '/(d+)/'
end end
trait :trace do trait :trace_live do
after(:create) do |build, evaluator| after(:create) do |build, evaluator|
build.trace.set('BUILD TRACE') build.trace.set('BUILD TRACE')
end end
end end
trait :unicode_trace do trait :trace_artifact do
after(:create) do |build, evaluator|
create(:ci_job_artifact, :trace, job: build)
end
end
trait :unicode_trace_live do
after(:create) do |build, evaluator| after(:create) do |build, evaluator|
trace = File.binread( trace = File.binread(
File.expand_path( File.expand_path(
......
...@@ -26,5 +26,14 @@ FactoryBot.define do ...@@ -26,5 +26,14 @@ FactoryBot.define do
Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip') Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip')
end end
end end
trait :trace do
file_type :trace
after(:build) do |artifact, evaluator|
artifact.file = fixture_file_upload(
Rails.root.join('spec/fixtures/trace/sample_trace'), 'text/plain')
end
end
end end
end end
...@@ -64,7 +64,7 @@ feature 'Clusters Applications', :js do ...@@ -64,7 +64,7 @@ feature 'Clusters Applications', :js do
expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installed') expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installed')
end end
expect(page).to have_content('Helm Tiller was successfully installed on your cluster') expect(page).to have_content('Helm Tiller was successfully installed on your Kubernetes cluster')
end end
end end
...@@ -98,7 +98,7 @@ feature 'Clusters Applications', :js do ...@@ -98,7 +98,7 @@ feature 'Clusters Applications', :js do
expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installed') expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installed')
end end
expect(page).to have_content('Ingress was successfully installed on your cluster') expect(page).to have_content('Ingress was successfully installed on your Kubernetes cluster')
end end
end end
end end
......
...@@ -32,7 +32,7 @@ feature 'Gcp Cluster', :js do ...@@ -32,7 +32,7 @@ feature 'Gcp Cluster', :js do
before do before do
visit project_clusters_path(project) visit project_clusters_path(project)
click_link 'Add cluster' click_link 'Add Kubernetes cluster'
click_link 'Create on GKE' click_link 'Create on GKE'
end end
...@@ -50,19 +50,19 @@ feature 'Gcp Cluster', :js do ...@@ -50,19 +50,19 @@ feature 'Gcp Cluster', :js do
fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123' fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
fill_in 'cluster_name', with: 'dev-cluster' fill_in 'cluster_name', with: 'dev-cluster'
click_button 'Create cluster' click_button 'Create Kubernetes cluster'
end end
it 'user sees a cluster details page and creation status' do it 'user sees a cluster details page and creation status' do
expect(page).to have_content('Cluster is being created on Google Kubernetes Engine...') expect(page).to have_content('Kubernetes cluster is being created on Google Kubernetes Engine...')
Clusters::Cluster.last.provider.make_created! Clusters::Cluster.last.provider.make_created!
expect(page).to have_content('Cluster was successfully created on Google Kubernetes Engine') expect(page).to have_content('Kubernetes cluster was successfully created on Google Kubernetes Engine')
end end
it 'user sees a error if something worng during creation' do it 'user sees a error if something worng during creation' do
expect(page).to have_content('Cluster is being created on Google Kubernetes Engine...') expect(page).to have_content('Kubernetes cluster is being created on Google Kubernetes Engine...')
Clusters::Cluster.last.provider.make_errored!('Something wrong!') Clusters::Cluster.last.provider.make_errored!('Something wrong!')
...@@ -72,7 +72,7 @@ feature 'Gcp Cluster', :js do ...@@ -72,7 +72,7 @@ feature 'Gcp Cluster', :js do
context 'when user filled form with invalid parameters' do context 'when user filled form with invalid parameters' do
before do before do
click_button 'Create cluster' click_button 'Create Kubernetes cluster'
end end
it 'user sees a validation error' do it 'user sees a validation error' do
...@@ -100,7 +100,7 @@ feature 'Gcp Cluster', :js do ...@@ -100,7 +100,7 @@ feature 'Gcp Cluster', :js do
end end
it 'user sees the successful message' do it 'user sees the successful message' do
expect(page).to have_content('Cluster was successfully updated.') expect(page).to have_content('Kubernetes cluster was successfully updated.')
end end
end end
...@@ -111,7 +111,7 @@ feature 'Gcp Cluster', :js do ...@@ -111,7 +111,7 @@ feature 'Gcp Cluster', :js do
end end
it 'user sees the successful message' do it 'user sees the successful message' do
expect(page).to have_content('Cluster was successfully updated.') expect(page).to have_content('Kubernetes cluster was successfully updated.')
expect(cluster.reload.platform_kubernetes.namespace).to eq('my-namespace') expect(cluster.reload.platform_kubernetes.namespace).to eq('my-namespace')
end end
end end
...@@ -124,8 +124,8 @@ feature 'Gcp Cluster', :js do ...@@ -124,8 +124,8 @@ feature 'Gcp Cluster', :js do
end end
it 'user sees creation form with the successful message' do it 'user sees creation form with the successful message' do
expect(page).to have_content('Cluster integration was successfully removed.') expect(page).to have_content('Kubernetes cluster integration was successfully removed.')
expect(page).to have_link('Add cluster') expect(page).to have_link('Add Kubernetes cluster')
end end
end end
end end
...@@ -138,16 +138,16 @@ feature 'Gcp Cluster', :js do ...@@ -138,16 +138,16 @@ feature 'Gcp Cluster', :js do
visit project_clusters_path(project) visit project_clusters_path(project)
click_link 'Add cluster' click_link 'Add Kubernetes cluster'
click_link 'Create on GKE' click_link 'Create on GKE'
fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123' fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
fill_in 'cluster_name', with: 'dev-cluster' fill_in 'cluster_name', with: 'dev-cluster'
click_button 'Create cluster' click_button 'Create Kubernetes cluster'
end end
it 'user sees form with error' do it 'user sees form with error' do
expect(page).to have_content('Please enable billing for one of your projects to be able to create a cluster, then try again.') expect(page).to have_content('Please enable billing for one of your projects to be able to create a Kubernetes cluster, then try again.')
end end
end end
...@@ -158,12 +158,12 @@ feature 'Gcp Cluster', :js do ...@@ -158,12 +158,12 @@ feature 'Gcp Cluster', :js do
visit project_clusters_path(project) visit project_clusters_path(project)
click_link 'Add cluster' click_link 'Add Kubernetes cluster'
click_link 'Create on GKE' click_link 'Create on GKE'
fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123' fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
fill_in 'cluster_name', with: 'dev-cluster' fill_in 'cluster_name', with: 'dev-cluster'
click_button 'Create cluster' click_button 'Create Kubernetes cluster'
end end
it 'user sees form with error' do it 'user sees form with error' do
...@@ -176,7 +176,7 @@ feature 'Gcp Cluster', :js do ...@@ -176,7 +176,7 @@ feature 'Gcp Cluster', :js do
before do before do
visit project_clusters_path(project) visit project_clusters_path(project)
click_link 'Add cluster' click_link 'Add Kubernetes cluster'
click_link 'Create on GKE' click_link 'Create on GKE'
end end
......
...@@ -16,8 +16,8 @@ feature 'User Cluster', :js do ...@@ -16,8 +16,8 @@ feature 'User Cluster', :js do
before do before do
visit project_clusters_path(project) visit project_clusters_path(project)
click_link 'Add cluster' click_link 'Add Kubernetes cluster'
click_link 'Add an existing cluster' click_link 'Add an existing Kubernetes cluster'
end end
context 'when user filled form with valid parameters' do context 'when user filled form with valid parameters' do
...@@ -25,11 +25,11 @@ feature 'User Cluster', :js do ...@@ -25,11 +25,11 @@ feature 'User Cluster', :js do
fill_in 'cluster_name', with: 'dev-cluster' fill_in 'cluster_name', with: 'dev-cluster'
fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'http://example.com' fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'http://example.com'
fill_in 'cluster_platform_kubernetes_attributes_token', with: 'my-token' fill_in 'cluster_platform_kubernetes_attributes_token', with: 'my-token'
click_button 'Add cluster' click_button 'Add Kubernetes cluster'
end end
it 'user sees a cluster details page' do it 'user sees a cluster details page' do
expect(page).to have_content('Cluster integration') expect(page).to have_content('Kubernetes cluster integration')
expect(page.find_field('cluster[name]').value).to eq('dev-cluster') expect(page.find_field('cluster[name]').value).to eq('dev-cluster')
expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value) expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value)
.to have_content('http://example.com') .to have_content('http://example.com')
...@@ -40,7 +40,7 @@ feature 'User Cluster', :js do ...@@ -40,7 +40,7 @@ feature 'User Cluster', :js do
context 'when user filled form with invalid parameters' do context 'when user filled form with invalid parameters' do
before do before do
click_button 'Add cluster' click_button 'Add Kubernetes cluster'
end end
it 'user sees a validation error' do it 'user sees a validation error' do
...@@ -68,7 +68,7 @@ feature 'User Cluster', :js do ...@@ -68,7 +68,7 @@ feature 'User Cluster', :js do
end end
it 'user sees the successful message' do it 'user sees the successful message' do
expect(page).to have_content('Cluster was successfully updated.') expect(page).to have_content('Kubernetes cluster was successfully updated.')
end end
end end
...@@ -80,7 +80,7 @@ feature 'User Cluster', :js do ...@@ -80,7 +80,7 @@ feature 'User Cluster', :js do
end end
it 'user sees the successful message' do it 'user sees the successful message' do
expect(page).to have_content('Cluster was successfully updated.') expect(page).to have_content('Kubernetes cluster was successfully updated.')
expect(cluster.reload.name).to eq('my-dev-cluster') expect(cluster.reload.name).to eq('my-dev-cluster')
expect(cluster.reload.platform_kubernetes.namespace).to eq('my-namespace') expect(cluster.reload.platform_kubernetes.namespace).to eq('my-namespace')
end end
...@@ -94,8 +94,8 @@ feature 'User Cluster', :js do ...@@ -94,8 +94,8 @@ feature 'User Cluster', :js do
end end
it 'user sees creation form with the successful message' do it 'user sees creation form with the successful message' do
expect(page).to have_content('Cluster integration was successfully removed.') expect(page).to have_content('Kubernetes cluster integration was successfully removed.')
expect(page).to have_link('Add cluster') expect(page).to have_link('Add Kubernetes cluster')
end end
end end
end end
......
...@@ -17,7 +17,7 @@ feature 'Clusters', :js do ...@@ -17,7 +17,7 @@ feature 'Clusters', :js do
end end
it 'sees empty state' do it 'sees empty state' do
expect(page).to have_link('Add cluster') expect(page).to have_link('Add Kubernetes cluster')
expect(page).to have_selector('.empty-state') expect(page).to have_selector('.empty-state')
end end
end end
...@@ -82,7 +82,7 @@ feature 'Clusters', :js do ...@@ -82,7 +82,7 @@ feature 'Clusters', :js do
before do before do
visit project_clusters_path(project) visit project_clusters_path(project)
click_link 'Add cluster' click_link 'Add Kubernetes cluster'
click_link 'Create on GKE' click_link 'Create on GKE'
end end
......
...@@ -7,7 +7,7 @@ feature 'Jobs' do ...@@ -7,7 +7,7 @@ feature 'Jobs' do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project) } let(:pipeline) { create(:ci_pipeline, project: project) }
let(:job) { create(:ci_build, :trace, pipeline: pipeline) } let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) }
let(:job2) { create(:ci_build) } let(:job2) { create(:ci_build) }
let(:artifacts_file) do let(:artifacts_file) do
...@@ -490,18 +490,34 @@ feature 'Jobs' do ...@@ -490,18 +490,34 @@ feature 'Jobs' do
describe 'GET /:project/jobs/:id/raw', :js do describe 'GET /:project/jobs/:id/raw', :js do
context 'access source' do context 'access source' do
context 'job from project' do context 'job from project' do
before do context 'when job is running' do
job.run! before do
end job.run!
end
it 'sends the right headers' do it 'sends the right headers' do
requests = inspect_requests(inject_headers: { 'X-Sendfile-Type' => 'X-Sendfile' }) do requests = inspect_requests(inject_headers: { 'X-Sendfile-Type' => 'X-Sendfile' }) do
visit raw_project_job_path(project, job) visit raw_project_job_path(project, job)
end
expect(requests.first.status_code).to eq(200)
expect(requests.first.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
expect(requests.first.response_headers['X-Sendfile']).to eq(job.trace.send(:current_path))
end end
end
expect(requests.first.status_code).to eq(200) context 'when job is complete' do
expect(requests.first.response_headers['Content-Type']).to eq('text/plain; charset=utf-8') let(:job) { create(:ci_build, :success, :trace_artifact, pipeline: pipeline) }
expect(requests.first.response_headers['X-Sendfile']).to eq(job.trace.send(:current_path))
it 'sends the right headers' do
requests = inspect_requests(inject_headers: { 'X-Sendfile-Type' => 'X-Sendfile' }) do
visit raw_project_job_path(project, job)
end
expect(requests.first.status_code).to eq(200)
expect(requests.first.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
expect(requests.first.response_headers['X-Sendfile']).to eq(job.job_artifacts_trace.file.path)
end
end end
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.
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