Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
93c6764dac
commit
cfc792b9ca
174 changed files with 4405 additions and 131141 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1,2 +1,3 @@
|
|||
VERSION merge=ours
|
||||
Dangerfile gitlab-language=ruby
|
||||
*.pdf filter=lfs diff=lfs merge=lfs -text
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-golang-1.12-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33"
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-golang-1.12-git-2.22-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33"
|
||||
|
||||
stages:
|
||||
- sync
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
- .default-before_script
|
||||
- .assets-compile-cache
|
||||
- .only:changes-code-backstage-qa
|
||||
image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-git-2.22-chrome-73.0-node-12.x-yarn-1.16-graphicsmagick-1.3.33-docker-19.03.1
|
||||
image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-git-2.22-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-graphicsmagick-1.3.33-docker-19.03.1
|
||||
stage: test
|
||||
dependencies: ["setup-test-env"]
|
||||
needs: ["setup-test-env"]
|
||||
|
|
|
@ -202,7 +202,7 @@
|
|||
- name: redis:alpine
|
||||
|
||||
.use-pg10:
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-golang-1.12-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-10-graphicsmagick-1.3.33"
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-golang-1.12-git-2.22-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-postgresql-10-graphicsmagick-1.3.33"
|
||||
services:
|
||||
- name: postgres:10.9
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
|
@ -216,7 +216,7 @@
|
|||
- name: docker.elastic.co/elasticsearch/elasticsearch:5.6.12
|
||||
|
||||
.use-pg10-ee:
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-golang-1.12-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-10-graphicsmagick-1.3.33"
|
||||
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-golang-1.12-git-2.22-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-postgresql-10-graphicsmagick-1.3.33"
|
||||
services:
|
||||
- name: postgres:10.9
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
|
|
|
@ -1 +1 @@
|
|||
8.14.1
|
||||
8.17.0
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import _ from 'underscore';
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import { GlButton, GlTooltipDirective, GlTooltip, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { polyfillSticky, stickyMonitor } from '~/lib/utils/sticky';
|
||||
import { polyfillSticky } from '~/lib/utils/sticky';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import FileIcon from '~/vue_shared/components/file_icon.vue';
|
||||
|
@ -11,7 +11,7 @@ import { __, s__, sprintf } from '~/locale';
|
|||
import { diffViewerModes } from '~/ide/constants';
|
||||
import EditButton from './edit_button.vue';
|
||||
import DiffStats from './diff_stats.vue';
|
||||
import { scrollToElement, contentTop } from '~/lib/utils/common_utils';
|
||||
import { scrollToElement } from '~/lib/utils/common_utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -127,8 +127,6 @@ export default {
|
|||
},
|
||||
mounted() {
|
||||
polyfillSticky(this.$refs.header);
|
||||
const fileHeaderHeight = this.$refs.header.clientHeight;
|
||||
stickyMonitor(this.$refs.header, contentTop() - fileHeaderHeight - 1, false);
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', [
|
||||
|
|
|
@ -288,7 +288,7 @@
|
|||
list-style: none;
|
||||
padding: 0 1px;
|
||||
|
||||
a:not(.help-link),
|
||||
> a,
|
||||
button,
|
||||
.menu-item {
|
||||
@include dropdown-link;
|
||||
|
|
|
@ -334,10 +334,6 @@ span.idiff {
|
|||
padding: $gl-padding-8 $gl-padding;
|
||||
margin: 0;
|
||||
border-radius: $border-radius-default $border-radius-default 0 0;
|
||||
|
||||
&.is-stuck {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.file-header-content {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
.file-title-flex-parent {
|
||||
border-top-left-radius: $border-radius-default;
|
||||
border-top-right-radius: $border-radius-default;
|
||||
box-shadow: 0 -2px 0 0 var(--white);
|
||||
cursor: pointer;
|
||||
|
||||
@media (min-width: map-get($grid-breakpoints, md)) {
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
.border-color-default { border-color: $border-color; }
|
||||
.box-shadow-default { box-shadow: 0 2px 4px 0 $black-transparent; }
|
||||
|
||||
.mh-50vh { max-height: 50vh; }
|
||||
|
||||
.gl-w-64 { width: px-to-rem($grid-size * 8); }
|
||||
.gl-h-64 { height: px-to-rem($grid-size * 8); }
|
||||
.gl-bg-blue-500 { @include gl-bg-blue-500; }
|
||||
|
|
|
@ -7,14 +7,15 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
|||
before_action :authorize_read_environment!
|
||||
before_action :authorize_create_environment!, only: [:new, :create]
|
||||
before_action :authorize_stop_environment!, only: [:stop]
|
||||
before_action :authorize_update_environment!, only: [:edit, :update]
|
||||
before_action :authorize_update_environment!, only: [:edit, :update, :cancel_auto_stop]
|
||||
before_action :authorize_admin_environment!, only: [:terminal, :terminal_websocket_authorize]
|
||||
before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics]
|
||||
before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics, :cancel_auto_stop]
|
||||
before_action :verify_api_request!, only: :terminal_websocket_authorize
|
||||
before_action :expire_etag_cache, only: [:index]
|
||||
before_action :expire_etag_cache, only: [:index], unless: -> { request.format.json? }
|
||||
before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do
|
||||
push_frontend_feature_flag(:prometheus_computed_alerts)
|
||||
end
|
||||
after_action :expire_etag_cache, only: [:cancel_auto_stop]
|
||||
|
||||
def index
|
||||
@environments = project.environments
|
||||
|
@ -104,6 +105,27 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def cancel_auto_stop
|
||||
result = Environments::ResetAutoStopService.new(project, current_user)
|
||||
.execute(environment)
|
||||
|
||||
if result[:status] == :success
|
||||
respond_to do |format|
|
||||
message = _('Auto stop successfully canceled.')
|
||||
|
||||
format.html { redirect_back_or_default(default: { action: 'show' }, options: { notice: message }) }
|
||||
format.json { render json: { message: message }, status: :ok }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
message = result[:message]
|
||||
|
||||
format.html { redirect_back_or_default(default: { action: 'show' }, options: { alert: message }) }
|
||||
format.json { render json: { message: message }, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def terminal
|
||||
# Currently, this acts as a hint to load the terminal details into the cache
|
||||
# if they aren't there already. In the future, users will need these details
|
||||
|
@ -175,8 +197,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def expire_etag_cache
|
||||
return if request.format.json?
|
||||
|
||||
# this forces to reload json content
|
||||
Gitlab::EtagCaching::Store.new.tap do |store|
|
||||
store.touch(project_environments_path(project, format: :json))
|
||||
|
@ -222,6 +242,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
|||
def authorize_stop_environment!
|
||||
access_denied! unless can?(current_user, :stop_environment, environment)
|
||||
end
|
||||
|
||||
def authorize_update_environment!
|
||||
access_denied! unless can?(current_user, :update_environment, environment)
|
||||
end
|
||||
end
|
||||
|
||||
Projects::EnvironmentsController.prepend_if_ee('EE::Projects::EnvironmentsController')
|
||||
|
|
|
@ -8,7 +8,6 @@ class Projects::PagesDomainsController < Projects::ApplicationController
|
|||
before_action :domain, except: [:new, :create]
|
||||
|
||||
def show
|
||||
redirect_to edit_project_pages_domain_path(@project, @domain)
|
||||
end
|
||||
|
||||
def new
|
||||
|
@ -24,17 +23,18 @@ class Projects::PagesDomainsController < Projects::ApplicationController
|
|||
flash[:alert] = 'Failed to verify domain ownership'
|
||||
end
|
||||
|
||||
redirect_to edit_project_pages_domain_path(@project, @domain)
|
||||
redirect_to project_pages_domain_path(@project, @domain)
|
||||
end
|
||||
|
||||
def edit
|
||||
redirect_to project_pages_domain_path(@project, @domain)
|
||||
end
|
||||
|
||||
def create
|
||||
@domain = @project.pages_domains.create(create_params)
|
||||
|
||||
if @domain.valid?
|
||||
redirect_to edit_project_pages_domain_path(@project, @domain)
|
||||
redirect_to project_pages_domain_path(@project, @domain)
|
||||
else
|
||||
render 'new'
|
||||
end
|
||||
|
@ -46,7 +46,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
|
|||
status: :found,
|
||||
notice: 'Domain was updated'
|
||||
else
|
||||
render 'edit'
|
||||
render 'show'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -68,7 +68,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
|
|||
flash[:alert] = @domain.errors.full_messages.join(', ')
|
||||
end
|
||||
|
||||
redirect_to edit_project_pages_domain_path(@project, @domain)
|
||||
redirect_to project_pages_domain_path(@project, @domain)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -13,7 +13,7 @@ module Projects
|
|||
Projects::UpdateService.new(project, current_user, update_params).tap do |service|
|
||||
result = service.execute
|
||||
if result[:status] == :success
|
||||
flash[:notice] = _("Pipelines settings for '%{project_name}' were successfully updated.") % { project_name: @project.name }
|
||||
flash[:toast] = _("Pipelines settings for '%{project_name}' were successfully updated.") % { project_name: @project.name }
|
||||
|
||||
run_autodevops_pipeline(service)
|
||||
|
||||
|
@ -39,7 +39,7 @@ module Projects
|
|||
def reset_registration_token
|
||||
@project.reset_runners_token!
|
||||
|
||||
flash[:notice] = _('New runners registration token has been generated!')
|
||||
flash[:toast] = _("New runners registration token has been generated!")
|
||||
redirect_to namespace_project_settings_ci_cd_path
|
||||
end
|
||||
|
||||
|
@ -65,12 +65,14 @@ module Projects
|
|||
return unless service.run_auto_devops_pipeline?
|
||||
|
||||
if @project.empty_repo?
|
||||
flash[:warning] = _("This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch.")
|
||||
flash[:notice] = _("This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch.")
|
||||
return
|
||||
end
|
||||
|
||||
CreatePipelineWorker.perform_async(project.id, current_user.id, project.default_branch, :web, ignore_skip_ci: true, save_on_errors: false)
|
||||
flash[:success] = "A new Auto DevOps pipeline has been created, go to <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details".html_safe
|
||||
|
||||
pipelines_link_start = '<a href="%{url}">'.html_safe % { url: project_pipelines_path(@project) }
|
||||
flash[:toast] = _("A new Auto DevOps pipeline has been created, go to %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details") % { pipelines_link_start: pipelines_link_start, pipelines_link_end: "</a>".html_safe }
|
||||
end
|
||||
|
||||
def define_variables
|
||||
|
|
57
app/graphql/resolvers/concerns/resolves_snippets.rb
Normal file
57
app/graphql/resolvers/concerns/resolves_snippets.rb
Normal file
|
@ -0,0 +1,57 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ResolvesSnippets
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
type Types::SnippetType, null: false
|
||||
|
||||
argument :ids, [GraphQL::ID_TYPE],
|
||||
required: false,
|
||||
description: 'Array of global snippet ids, e.g., "gid://gitlab/ProjectSnippet/1"'
|
||||
|
||||
argument :visibility, Types::Snippets::VisibilityScopesEnum,
|
||||
required: false,
|
||||
description: 'The visibility of the snippet'
|
||||
end
|
||||
|
||||
def resolve(**args)
|
||||
resolve_snippets(args)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resolve_snippets(args)
|
||||
SnippetsFinder.new(context[:current_user], snippet_finder_params(args)).execute
|
||||
end
|
||||
|
||||
def snippet_finder_params(args)
|
||||
{
|
||||
ids: resolve_ids(args[:ids]),
|
||||
scope: args[:visibility]
|
||||
}.merge(options_by_type(args[:type]))
|
||||
end
|
||||
|
||||
def resolve_ids(ids)
|
||||
Array.wrap(ids).map { |id| resolve_gid(id, :id) }
|
||||
end
|
||||
|
||||
def resolve_gid(gid, argument)
|
||||
return unless gid.present?
|
||||
|
||||
GlobalID.parse(gid)&.model_id.tap do |id|
|
||||
raise Gitlab::Graphql::Errors::ArgumentError, "Invalid global id format for param #{argument}" if id.nil?
|
||||
end
|
||||
end
|
||||
|
||||
def options_by_type(type)
|
||||
case type
|
||||
when 'personal'
|
||||
{ only_personal: true }
|
||||
when 'project'
|
||||
{ only_project: true }
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
23
app/graphql/resolvers/projects/snippets_resolver.rb
Normal file
23
app/graphql/resolvers/projects/snippets_resolver.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
module Projects
|
||||
class SnippetsResolver < BaseResolver
|
||||
include ResolvesSnippets
|
||||
|
||||
alias_method :project, :object
|
||||
|
||||
def resolve(**args)
|
||||
return Snippet.none if project.nil?
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def snippet_finder_params(args)
|
||||
super.merge(project: project)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
45
app/graphql/resolvers/snippets_resolver.rb
Normal file
45
app/graphql/resolvers/snippets_resolver.rb
Normal file
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
class SnippetsResolver < BaseResolver
|
||||
include ResolvesSnippets
|
||||
|
||||
ERROR_MESSAGE = 'Filtering by both an author and a project is not supported'
|
||||
|
||||
alias_method :user, :object
|
||||
|
||||
argument :author_id, GraphQL::ID_TYPE,
|
||||
required: false,
|
||||
description: 'The ID of an author'
|
||||
|
||||
argument :project_id, GraphQL::ID_TYPE,
|
||||
required: false,
|
||||
description: 'The ID of a project'
|
||||
|
||||
argument :type, Types::Snippets::TypeEnum,
|
||||
required: false,
|
||||
description: 'The type of snippet'
|
||||
|
||||
argument :explore,
|
||||
GraphQL::BOOLEAN_TYPE,
|
||||
required: false,
|
||||
description: 'Explore personal snippets'
|
||||
|
||||
def resolve(**args)
|
||||
if args[:author_id].present? && args[:project_id].present?
|
||||
raise Gitlab::Graphql::Errors::ArgumentError, ERROR_MESSAGE
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def snippet_finder_params(args)
|
||||
super
|
||||
.merge(author: resolve_gid(args[:author_id], :author),
|
||||
project: resolve_gid(args[:project_id], :project),
|
||||
explore: args[:explore])
|
||||
end
|
||||
end
|
||||
end
|
21
app/graphql/resolvers/users/snippets_resolver.rb
Normal file
21
app/graphql/resolvers/users/snippets_resolver.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
module Users
|
||||
class SnippetsResolver < BaseResolver
|
||||
include ResolvesSnippets
|
||||
|
||||
alias_method :user, :object
|
||||
|
||||
argument :type, Types::Snippets::TypeEnum,
|
||||
required: false,
|
||||
description: 'The type of snippet'
|
||||
|
||||
private
|
||||
|
||||
def snippet_finder_params(args)
|
||||
super.merge(author: user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -15,6 +15,8 @@ module Types
|
|||
Types::IssueType
|
||||
when MergeRequest
|
||||
Types::MergeRequestType
|
||||
when Snippet
|
||||
Types::SnippetType
|
||||
else
|
||||
raise "Unknown GraphQL type for #{object}"
|
||||
end
|
||||
|
|
|
@ -10,13 +10,19 @@ module Types
|
|||
:remove_pages, :read_project, :create_merge_request_in,
|
||||
:read_wiki, :read_project_member, :create_issue, :upload_file,
|
||||
:read_cycle_analytics, :download_code, :download_wiki_code,
|
||||
:fork_project, :create_project_snippet, :read_commit_status,
|
||||
:fork_project, :read_commit_status,
|
||||
:request_access, :create_pipeline, :create_pipeline_schedule,
|
||||
:create_merge_request_from, :create_wiki, :push_code,
|
||||
:create_deployment, :push_to_delete_protected_branch,
|
||||
:admin_wiki, :admin_project, :update_pages,
|
||||
:admin_remote_mirror, :create_label, :update_wiki, :destroy_wiki,
|
||||
:create_pages, :destroy_pages, :read_pages_content, :admin_operations
|
||||
|
||||
permission_field :create_snippet
|
||||
|
||||
def create_snippet
|
||||
Ability.allowed?(context[:current_user], :create_project_snippet, object)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
15
app/graphql/types/permission_types/snippet.rb
Normal file
15
app/graphql/types/permission_types/snippet.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module PermissionTypes
|
||||
class Snippet < BasePermissionType
|
||||
graphql_name 'SnippetPermissions'
|
||||
|
||||
abilities :create_note, :award_emoji
|
||||
|
||||
permission_field :read_snippet, method: :can_read_snippet?
|
||||
permission_field :update_snippet, method: :can_update_snippet?
|
||||
permission_field :admin_snippet, method: :can_admin_snippet?
|
||||
end
|
||||
end
|
||||
end
|
15
app/graphql/types/permission_types/user.rb
Normal file
15
app/graphql/types/permission_types/user.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module PermissionTypes
|
||||
class User < BasePermissionType
|
||||
graphql_name 'UserPermissions'
|
||||
|
||||
permission_field :create_snippet
|
||||
|
||||
def create_snippet
|
||||
Ability.allowed?(context[:current_user], :create_personal_snippet)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -151,5 +151,11 @@ module Types
|
|||
null: true,
|
||||
description: 'Detailed version of a Sentry error on the project',
|
||||
resolver: Resolvers::ErrorTracking::SentryDetailedErrorResolver
|
||||
|
||||
field :snippets,
|
||||
Types::SnippetType.connection_type,
|
||||
null: true,
|
||||
description: 'Snippets of the project',
|
||||
resolver: Resolvers::Projects::SnippetsResolver
|
||||
end
|
||||
end
|
||||
|
|
|
@ -29,6 +29,12 @@ module Types
|
|||
resolver: Resolvers::MetadataResolver,
|
||||
description: 'Metadata about GitLab'
|
||||
|
||||
field :snippets,
|
||||
Types::SnippetType.connection_type,
|
||||
null: true,
|
||||
resolver: Resolvers::SnippetsResolver,
|
||||
description: 'Find Snippets visible to the current user'
|
||||
|
||||
field :echo, GraphQL::STRING_TYPE, null: false, resolver: Resolvers::EchoResolver # rubocop:disable Graphql/Descriptions
|
||||
end
|
||||
end
|
||||
|
|
69
app/graphql/types/snippet_type.rb
Normal file
69
app/graphql/types/snippet_type.rb
Normal file
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
class SnippetType < BaseObject
|
||||
graphql_name 'Snippet'
|
||||
description 'Represents a snippet entry'
|
||||
|
||||
implements(Types::Notes::NoteableType)
|
||||
|
||||
present_using SnippetPresenter
|
||||
|
||||
authorize :read_snippet
|
||||
|
||||
expose_permissions Types::PermissionTypes::Snippet
|
||||
|
||||
field :id, GraphQL::ID_TYPE,
|
||||
description: 'Id of the snippet',
|
||||
null: false
|
||||
|
||||
field :title, GraphQL::STRING_TYPE,
|
||||
description: 'Title of the snippet',
|
||||
null: false
|
||||
|
||||
field :project, Types::ProjectType,
|
||||
description: 'The project the snippet is associated with',
|
||||
null: true,
|
||||
authorize: :read_project,
|
||||
resolve: -> (snippet, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, snippet.project_id).find }
|
||||
|
||||
field :author, Types::UserType,
|
||||
description: 'The owner of the snippet',
|
||||
null: false,
|
||||
resolve: -> (snippet, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, snippet.author_id).find }
|
||||
|
||||
field :file_name, GraphQL::STRING_TYPE,
|
||||
description: 'File Name of the snippet',
|
||||
null: true
|
||||
|
||||
field :content, GraphQL::STRING_TYPE,
|
||||
description: 'Content of the snippet',
|
||||
null: false
|
||||
|
||||
field :description, GraphQL::STRING_TYPE,
|
||||
description: 'Description of the snippet',
|
||||
null: true
|
||||
|
||||
field :visibility, GraphQL::STRING_TYPE,
|
||||
description: 'Visibility of the snippet',
|
||||
null: false
|
||||
|
||||
field :created_at, Types::TimeType,
|
||||
description: 'Timestamp this snippet was created',
|
||||
null: false
|
||||
|
||||
field :updated_at, Types::TimeType,
|
||||
description: 'Timestamp this snippet was updated',
|
||||
null: false
|
||||
|
||||
field :web_url, type: GraphQL::STRING_TYPE,
|
||||
description: 'Web URL of the snippet',
|
||||
null: false
|
||||
|
||||
field :raw_url, type: GraphQL::STRING_TYPE,
|
||||
description: 'Raw URL of the snippet',
|
||||
null: false
|
||||
|
||||
markdown_field :description_html, null: true, method: :description
|
||||
end
|
||||
end
|
10
app/graphql/types/snippets/type_enum.rb
Normal file
10
app/graphql/types/snippets/type_enum.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module Snippets
|
||||
class TypeEnum < BaseEnum
|
||||
value 'personal', value: 'personal'
|
||||
value 'project', value: 'project'
|
||||
end
|
||||
end
|
||||
end
|
11
app/graphql/types/snippets/visibility_scopes_enum.rb
Normal file
11
app/graphql/types/snippets/visibility_scopes_enum.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module Snippets
|
||||
class VisibilityScopesEnum < BaseEnum
|
||||
value 'private', value: 'are_private'
|
||||
value 'internal', value: 'are_internal'
|
||||
value 'public', value: 'are_public'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,6 +8,8 @@ module Types
|
|||
|
||||
present_using UserPresenter
|
||||
|
||||
expose_permissions Types::PermissionTypes::User
|
||||
|
||||
field :name, GraphQL::STRING_TYPE, null: false,
|
||||
description: 'Human-readable name of the user'
|
||||
field :username, GraphQL::STRING_TYPE, null: false,
|
||||
|
@ -19,5 +21,11 @@ module Types
|
|||
field :todos, Types::TodoType.connection_type, null: false,
|
||||
resolver: Resolvers::TodoResolver,
|
||||
description: 'Todos of the user'
|
||||
|
||||
field :snippets,
|
||||
Types::SnippetType.connection_type,
|
||||
null: true,
|
||||
description: 'Snippets authored by the user',
|
||||
resolver: Resolvers::Users::SnippetsResolver
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,6 +32,18 @@ module Emails
|
|||
mail(to: @user.notification_email, subject: subject("GPG key was added to your account"))
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def access_token_about_to_expire_email(user)
|
||||
return unless user
|
||||
|
||||
@user = user
|
||||
@target_url = profile_personal_access_tokens_url
|
||||
@days_to_expire = PersonalAccessToken::DAYS_TO_EXPIRE
|
||||
|
||||
Gitlab::I18n.with_locale(@user.preferred_language) do
|
||||
mail(to: @user.notification_email, subject: subject(_("Your Personal Access Tokens will expire in %{days_to_expire} days or less") % { days_to_expire: @days_to_expire }))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -13,15 +13,21 @@ module Clusters
|
|||
include ::Clusters::Concerns::ApplicationStatus
|
||||
include ::Clusters::Concerns::ApplicationVersion
|
||||
include ::Clusters::Concerns::ApplicationData
|
||||
include AfterCommitQueue
|
||||
|
||||
default_value_for :version, VERSION
|
||||
|
||||
after_destroy :disable_prometheus_integration
|
||||
after_destroy do
|
||||
run_after_commit do
|
||||
disable_prometheus_integration
|
||||
end
|
||||
end
|
||||
|
||||
state_machine :status do
|
||||
after_transition any => [:installed] do |application|
|
||||
application.cluster.projects.each do |project|
|
||||
project.find_or_initialize_service('prometheus').update!(active: true)
|
||||
application.run_after_commit do
|
||||
Clusters::Applications::ActivateServiceWorker
|
||||
.perform_async(application.cluster_id, ::PrometheusService.to_param) # rubocop:disable CodeReuse/ServiceClass
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -98,9 +104,8 @@ module Clusters
|
|||
private
|
||||
|
||||
def disable_prometheus_integration
|
||||
cluster.projects.each do |project|
|
||||
project.prometheus_service&.update!(active: false)
|
||||
end
|
||||
::Clusters::Applications::DeactivateServiceWorker
|
||||
.perform_async(cluster_id, ::PrometheusService.to_param) # rubocop:disable CodeReuse/ServiceClass
|
||||
end
|
||||
|
||||
def kube_client
|
||||
|
|
|
@ -34,6 +34,7 @@ module Clusters
|
|||
|
||||
has_many :cluster_groups, class_name: 'Clusters::Group'
|
||||
has_many :groups, through: :cluster_groups, class_name: '::Group'
|
||||
has_many :groups_projects, through: :groups, source: :projects, class_name: '::Project'
|
||||
|
||||
# we force autosave to happen when we save `Cluster` model
|
||||
has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true
|
||||
|
@ -177,6 +178,13 @@ module Clusters
|
|||
end
|
||||
end
|
||||
|
||||
def all_projects
|
||||
return projects if project_type?
|
||||
return groups_projects if group_type?
|
||||
|
||||
::Project.all
|
||||
end
|
||||
|
||||
def status_name
|
||||
return cleanup_status_name if cleanup_errored?
|
||||
return :cleanup_ongoing unless cleanup_not_started?
|
||||
|
|
|
@ -17,6 +17,7 @@ module Ci
|
|||
delegate :timeout, to: :metadata, prefix: true, allow_nil: true
|
||||
delegate :interruptible, to: :metadata, prefix: false, allow_nil: true
|
||||
delegate :has_exposed_artifacts?, to: :metadata, prefix: false, allow_nil: true
|
||||
delegate :environment_auto_stop_in, to: :metadata, prefix: false, allow_nil: true
|
||||
before_create :ensure_metadata
|
||||
end
|
||||
|
||||
|
@ -47,8 +48,11 @@ module Ci
|
|||
def options=(value)
|
||||
write_metadata_attribute(:options, :config_options, value)
|
||||
|
||||
# Store presence of exposed artifacts in build metadata to make it easier to query
|
||||
ensure_metadata.has_exposed_artifacts = value&.dig(:artifacts, :expose_as).present?
|
||||
ensure_metadata.tap do |metadata|
|
||||
# Store presence of exposed artifacts in build metadata to make it easier to query
|
||||
metadata.has_exposed_artifacts = value&.dig(:artifacts, :expose_as).present?
|
||||
metadata.environment_auto_stop_in = value&.dig(:environment, :auto_stop_in)
|
||||
end
|
||||
end
|
||||
|
||||
def yaml_variables=(value)
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
module Expirable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
DAYS_TO_EXPIRE = 7
|
||||
|
||||
included do
|
||||
scope :expired, -> { where('expires_at <= ?', Time.current) }
|
||||
end
|
||||
|
@ -16,6 +18,6 @@ module Expirable
|
|||
end
|
||||
|
||||
def expires_soon?
|
||||
expires? && expires_at < 7.days.from_now
|
||||
expires? && expires_at < DAYS_TO_EXPIRE.days.from_now
|
||||
end
|
||||
end
|
||||
|
|
|
@ -162,6 +162,10 @@ class Environment < ApplicationRecord
|
|||
stop_action&.play(current_user)
|
||||
end
|
||||
|
||||
def reset_auto_stop
|
||||
update_column(:auto_stop_at, nil)
|
||||
end
|
||||
|
||||
def actions_for(environment)
|
||||
return [] unless manual_actions
|
||||
|
||||
|
@ -261,6 +265,17 @@ class Environment < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def auto_stop_in
|
||||
auto_stop_at - Time.now if auto_stop_at
|
||||
end
|
||||
|
||||
def auto_stop_in=(value)
|
||||
return unless value
|
||||
return unless parsed_result = ChronicDuration.parse(value)
|
||||
|
||||
self.auto_stop_at = parsed_result.seconds.from_now
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_slug
|
||||
|
|
|
@ -341,6 +341,6 @@ class Milestone < ApplicationRecord
|
|||
end
|
||||
|
||||
def issues_finder_params
|
||||
{ project_id: project_id, group_id: group_id }.compact
|
||||
{ project_id: project_id, group_id: group_id, include_subgroups: group_id.present? }.compact
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,6 +16,7 @@ class PersonalAccessToken < ApplicationRecord
|
|||
before_save :ensure_token
|
||||
|
||||
scope :active, -> { where("revoked = false AND (expires_at >= NOW() OR expires_at IS NULL)") }
|
||||
scope :expiring_and_not_notified, ->(date) { where(["revoked = false AND expire_notification_delivered = false AND expires_at >= NOW() AND expires_at <= ?", date]) }
|
||||
scope :inactive, -> { where("revoked = true OR expires_at < NOW()") }
|
||||
scope :with_impersonation, -> { where(impersonation: true) }
|
||||
scope :without_impersonation, -> { where(impersonation: false) }
|
||||
|
|
|
@ -404,6 +404,7 @@ class Project < ApplicationRecord
|
|||
scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) }
|
||||
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
|
||||
scope :with_statistics, -> { includes(:statistics) }
|
||||
scope :with_service, ->(service) { joins(service).eager_load(service) }
|
||||
scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
|
||||
scope :with_container_registry, -> { where(container_registry_enabled: true) }
|
||||
scope :inside_path, ->(path) do
|
||||
|
@ -1256,8 +1257,9 @@ class Project < ApplicationRecord
|
|||
|
||||
def all_clusters
|
||||
group_clusters = Clusters::Cluster.joins(:groups).where(cluster_groups: { group_id: ancestors_upto } )
|
||||
instance_clusters = Clusters::Cluster.instance_type
|
||||
|
||||
Clusters::Cluster.from_union([clusters, group_clusters])
|
||||
Clusters::Cluster.from_union([clusters, group_clusters, instance_clusters])
|
||||
end
|
||||
|
||||
def items_for(entity)
|
||||
|
|
|
@ -88,7 +88,7 @@ class PrometheusService < MonitoringService
|
|||
return false if template?
|
||||
return false unless project
|
||||
|
||||
project.clusters.enabled.any? { |cluster| cluster.application_prometheus_available? }
|
||||
project.all_clusters.enabled.any? { |cluster| cluster.application_prometheus_available? }
|
||||
end
|
||||
|
||||
def allow_local_api_url?
|
||||
|
|
|
@ -310,6 +310,13 @@ class User < ApplicationRecord
|
|||
scope :with_dashboard, -> (dashboard) { where(dashboard: dashboard) }
|
||||
scope :with_public_profile, -> { where(private_profile: false) }
|
||||
|
||||
scope :with_expiring_and_not_notified_personal_access_tokens, ->(at) do
|
||||
where('EXISTS (?)',
|
||||
::PersonalAccessToken
|
||||
.where('personal_access_tokens.user_id = users.id')
|
||||
.expiring_and_not_notified(at).select(1))
|
||||
end
|
||||
|
||||
def self.with_visible_profile(user)
|
||||
return with_public_profile if user.nil?
|
||||
|
||||
|
|
|
@ -27,4 +27,7 @@ class PersonalSnippetPolicy < BasePolicy
|
|||
rule { can?(:create_note) }.enable :award_emoji
|
||||
|
||||
rule { can?(:read_all_resources) }.enable :read_personal_snippet
|
||||
|
||||
# Aliasing the ability to ease GraphQL permissions check
|
||||
rule { can?(:read_personal_snippet) }.enable :read_snippet
|
||||
end
|
||||
|
|
|
@ -262,6 +262,7 @@ class ProjectPolicy < BasePolicy
|
|||
enable :update_container_image
|
||||
enable :destroy_container_image
|
||||
enable :create_environment
|
||||
enable :update_environment
|
||||
enable :create_deployment
|
||||
enable :update_deployment
|
||||
enable :create_release
|
||||
|
@ -278,8 +279,6 @@ class ProjectPolicy < BasePolicy
|
|||
enable :admin_board
|
||||
enable :push_to_delete_protected_branch
|
||||
enable :update_project_snippet
|
||||
enable :update_environment
|
||||
enable :update_deployment
|
||||
enable :admin_project_snippet
|
||||
enable :admin_project_member
|
||||
enable :admin_note
|
||||
|
|
|
@ -45,6 +45,9 @@ class ProjectSnippetPolicy < BasePolicy
|
|||
end
|
||||
|
||||
rule { ~can?(:read_project_snippet) }.prevent :create_note
|
||||
|
||||
# Aliasing the ability to ease GraphQL permissions check
|
||||
rule { can?(:read_project_snippet) }.enable :read_snippet
|
||||
end
|
||||
|
||||
ProjectSnippetPolicy.prepend_if_ee('EE::ProjectSnippetPolicy')
|
||||
|
|
35
app/presenters/snippet_presenter.rb
Normal file
35
app/presenters/snippet_presenter.rb
Normal file
|
@ -0,0 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class SnippetPresenter < Gitlab::View::Presenter::Delegated
|
||||
presents :snippet
|
||||
|
||||
def web_url
|
||||
Gitlab::UrlBuilder.build(snippet)
|
||||
end
|
||||
|
||||
def raw_url
|
||||
Gitlab::UrlBuilder.build(snippet, raw: true)
|
||||
end
|
||||
|
||||
def can_read_snippet?
|
||||
can_access_resource?("read")
|
||||
end
|
||||
|
||||
def can_update_snippet?
|
||||
can_access_resource?("update")
|
||||
end
|
||||
|
||||
def can_admin_snippet?
|
||||
can_access_resource?("admin")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def can_access_resource?(ability_prefix)
|
||||
can?(current_user, ability_name(ability_prefix), snippet)
|
||||
end
|
||||
|
||||
def ability_name(ability_prefix)
|
||||
"#{ability_prefix}_#{snippet.class.underscore}".to_sym
|
||||
end
|
||||
end
|
|
@ -24,6 +24,10 @@ class EnvironmentEntity < Grape::Entity
|
|||
stop_project_environment_path(environment.project, environment)
|
||||
end
|
||||
|
||||
expose :cancel_auto_stop_path, if: -> (*) { can_update_environment? } do |environment|
|
||||
cancel_auto_stop_project_environment_path(environment.project, environment)
|
||||
end
|
||||
|
||||
expose :cluster_type, if: ->(environment, _) { cluster_platform_kubernetes? } do |environment|
|
||||
cluster.cluster_type
|
||||
end
|
||||
|
@ -37,6 +41,7 @@ class EnvironmentEntity < Grape::Entity
|
|||
end
|
||||
|
||||
expose :created_at, :updated_at
|
||||
expose :auto_stop_at, expose_nil: false
|
||||
|
||||
expose :can_stop do |environment|
|
||||
environment.available? && can?(current_user, :stop_environment, environment)
|
||||
|
@ -54,6 +59,10 @@ class EnvironmentEntity < Grape::Entity
|
|||
can?(request.current_user, :create_environment_terminal, environment)
|
||||
end
|
||||
|
||||
def can_update_environment?
|
||||
can?(current_user, :update_environment, environment)
|
||||
end
|
||||
|
||||
def cluster_platform_kubernetes?
|
||||
deployment_platform && deployment_platform.is_a?(Clusters::Platforms::Kubernetes)
|
||||
end
|
||||
|
|
|
@ -29,6 +29,7 @@ module Deployments
|
|||
environment.external_url = url
|
||||
end
|
||||
|
||||
renew_auto_stop_in
|
||||
environment.fire_state_event(action)
|
||||
|
||||
if environment.save && !environment.stopped?
|
||||
|
@ -63,6 +64,12 @@ module Deployments
|
|||
def action
|
||||
environment_options[:action] || 'start'
|
||||
end
|
||||
|
||||
def renew_auto_stop_in
|
||||
return unless deployable
|
||||
|
||||
environment.auto_stop_in = deployable.environment_auto_stop_in
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
22
app/services/environments/reset_auto_stop_service.rb
Normal file
22
app/services/environments/reset_auto_stop_service.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Environments
|
||||
class ResetAutoStopService < ::BaseService
|
||||
def execute(environment)
|
||||
return error(_('Failed to cancel auto stop because you do not have permission to update the environment.')) unless can_update_environment?(environment)
|
||||
return error(_('Failed to cancel auto stop because the environment is not set as auto stop.')) unless environment.auto_stop_at?
|
||||
|
||||
if environment.reset_auto_stop
|
||||
success
|
||||
else
|
||||
error(_('Failed to cancel auto stop because failed to update the environment.'))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def can_update_environment?(environment)
|
||||
can?(current_user, :update_environment, environment)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -58,6 +58,14 @@ class NotificationService
|
|||
end
|
||||
end
|
||||
|
||||
# Notify the owner of the personal access token, when it is about to expire
|
||||
# And mark the token with about_to_expire_delivered
|
||||
def access_token_about_to_expire(user)
|
||||
return unless user.can?(:receive_notifications)
|
||||
|
||||
mailer.access_token_about_to_expire_email(user).deliver_later
|
||||
end
|
||||
|
||||
# When create an issue we should send an email to:
|
||||
#
|
||||
# * issue assignee if their notification level is not Disabled
|
||||
|
|
|
@ -6,17 +6,19 @@
|
|||
%span.spinner.spinner-dark.spinner-sm{ 'aria-label': 'Loading' }
|
||||
%span.prepend-left-4= s_('ClusterIntegration|Kubernetes cluster is being created...')
|
||||
|
||||
.hidden.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' }
|
||||
.col-11
|
||||
.hidden.row.js-cluster-api-unreachable.gl-alert.gl-alert-warning{ role: 'alert' }
|
||||
= sprite_icon('warning', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
|
||||
%button.js-close-banner.gl-alert-dismiss{ type: 'button', 'aria-label' => _('Dismiss') }
|
||||
= sprite_icon('close', size: 16, css_class: 'gl-icon')
|
||||
.gl-alert-body
|
||||
= s_('ClusterIntegration|Your cluster API is unreachable. Please ensure your API URL is correct.')
|
||||
.col-1.p-0
|
||||
%button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×"
|
||||
|
||||
.hidden.js-cluster-authentication-failure.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' }
|
||||
.col-11
|
||||
.hidden.js-cluster-authentication-failure.js-cluster-api-unreachable.gl-alert.gl-alert-warning{ role: 'alert' }
|
||||
= sprite_icon('warning', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
|
||||
%button.js-close-banner.gl-alert-dismiss{ type: 'button', 'aria-label' => _('Dismiss') }
|
||||
= sprite_icon('close', size: 16, css_class: 'gl-icon')
|
||||
.gl-alert-body
|
||||
= s_('ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid.')
|
||||
.col-1.p-0
|
||||
%button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×"
|
||||
|
||||
.hidden.js-cluster-success.bs-callout.bs-callout-success{ role: 'alert' }
|
||||
= s_("ClusterIntegration|Kubernetes cluster was successfully created.")
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
-# We currently only support `alert`, `notice`, `success`, 'toast'
|
||||
.flash-container.flash-container-page.sticky
|
||||
-# We currently only support `alert`, `notice`, `success`
|
||||
- flash.each do |key, value|
|
||||
-# Don't show a flash message if the message is nil
|
||||
- if value
|
||||
- if key == 'toast' && value
|
||||
.js-toast-message{ data: { message: value } }
|
||||
- elsif value
|
||||
%div{ class: "flash-#{key} mb-2" }
|
||||
%span= value
|
||||
%div{ class: "close-icon-wrapper js-close-icon" }
|
||||
|
|
|
@ -144,8 +144,16 @@
|
|||
%strong.fly-out-top-item-name
|
||||
= issue_tracker.title
|
||||
|
||||
- if (project_nav_tab? :labels) && !@project.issues_enabled?
|
||||
= nav_link(controller: [:labels]) do
|
||||
= link_to project_labels_path(@project), title: _('Labels'), class: 'shortcuts-labels qa-labels-items' do
|
||||
.nav-icon-container
|
||||
= sprite_icon('label')
|
||||
%span.nav-item-name#js-onboarding-labels-link
|
||||
= _('Labels')
|
||||
|
||||
- if project_nav_tab? :merge_requests
|
||||
= nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
|
||||
= nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :milestones]) do
|
||||
= link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests', data: { qa_selector: 'merge_requests_link' } do
|
||||
.nav-icon-container
|
||||
= sprite_icon('git-merge')
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
%p
|
||||
= _('Hi %{username}!') % { username: sanitize_name(@user.name) }
|
||||
%p
|
||||
= _('One or more of your personal access tokens will expire in %{days_to_expire} days or less.') % { days_to_expire: @days_to_expire }
|
||||
%p
|
||||
- pat_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: @target_url }
|
||||
= _('You can create a new one or check them in your %{pat_link_start}Personal Access Tokens%{pat_link_end} settings').html_safe % { pat_link_start: pat_link_start, pat_link_end: '</a>'.html_safe }
|
|
@ -0,0 +1,5 @@
|
|||
<%= _('Hi %{username}!') % { username: sanitize_name(@user.name) } %>
|
||||
|
||||
<%= _('One or more of your personal access tokens will expire in %{days_to_expire} days or less.') % { days_to_expire: @days_to_expire} %>
|
||||
|
||||
<%= _('You can create a new one or check them in your Personal Access Tokens settings %{pat_link}') % { pat_link: @target_url } %>
|
|
@ -5,7 +5,7 @@
|
|||
- content_for :page_specific_javascripts do
|
||||
= stylesheet_link_tag 'page_bundles/xterm'
|
||||
|
||||
- if can?(current_user, :stop_environment, @environment)
|
||||
- if @environment.available? && can?(current_user, :stop_environment, @environment)
|
||||
#stop-environment-modal.modal.fade{ tabindex: -1 }
|
||||
.modal-dialog
|
||||
.modal-content
|
||||
|
@ -40,7 +40,7 @@
|
|||
= render 'projects/environments/metrics_button', environment: @environment
|
||||
- if can?(current_user, :update_environment, @environment)
|
||||
= link_to _('Edit'), edit_project_environment_path(@project, @environment), class: 'btn'
|
||||
- if can?(current_user, :stop_environment, @environment)
|
||||
- if @environment.available? && can?(current_user, :stop_environment, @environment)
|
||||
= button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal',
|
||||
target: '#stop-environment-modal' } do
|
||||
= sprite_icon('stop')
|
||||
|
|
|
@ -21,11 +21,11 @@
|
|||
%span.badge.badge-danger
|
||||
= s_('GitLabPages|Expired')
|
||||
%div
|
||||
= link_to s_('GitLabPages|Edit'), edit_project_pages_domain_path(@project, domain), class: "btn btn-sm btn-grouped btn-success btn-inverted"
|
||||
= link_to s_('GitLabPages|Edit'), project_pages_domain_path(@project, domain), class: "btn btn-sm btn-grouped btn-success btn-inverted"
|
||||
= link_to s_('GitLabPages|Remove'), project_pages_domain_path(@project, domain), data: { confirm: s_('GitLabPages|Are you sure?')}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
|
||||
- if verification_enabled && domain.unverified?
|
||||
%li.list-group-item.bs-callout-warning
|
||||
- details_link_start = "<a href='#{edit_project_pages_domain_path(@project, domain)}'>".html_safe
|
||||
- details_link_start = "<a href='#{project_pages_domain_path(@project, domain)}'>".html_safe
|
||||
- details_link_end = '</a>'.html_safe
|
||||
= s_('GitLabPages|%{domain} is not verified. To learn how to verify ownership, visit your %{link_start}domain details%{link_end}.').html_safe % { domain: domain.domain,
|
||||
link_start: details_link_start,
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
- add_to_breadcrumbs _("Pages"), project_pages_path(@project)
|
||||
- breadcrumb_title @domain.domain
|
||||
- page_title @domain.domain
|
||||
|
||||
- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
|
||||
|
||||
- if verification_enabled && @domain.unverified?
|
||||
= content_for :flash_message do
|
||||
.alert.alert-warning
|
||||
.container-fluid.container-limited
|
||||
= _("This domain is not verified. You will need to verify ownership before access is enabled.")
|
||||
|
||||
%h3.page-title
|
||||
= _('Pages Domain')
|
||||
= render 'projects/pages_domains/helper_text'
|
||||
%div
|
||||
= form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f|
|
||||
= render 'form', { f: f }
|
||||
.form-actions.d-flex.justify-content-between
|
||||
= f.submit _('Save Changes'), class: "btn btn-success"
|
||||
= link_to _('Cancel'), project_pages_path(@project), class: 'btn btn-default btn-inverse'
|
|
@ -1,7 +1,6 @@
|
|||
- add_to_breadcrumbs _("Pages"), project_pages_path(@project)
|
||||
- breadcrumb_title @domain.domain
|
||||
- page_title "#{@domain.domain}", _('Pages Domains')
|
||||
- dns_record = "#{@domain.domain} CNAME #{@domain.project.pages_subdomain}.#{Settings.pages.host}."
|
||||
- page_title @domain.domain
|
||||
|
||||
- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
|
||||
|
||||
|
@ -11,51 +10,12 @@
|
|||
.container-fluid.container-limited
|
||||
= _("This domain is not verified. You will need to verify ownership before access is enabled.")
|
||||
|
||||
%h3.page-title.with-button
|
||||
= link_to _('Edit'), edit_project_pages_domain_path(@project, @domain), class: 'btn btn-success float-right'
|
||||
= _("Pages Domain")
|
||||
|
||||
.table-holder
|
||||
%table.table
|
||||
%tr
|
||||
%td
|
||||
= _("Domain")
|
||||
%td
|
||||
= external_link(@domain.url, @domain.url)
|
||||
%tr
|
||||
%td
|
||||
= _("DNS")
|
||||
%td
|
||||
.input-group
|
||||
= text_field_tag :domain_dns, dns_record , class: "monospace js-select-on-focus form-control", readonly: true
|
||||
.input-group-append
|
||||
= clipboard_button(target: '#domain_dns', class: 'btn-default input-group-text d-none d-sm-block')
|
||||
%p.form-text.text-muted
|
||||
= _("To access this domain create a new DNS record")
|
||||
|
||||
- if verification_enabled
|
||||
- verification_record = "#{@domain.verification_domain} TXT #{@domain.keyed_verification_code}"
|
||||
%tr
|
||||
%td
|
||||
= _("Verification status")
|
||||
%td
|
||||
= form_tag verify_project_pages_domain_path(@project, @domain) do
|
||||
.status-badge
|
||||
- text, status = @domain.unverified? ? [_('Unverified'), 'badge-danger'] : [_('Verified'), 'badge-success']
|
||||
.badge{ class: status }
|
||||
= text
|
||||
%button.btn.has-tooltip{ type: "submit", data: { container: 'body' }, title: _("Retry verification") }
|
||||
= sprite_icon('redo')
|
||||
.input-group
|
||||
= text_field_tag :domain_verification, verification_record, class: "monospace js-select-on-focus form-control", readonly: true
|
||||
.input-group-append
|
||||
= clipboard_button(target: '#domain_verification', class: 'btn-default d-none d-sm-block')
|
||||
%p.form-text.text-muted
|
||||
- link_to_help = link_to(_('verify ownership'), help_page_path('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: '4-verify-the-domains-ownership'))
|
||||
= _("To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration.").html_safe % { link_to_help: link_to_help }
|
||||
|
||||
%tr
|
||||
%td
|
||||
= _("Certificate")
|
||||
%td
|
||||
= render 'lets_encrypt_callout', auto_ssl_available_and_enabled: false
|
||||
%h3.page-title
|
||||
= _('Pages Domain')
|
||||
= render 'projects/pages_domains/helper_text'
|
||||
%div
|
||||
= form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f|
|
||||
= render 'form', { f: f }
|
||||
.form-actions.d-flex.justify-content-between
|
||||
= f.submit _('Save Changes'), class: "btn btn-success"
|
||||
= link_to _('Cancel'), project_pages_path(@project), class: 'btn btn-default btn-inverse'
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
.block.milestone{ data: { qa_selector: 'milestone_block' } }
|
||||
.sidebar-collapsed-icon.has-tooltip{ title: sidebar_milestone_tooltip_label(milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } }
|
||||
= icon('clock-o', 'aria-hidden': 'true')
|
||||
%span.milestone-title.collapse-truncated-title{ data: { qa_selector: 'milestone_title' } }
|
||||
%span.milestone-title.collapse-truncated-title
|
||||
- if milestone.present?
|
||||
= milestone[:title]
|
||||
- else
|
||||
|
@ -45,7 +45,7 @@
|
|||
= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { track_label: "right_sidebar", track_property: "milestone", track_event: "click_edit_button", track_value: "" }
|
||||
.value.hide-collapsed
|
||||
- if milestone.present?
|
||||
= link_to milestone[:title], milestone[:web_url], class: "bold has-tooltip", title: sidebar_milestone_remaining_days(milestone), data: { container: "body", html: 'true', boundary: 'viewport', qa_selector: 'milestone_link' }
|
||||
= link_to milestone[:title], milestone[:web_url], class: "bold has-tooltip", title: sidebar_milestone_remaining_days(milestone), data: { container: "body", html: 'true', boundary: 'viewport', qa_selector: 'milestone_link', qa_title: milestone[:title] }
|
||||
- else
|
||||
%span.no-value
|
||||
= _('None')
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
- cronjob:pages_domain_verification_cron
|
||||
- cronjob:pages_domain_removal_cron
|
||||
- cronjob:pages_domain_ssl_renewal_cron
|
||||
- cronjob:personal_access_tokens_expiring
|
||||
- cronjob:pipeline_schedule
|
||||
- cronjob:prune_old_events
|
||||
- cronjob:remove_expired_group_links
|
||||
|
@ -51,6 +52,8 @@
|
|||
- gcp_cluster:clusters_cleanup_app
|
||||
- gcp_cluster:clusters_cleanup_project_namespace
|
||||
- gcp_cluster:clusters_cleanup_service_account
|
||||
- gcp_cluster:clusters_applications_activate_service
|
||||
- gcp_cluster:clusters_applications_deactivate_service
|
||||
|
||||
- github_import_advance_stage
|
||||
- github_importer:github_import_import_diff_note
|
||||
|
|
19
app/workers/clusters/applications/activate_service_worker.rb
Normal file
19
app/workers/clusters/applications/activate_service_worker.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Clusters
|
||||
module Applications
|
||||
class ActivateServiceWorker
|
||||
include ApplicationWorker
|
||||
include ClusterQueue
|
||||
|
||||
def perform(cluster_id, service_name)
|
||||
cluster = Clusters::Cluster.find_by_id(cluster_id)
|
||||
return unless cluster
|
||||
|
||||
cluster.all_projects.find_each do |project|
|
||||
project.find_or_initialize_service(service_name).update!(active: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Clusters
|
||||
module Applications
|
||||
class DeactivateServiceWorker
|
||||
include ApplicationWorker
|
||||
include ClusterQueue
|
||||
|
||||
def perform(cluster_id, service_name)
|
||||
cluster = Clusters::Cluster.find_by_id(cluster_id)
|
||||
raise cluster_missing_error(service_name) unless cluster
|
||||
|
||||
service = "#{service_name}_service".to_sym
|
||||
cluster.all_projects.with_service(service).find_each do |project|
|
||||
project.public_send(service).update!(active: false) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
end
|
||||
|
||||
def cluster_missing_error(service)
|
||||
ActiveRecord::RecordNotFound.new("Can't deactivate #{service} services, host cluster not found! Some inconsistent records may be left in database.")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
23
app/workers/personal_access_tokens/expiring_worker.rb
Normal file
23
app/workers/personal_access_tokens/expiring_worker.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module PersonalAccessTokens
|
||||
class ExpiringWorker
|
||||
include ApplicationWorker
|
||||
include CronjobQueue
|
||||
|
||||
feature_category :authentication_and_authorization
|
||||
|
||||
def perform(*args)
|
||||
notification_service = NotificationService.new
|
||||
limit_date = PersonalAccessToken::DAYS_TO_EXPIRE.days.from_now.to_date
|
||||
|
||||
User.with_expiring_and_not_notified_personal_access_tokens(limit_date).find_each do |user|
|
||||
notification_service.access_token_about_to_expire(user)
|
||||
|
||||
Rails.logger.info "#{self.class}: Notifying User #{user.id} about expiring tokens" # rubocop:disable Gitlab/RailsLogger
|
||||
|
||||
user.personal_access_tokens.expiring_and_not_notified(limit_date).update_all(expire_notification_delivered: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Activate projects Prometheus service integration when Prometheus managed application is installed on shared cluster
|
||||
merge_request:
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Display Labels item in sidebar when Issues are disabled
|
||||
merge_request: 20817
|
||||
author:
|
||||
type: fixed
|
5
changelogs/unreleased/32897-convert-alerts-to-toasts.yml
Normal file
5
changelogs/unreleased/32897-convert-alerts-to-toasts.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Convert flash alerts to toasts
|
||||
merge_request: 20356
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update padding for cluster alert warning
|
||||
merge_request: 20036
|
||||
author: George Tsiolis
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Retrieve issues from subgroups when rendering group milestone
|
||||
merge_request: 21024
|
||||
author:
|
||||
type: fixed
|
5
changelogs/unreleased/8524-add-ds-var.yml
Normal file
5
changelogs/unreleased/8524-add-ds-var.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add dependency scanning flag for specifying pip requirements file for scanning.
|
||||
merge_request: 21219
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add Snippet GraphQL resolver endpoints
|
||||
merge_request: 20613
|
||||
author:
|
||||
type: added
|
5
changelogs/unreleased/sa-pat-expiration-notification.yml
Normal file
5
changelogs/unreleased/sa-pat-expiration-notification.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add Personal Access Token expiration reminder
|
||||
merge_request: 19296
|
||||
author:
|
||||
type: added
|
5
changelogs/unreleased/userwithid-per-env-backend.yml
Normal file
5
changelogs/unreleased/userwithid-per-env-backend.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add feature to allow specifying userWithId strategies per environment spec
|
||||
merge_request: 20325
|
||||
author:
|
||||
type: added
|
|
@ -366,6 +366,9 @@ production: &base
|
|||
# Send admin emails once a week
|
||||
admin_email_worker:
|
||||
cron: "0 0 * * 0"
|
||||
# Send emails for personal tokens which are about to expire
|
||||
personal_access_tokens_expiring_worker:
|
||||
cron: "0 1 * * *"
|
||||
|
||||
# Remove outdated repository archives
|
||||
repository_archive_cache_worker:
|
||||
|
|
|
@ -407,6 +407,9 @@ Settings.cron_jobs['repository_check_worker']['job_class'] = 'RepositoryCheck::D
|
|||
Settings.cron_jobs['admin_email_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['admin_email_worker']['cron'] ||= '0 0 * * 0'
|
||||
Settings.cron_jobs['admin_email_worker']['job_class'] = 'AdminEmailWorker'
|
||||
Settings.cron_jobs['personal_access_tokens_expiring_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['personal_access_tokens_expiring_worker']['cron'] ||= '0 1 * * *'
|
||||
Settings.cron_jobs['personal_access_tokens_expiring_worker']['job_class'] = 'PersonalAccessTokens::ExpiringWorker'
|
||||
Settings.cron_jobs['repository_archive_cache_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['repository_archive_cache_worker']['cron'] ||= '0 * * * *'
|
||||
Settings.cron_jobs['repository_archive_cache_worker']['job_class'] = 'RepositoryArchiveCacheWorker'
|
||||
|
|
|
@ -224,6 +224,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
|||
resources :environments, except: [:destroy] do
|
||||
member do
|
||||
post :stop
|
||||
post :cancel_auto_stop
|
||||
get :terminal
|
||||
get :metrics
|
||||
get :additional_metrics
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddAutoStopInToEnvironments < ActiveRecord::Migration[5.2]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
add_column :environments, :auto_stop_at, :datetime_with_timezone
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddExpireNotificationDeliveredToPersonalAccessTokens < ActiveRecord::Migration[5.2]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_column_with_default :personal_access_tokens, :expire_notification_delivered, :boolean, default: false
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :personal_access_tokens, :expire_notification_delivered
|
||||
end
|
||||
end
|
14
db/migrate/20191121111621_create_packages_dependencies.rb
Normal file
14
db/migrate/20191121111621_create_packages_dependencies.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreatePackagesDependencies < ActiveRecord::Migration[5.2]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
create_table :packages_dependencies do |t|
|
||||
t.string :name, null: false, limit: 255
|
||||
t.string :version_pattern, null: false, limit: 255
|
||||
end
|
||||
|
||||
add_index :packages_dependencies, [:name, :version_pattern], unique: true
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreatePackagesDependencyLinks < ActiveRecord::Migration[5.2]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
create_table :packages_dependency_links do |t|
|
||||
t.references :package, index: false, null: false, foreign_key: { to_table: :packages_packages, on_delete: :cascade }, type: :bigint
|
||||
t.references :dependency, null: false, foreign_key: { to_table: :packages_dependencies, on_delete: :cascade }, type: :bigint
|
||||
t.integer :dependency_type, limit: 2, null: false
|
||||
end
|
||||
|
||||
add_index :packages_dependency_links, [:package_id, :dependency_id, :dependency_type], unique: true, name: 'idx_pkgs_dep_links_on_pkg_id_dependency_id_dependency_type'
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddProjectIdNameVersionPackageTypeIndexToPackagesPackages < ActiveRecord::Migration[5.2]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
INDEX_NAME = 'idx_packages_packages_on_project_id_name_version_package_type'.freeze
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_index :packages_packages,
|
||||
[:project_id, :name, :version, :package_type],
|
||||
name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index :packages_packages,
|
||||
[:project_id, :name, :version, :package_type],
|
||||
name: INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddEnvironmentAutoStopInToCiBuildsMetadata < ActiveRecord::Migration[5.2]
|
||||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
add_column :ci_builds_metadata, :environment_auto_stop_in, :string, limit: 255
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :ci_builds_metadata, :environment_auto_stop_in
|
||||
end
|
||||
end
|
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MigrateOpsFeatureFlagsScopesTargetUserIds < ActiveRecord::Migration[5.2]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
class OperationsFeatureFlagScope < ActiveRecord::Base
|
||||
include EachBatch
|
||||
self.table_name = 'operations_feature_flag_scopes'
|
||||
self.inheritance_column = :_type_disabled
|
||||
end
|
||||
|
||||
###
|
||||
# 2019-11-26
|
||||
#
|
||||
# There are about 1000 rows in the operations_feature_flag_scopes table on gitlab.com.
|
||||
# This migration will update about 30 of them.
|
||||
# https://gitlab.com/gitlab-org/gitlab/merge_requests/20325#note_250742098
|
||||
#
|
||||
# This should take a few seconds to run.
|
||||
# https://gitlab.com/gitlab-org/gitlab/merge_requests/20325#note_254871603
|
||||
#
|
||||
###
|
||||
def up
|
||||
OperationsFeatureFlagScope.where("strategies @> ?", [{ 'name': 'userWithId' }].to_json).each_batch do |scopes|
|
||||
scopes.each do |scope|
|
||||
if scope.active
|
||||
default_strategy = scope.strategies.find { |s| s['name'] == 'default' }
|
||||
|
||||
if default_strategy.present?
|
||||
scope.update({ strategies: [default_strategy] })
|
||||
end
|
||||
else
|
||||
user_with_id_strategy = scope.strategies.find { |s| s['name'] == 'userWithId' }
|
||||
|
||||
scope.update({
|
||||
active: true,
|
||||
strategies: [user_with_id_strategy]
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
# This is not reversible.
|
||||
# The old Target Users feature required the same list of user ids to be applied to each environment scope.
|
||||
# Now we allow the list of user ids to differ for each scope.
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DropPackagesPackageMetadataTable < ActiveRecord::Migration[5.2]
|
||||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
drop_table :packages_package_metadata
|
||||
end
|
||||
|
||||
def down
|
||||
create_table :packages_package_metadata do |t|
|
||||
t.references :package, index: { unique: true }, null: false, foreign_key: { to_table: :packages_packages, on_delete: :cascade }, type: :integer
|
||||
t.binary :metadata, null: false
|
||||
end
|
||||
end
|
||||
end
|
27
db/schema.rb
27
db/schema.rb
|
@ -717,6 +717,7 @@ ActiveRecord::Schema.define(version: 2019_12_04_093410) do
|
|||
t.jsonb "config_options"
|
||||
t.jsonb "config_variables"
|
||||
t.boolean "has_exposed_artifacts"
|
||||
t.string "environment_auto_stop_in", limit: 255
|
||||
t.index ["build_id"], name: "index_ci_builds_metadata_on_build_id", unique: true
|
||||
t.index ["build_id"], name: "index_ci_builds_metadata_on_build_id_and_has_exposed_artifacts", where: "(has_exposed_artifacts IS TRUE)"
|
||||
t.index ["build_id"], name: "index_ci_builds_metadata_on_build_id_and_interruptible", where: "(interruptible = true)"
|
||||
|
@ -1447,6 +1448,7 @@ ActiveRecord::Schema.define(version: 2019_12_04_093410) do
|
|||
t.string "environment_type"
|
||||
t.string "state", default: "available", null: false
|
||||
t.string "slug", null: false
|
||||
t.datetime_with_timezone "auto_stop_at"
|
||||
t.index ["name"], name: "index_environments_on_name_varchar_pattern_ops", opclass: :varchar_pattern_ops
|
||||
t.index ["project_id", "name"], name: "index_environments_on_project_id_and_name", unique: true
|
||||
t.index ["project_id", "slug"], name: "index_environments_on_project_id_and_slug", unique: true
|
||||
|
@ -2822,6 +2824,20 @@ ActiveRecord::Schema.define(version: 2019_12_04_093410) do
|
|||
t.index ["package_id"], name: "index_packages_conan_metadata_on_package_id", unique: true
|
||||
end
|
||||
|
||||
create_table "packages_dependencies", force: :cascade do |t|
|
||||
t.string "name", limit: 255, null: false
|
||||
t.string "version_pattern", limit: 255, null: false
|
||||
t.index ["name", "version_pattern"], name: "index_packages_dependencies_on_name_and_version_pattern", unique: true
|
||||
end
|
||||
|
||||
create_table "packages_dependency_links", force: :cascade do |t|
|
||||
t.bigint "package_id", null: false
|
||||
t.bigint "dependency_id", null: false
|
||||
t.integer "dependency_type", limit: 2, null: false
|
||||
t.index ["dependency_id"], name: "index_packages_dependency_links_on_dependency_id"
|
||||
t.index ["package_id", "dependency_id", "dependency_type"], name: "idx_pkgs_dep_links_on_pkg_id_dependency_id_dependency_type", unique: true
|
||||
end
|
||||
|
||||
create_table "packages_maven_metadata", force: :cascade do |t|
|
||||
t.bigint "package_id", null: false
|
||||
t.datetime_with_timezone "created_at", null: false
|
||||
|
@ -2847,12 +2863,6 @@ ActiveRecord::Schema.define(version: 2019_12_04_093410) do
|
|||
t.index ["package_id", "file_name"], name: "index_packages_package_files_on_package_id_and_file_name"
|
||||
end
|
||||
|
||||
create_table "packages_package_metadata", force: :cascade do |t|
|
||||
t.integer "package_id", null: false
|
||||
t.binary "metadata", null: false
|
||||
t.index ["package_id"], name: "index_packages_package_metadata_on_package_id", unique: true
|
||||
end
|
||||
|
||||
create_table "packages_package_tags", force: :cascade do |t|
|
||||
t.integer "package_id", null: false
|
||||
t.string "name", limit: 255, null: false
|
||||
|
@ -2867,6 +2877,7 @@ ActiveRecord::Schema.define(version: 2019_12_04_093410) do
|
|||
t.string "version"
|
||||
t.integer "package_type", limit: 2, null: false
|
||||
t.index ["name"], name: "index_packages_packages_on_name_trigram", opclass: :gin_trgm_ops, using: :gin
|
||||
t.index ["project_id", "name", "version", "package_type"], name: "idx_packages_packages_on_project_id_name_version_package_type"
|
||||
t.index ["project_id"], name: "index_packages_packages_on_project_id"
|
||||
end
|
||||
|
||||
|
@ -2929,6 +2940,7 @@ ActiveRecord::Schema.define(version: 2019_12_04_093410) do
|
|||
t.string "scopes", default: "--- []\n", null: false
|
||||
t.boolean "impersonation", default: false, null: false
|
||||
t.string "token_digest"
|
||||
t.boolean "expire_notification_delivered", default: false, null: false
|
||||
t.index ["token_digest"], name: "index_personal_access_tokens_on_token_digest", unique: true
|
||||
t.index ["user_id", "expires_at"], name: "index_pat_on_user_id_and_expires_at"
|
||||
t.index ["user_id"], name: "index_personal_access_tokens_on_user_id"
|
||||
|
@ -4565,9 +4577,10 @@ ActiveRecord::Schema.define(version: 2019_12_04_093410) do
|
|||
add_foreign_key "operations_feature_flags_clients", "projects", on_delete: :cascade
|
||||
add_foreign_key "packages_conan_file_metadata", "packages_package_files", column: "package_file_id", on_delete: :cascade
|
||||
add_foreign_key "packages_conan_metadata", "packages_packages", column: "package_id", on_delete: :cascade
|
||||
add_foreign_key "packages_dependency_links", "packages_dependencies", column: "dependency_id", on_delete: :cascade
|
||||
add_foreign_key "packages_dependency_links", "packages_packages", column: "package_id", on_delete: :cascade
|
||||
add_foreign_key "packages_maven_metadata", "packages_packages", column: "package_id", name: "fk_be88aed360", on_delete: :cascade
|
||||
add_foreign_key "packages_package_files", "packages_packages", column: "package_id", name: "fk_86f0f182f8", on_delete: :cascade
|
||||
add_foreign_key "packages_package_metadata", "packages_packages", column: "package_id", on_delete: :cascade
|
||||
add_foreign_key "packages_package_tags", "packages_packages", column: "package_id", on_delete: :cascade
|
||||
add_foreign_key "packages_packages", "projects", on_delete: :cascade
|
||||
add_foreign_key "pages_domain_acme_orders", "pages_domains", on_delete: :cascade
|
||||
|
|
|
@ -51,7 +51,7 @@ We need to make Docker Registry send notification events to the
|
|||
'threshold' => 5,
|
||||
'backoff' => '1s',
|
||||
'headers' => {
|
||||
'Authorization' => ['<replace_with_a_secret_token>']
|
||||
'Authorization' => ['<replace_with_a_secret_token>'] # An alphanumeric string. Case sensitive and must start with a letter.
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -59,7 +59,7 @@ We need to make Docker Registry send notification events to the
|
|||
|
||||
NOTE: **Note:**
|
||||
If you use an external Registry (not the one integrated with GitLab), you must add
|
||||
these settings to its configuration. In this case, you will also have to specify
|
||||
these settings to its configuration yourself. In this case, you will also have to specify
|
||||
notification secret in `registry.notification_secret` section of
|
||||
`/etc/gitlab/gitlab.rb` file.
|
||||
|
||||
|
@ -100,7 +100,7 @@ generate a short-lived JWT that is pull-only-capable to access the
|
|||
|
||||
```ruby
|
||||
gitlab_rails['geo_registry_replication_enabled'] = true
|
||||
gitlab_rails['geo_registry_replication_primary_api_url'] = 'http://primary.example.com:5000/' # internal address to the primary registry, will be used by GitLab to directly communicate with primary registry API
|
||||
gitlab_rails['geo_registry_replication_primary_api_url'] = 'http://primary.example.com:4567/' # Primary registry address, it will be used by the secondary node to directly communicate to primary registry
|
||||
```
|
||||
|
||||
1. Reconfigure the **secondary** node for the change to take effect:
|
||||
|
|
|
@ -61,6 +61,7 @@ The GraphQL API includes the following queries at the root level:
|
|||
1. `namespace` : Within a namespace it is also possible to fetch `projects`.
|
||||
1. `currentUser`: Information about the currently logged in user.
|
||||
1. `metaData`: Metadata about GitLab and the GraphQL API.
|
||||
1. `snippets`: Snippets visible to the currently logged in user.
|
||||
|
||||
Root-level queries are defined in
|
||||
[`app/graphql/types/query_type.rb`](https://gitlab.com/gitlab-org/gitlab/blob/master/app/graphql/types/query_type.rb).
|
||||
|
|
|
@ -4512,6 +4512,41 @@ type Project {
|
|||
"""
|
||||
sharedRunnersEnabled: Boolean
|
||||
|
||||
"""
|
||||
Snippets of the project
|
||||
"""
|
||||
snippets(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Array of global snippet ids, e.g., "gid://gitlab/ProjectSnippet/1"
|
||||
"""
|
||||
ids: [ID!]
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
|
||||
"""
|
||||
The visibility of the snippet
|
||||
"""
|
||||
visibility: VisibilityScopesEnum
|
||||
): SnippetConnection
|
||||
|
||||
"""
|
||||
(deprecated) Does this project have snippets enabled?. Use `snippets_access_level` instead
|
||||
"""
|
||||
|
@ -4675,9 +4710,9 @@ type ProjectPermissions {
|
|||
createPipelineSchedule: Boolean!
|
||||
|
||||
"""
|
||||
Whether or not a user can perform `create_project_snippet` on this resource
|
||||
Whether or not a user can perform `create_snippet` on this resource
|
||||
"""
|
||||
createProjectSnippet: Boolean!
|
||||
createSnippet: Boolean!
|
||||
|
||||
"""
|
||||
Whether or not a user can perform `create_wiki` on this resource
|
||||
|
@ -4882,6 +4917,61 @@ type Query {
|
|||
"""
|
||||
fullPath: ID!
|
||||
): Project
|
||||
|
||||
"""
|
||||
Find Snippets visible to the current user
|
||||
"""
|
||||
snippets(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
The ID of an author
|
||||
"""
|
||||
authorId: ID
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Explore personal snippets
|
||||
"""
|
||||
explore: Boolean
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Array of global snippet ids, e.g., "gid://gitlab/ProjectSnippet/1"
|
||||
"""
|
||||
ids: [ID!]
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
|
||||
"""
|
||||
The ID of a project
|
||||
"""
|
||||
projectId: ID
|
||||
|
||||
"""
|
||||
The type of snippet
|
||||
"""
|
||||
type: TypeEnum
|
||||
|
||||
"""
|
||||
The visibility of the snippet
|
||||
"""
|
||||
visibility: VisibilityScopesEnum
|
||||
): SnippetConnection
|
||||
}
|
||||
|
||||
"""
|
||||
|
@ -5137,6 +5227,193 @@ enum SentryErrorStatus {
|
|||
UNRESOLVED
|
||||
}
|
||||
|
||||
"""
|
||||
Represents a snippet entry
|
||||
"""
|
||||
type Snippet implements Noteable {
|
||||
"""
|
||||
The owner of the snippet
|
||||
"""
|
||||
author: User!
|
||||
|
||||
"""
|
||||
Content of the snippet
|
||||
"""
|
||||
content: String!
|
||||
|
||||
"""
|
||||
Timestamp this snippet was created
|
||||
"""
|
||||
createdAt: Time!
|
||||
|
||||
"""
|
||||
Description of the snippet
|
||||
"""
|
||||
description: String
|
||||
|
||||
"""
|
||||
The GitLab Flavored Markdown rendering of `description`
|
||||
"""
|
||||
descriptionHtml: String
|
||||
|
||||
"""
|
||||
All discussions on this noteable
|
||||
"""
|
||||
discussions(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
): DiscussionConnection!
|
||||
|
||||
"""
|
||||
File Name of the snippet
|
||||
"""
|
||||
fileName: String
|
||||
|
||||
"""
|
||||
Id of the snippet
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
All notes on this noteable
|
||||
"""
|
||||
notes(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
): NoteConnection!
|
||||
|
||||
"""
|
||||
The project the snippet is associated with
|
||||
"""
|
||||
project: Project
|
||||
|
||||
"""
|
||||
Raw URL of the snippet
|
||||
"""
|
||||
rawUrl: String!
|
||||
|
||||
"""
|
||||
Title of the snippet
|
||||
"""
|
||||
title: String!
|
||||
|
||||
"""
|
||||
Timestamp this snippet was updated
|
||||
"""
|
||||
updatedAt: Time!
|
||||
|
||||
"""
|
||||
Permissions for the current user on the resource
|
||||
"""
|
||||
userPermissions: SnippetPermissions!
|
||||
|
||||
"""
|
||||
Visibility of the snippet
|
||||
"""
|
||||
visibility: String!
|
||||
|
||||
"""
|
||||
Web URL of the snippet
|
||||
"""
|
||||
webUrl: String!
|
||||
}
|
||||
|
||||
"""
|
||||
The connection type for Snippet.
|
||||
"""
|
||||
type SnippetConnection {
|
||||
"""
|
||||
A list of edges.
|
||||
"""
|
||||
edges: [SnippetEdge]
|
||||
|
||||
"""
|
||||
A list of nodes.
|
||||
"""
|
||||
nodes: [Snippet]
|
||||
|
||||
"""
|
||||
Information to aid in pagination.
|
||||
"""
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
"""
|
||||
An edge in a connection.
|
||||
"""
|
||||
type SnippetEdge {
|
||||
"""
|
||||
A cursor for use in pagination.
|
||||
"""
|
||||
cursor: String!
|
||||
|
||||
"""
|
||||
The item at the end of the edge.
|
||||
"""
|
||||
node: Snippet
|
||||
}
|
||||
|
||||
type SnippetPermissions {
|
||||
"""
|
||||
Whether or not a user can perform `admin_snippet` on this resource
|
||||
"""
|
||||
adminSnippet: Boolean!
|
||||
|
||||
"""
|
||||
Whether or not a user can perform `award_emoji` on this resource
|
||||
"""
|
||||
awardEmoji: Boolean!
|
||||
|
||||
"""
|
||||
Whether or not a user can perform `create_note` on this resource
|
||||
"""
|
||||
createNote: Boolean!
|
||||
|
||||
"""
|
||||
Whether or not a user can perform `read_snippet` on this resource
|
||||
"""
|
||||
readSnippet: Boolean!
|
||||
|
||||
"""
|
||||
Whether or not a user can perform `update_snippet` on this resource
|
||||
"""
|
||||
updateSnippet: Boolean!
|
||||
}
|
||||
|
||||
type Submodule implements Entry {
|
||||
flatPath: String!
|
||||
id: ID!
|
||||
|
@ -5602,6 +5879,11 @@ type TreeEntryEdge {
|
|||
node: TreeEntry
|
||||
}
|
||||
|
||||
enum TypeEnum {
|
||||
personal
|
||||
project
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of UpdateEpic
|
||||
"""
|
||||
|
@ -5740,6 +6022,46 @@ type User {
|
|||
"""
|
||||
name: String!
|
||||
|
||||
"""
|
||||
Snippets authored by the user
|
||||
"""
|
||||
snippets(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Array of global snippet ids, e.g., "gid://gitlab/ProjectSnippet/1"
|
||||
"""
|
||||
ids: [ID!]
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
|
||||
"""
|
||||
The type of snippet
|
||||
"""
|
||||
type: TypeEnum
|
||||
|
||||
"""
|
||||
The visibility of the snippet
|
||||
"""
|
||||
visibility: VisibilityScopesEnum
|
||||
): SnippetConnection
|
||||
|
||||
"""
|
||||
Todos of the user
|
||||
"""
|
||||
|
@ -5795,6 +6117,11 @@ type User {
|
|||
type: [TodoTargetEnum!]
|
||||
): TodoConnection!
|
||||
|
||||
"""
|
||||
Permissions for the current user on the resource
|
||||
"""
|
||||
userPermissions: UserPermissions!
|
||||
|
||||
"""
|
||||
Username of the user. Unique within this instance of GitLab
|
||||
"""
|
||||
|
@ -5839,4 +6166,17 @@ type UserEdge {
|
|||
The item at the end of the edge.
|
||||
"""
|
||||
node: User
|
||||
}
|
||||
|
||||
type UserPermissions {
|
||||
"""
|
||||
Whether or not a user can perform `create_snippet` on this resource
|
||||
"""
|
||||
createSnippet: Boolean!
|
||||
}
|
||||
|
||||
enum VisibilityScopesEnum {
|
||||
internal
|
||||
private
|
||||
public
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -689,7 +689,6 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
|||
| `downloadCode` | Boolean! | Whether or not a user can perform `download_code` on this resource |
|
||||
| `downloadWikiCode` | Boolean! | Whether or not a user can perform `download_wiki_code` on this resource |
|
||||
| `forkProject` | Boolean! | Whether or not a user can perform `fork_project` on this resource |
|
||||
| `createProjectSnippet` | Boolean! | Whether or not a user can perform `create_project_snippet` on this resource |
|
||||
| `readCommitStatus` | Boolean! | Whether or not a user can perform `read_commit_status` on this resource |
|
||||
| `requestAccess` | Boolean! | Whether or not a user can perform `request_access` on this resource |
|
||||
| `createPipeline` | Boolean! | Whether or not a user can perform `create_pipeline` on this resource |
|
||||
|
@ -710,6 +709,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
|||
| `destroyPages` | Boolean! | Whether or not a user can perform `destroy_pages` on this resource |
|
||||
| `readPagesContent` | Boolean! | Whether or not a user can perform `read_pages_content` on this resource |
|
||||
| `adminOperations` | Boolean! | Whether or not a user can perform `admin_operations` on this resource |
|
||||
| `createSnippet` | Boolean! | Whether or not a user can perform `create_snippet` on this resource |
|
||||
| `readDesign` | Boolean! | Whether or not a user can perform `read_design` on this resource |
|
||||
| `createDesign` | Boolean! | Whether or not a user can perform `create_design` on this resource |
|
||||
| `destroyDesign` | Boolean! | Whether or not a user can perform `destroy_design` on this resource |
|
||||
|
@ -787,6 +787,35 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
|||
| `time` | Time! | Time the error frequency stats were recorded |
|
||||
| `count` | Int! | Count of errors received since the previously recorded time |
|
||||
|
||||
### Snippet
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `userPermissions` | SnippetPermissions! | Permissions for the current user on the resource |
|
||||
| `id` | ID! | Id of the snippet |
|
||||
| `title` | String! | Title of the snippet |
|
||||
| `project` | Project | The project the snippet is associated with |
|
||||
| `author` | User! | The owner of the snippet |
|
||||
| `fileName` | String | File Name of the snippet |
|
||||
| `content` | String! | Content of the snippet |
|
||||
| `description` | String | Description of the snippet |
|
||||
| `visibility` | String! | Visibility of the snippet |
|
||||
| `createdAt` | Time! | Timestamp this snippet was created |
|
||||
| `updatedAt` | Time! | Timestamp this snippet was updated |
|
||||
| `webUrl` | String! | Web URL of the snippet |
|
||||
| `rawUrl` | String! | Raw URL of the snippet |
|
||||
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
|
||||
|
||||
### SnippetPermissions
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `createNote` | Boolean! | Whether or not a user can perform `create_note` on this resource |
|
||||
| `awardEmoji` | Boolean! | Whether or not a user can perform `award_emoji` on this resource |
|
||||
| `readSnippet` | Boolean! | Whether or not a user can perform `read_snippet` on this resource |
|
||||
| `updateSnippet` | Boolean! | Whether or not a user can perform `update_snippet` on this resource |
|
||||
| `adminSnippet` | Boolean! | Whether or not a user can perform `admin_snippet` on this resource |
|
||||
|
||||
### Submodule
|
||||
|
||||
| Name | Type | Description |
|
||||
|
@ -892,7 +921,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
|||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `userPermissions` | UserPermissions! | Permissions for the current user on the resource |
|
||||
| `name` | String! | Human-readable name of the user |
|
||||
| `username` | String! | Username of the user. Unique within this instance of GitLab |
|
||||
| `avatarUrl` | String! | URL of the user's avatar |
|
||||
| `webUrl` | String! | Web URL of the user |
|
||||
|
||||
### UserPermissions
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `createSnippet` | Boolean! | Whether or not a user can perform `create_snippet` on this resource |
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
|
@ -86,12 +86,15 @@ In case, you have a high-priority merge request (e.g. critical patch) to be merg
|
|||
you can use **Merge Immediately** option for bypassing the merge train.
|
||||
This is the fastest option to get the change merged into the target branch.
|
||||
|
||||
![Merge Immediately](img/merge_train_immediate_merge.png)
|
||||
![Merge Immediately](img/merge_train_immediate_merge_v12_6.png)
|
||||
|
||||
However, every time you merge a merge request immediately, it could affect the
|
||||
existing merge train to be reconstructed, specifically, it regenerates expected
|
||||
merge commits and pipelines. This means, merging immediately essentially wastes
|
||||
CI resources.
|
||||
CI resources. Because of these downsides, you will be asked to confirm before
|
||||
the merge is initiated:
|
||||
|
||||
![Merge immediately confirmation dialog](img/merge_train_immediate_merge_confirmation_dialog_v12_6.png)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
|
|
@ -23,19 +23,12 @@ The goal of the Package group is to build a set of features that, within three y
|
|||
| [Bower](https://gitlab.com/gitlab-org/gitlab/issues/36888) | Boost your front end development by hosting your own Bower components. |
|
||||
| [Chef](https://gitlab.com/gitlab-org/gitlab/issues/36889) | Configuration management with Chef using all the benefits of a repository manager. |
|
||||
| [CocoaPods](https://gitlab.com/gitlab-org/gitlab/issues/36890) | Speed up development with Xcode and CocoaPods. |
|
||||
| [Conan](https://docs.gitlab.com/ee/user/packages/conan_repository/) *12.6+* | A standardized way to share and version control C/C++ libraries across projects. |
|
||||
| [Conda](https://gitlab.com/gitlab-org/gitlab/issues/36891) | Secure and private local Conda repositories. |
|
||||
| [CRAN](https://gitlab.com/gitlab-org/gitlab/issues/36892) | Deploy and resolve CRAN packages for the R language. |
|
||||
| [Debian](https://gitlab.com/gitlab-org/gitlab/issues/5835) | Host and provision Debian packages. |
|
||||
| [Docker](https://docs.gitlab.com/ee/user/packages/container_registry/) *8.8+* | Host your own secure private Docker registries and proxy external Docker registries such as Docker Hub. |
|
||||
| [Go](https://gitlab.com/gitlab-org/gitlab/issues/9773) | Resolve Go dependencies from and publish your Go packages to GitLab. |
|
||||
| [Helm](https://gitlab.com/gitlab-org/gitlab/issues/18997) | Manage your Helm Charts in GitLab and gain control over deployments to your Kubernetes cluster. |
|
||||
| [Maven](https://docs.gitlab.com/ee/user/packages/maven_repository/index.html) *11.3+*| The GitLab Maven Repository enables every project in GitLab to have its own space to store Maven packages. |
|
||||
| [npm](https://docs.gitlab.com/ee/user/packages/npm_registry/index.html) *11.7+* | Host your own node.js packages. |
|
||||
| [NuGet](https://gitlab.com/gitlab-org/gitlab/issues/20050) *Planned for 12.7*| Host NuGet packages in GitLab, and pull libraries into your various Visual Studio .NET applications. |
|
||||
| [Opkg](https://gitlab.com/gitlab-org/gitlab/issues/36894) | Optimize your work with OpenWrt using Opkg repositories. |
|
||||
| [P2](https://gitlab.com/gitlab-org/gitlab/issues/36895) | Host all your Eclipse plugins in your own GitLab P2 repository. |
|
||||
| [PHP Composer](https://gitlab.com/gitlab-org/gitlab/issues/15886) | Provision Composer packages from GitLab and access Packagist and other remote Composer metadata repositories. |
|
||||
| [Puppet](https://gitlab.com/gitlab-org/gitlab/issues/36897) | Configuration management meets repository management with Puppet repositories. |
|
||||
| [PyPi](https://gitlab.com/gitlab-org/gitlab/issues/10483) | Host PyPi distributions. |
|
||||
| [RPM](https://gitlab.com/gitlab-org/gitlab/issues/5932) | Distribute RPMs directly from GitLab. |
|
||||
|
|
|
@ -186,22 +186,89 @@ secure note named `gitlab-{ce,ee} Review App's root password`.
|
|||
`review-qa-raise-e-12chm0-migrations.1-nqwtx`.
|
||||
1. Click on the `Container logs` link.
|
||||
|
||||
### Diagnosing unhealthy review-app releases
|
||||
## Diagnosing unhealthy Review App releases
|
||||
|
||||
If [Review App Stability](https://gitlab.com/gitlab-org/quality/team-tasks/issues/93) dips this may be a signal
|
||||
that the `review-apps-ce/ee` cluster is unhealthy. Leading indicators may be healthcheck failures leading to restarts or majority failure for Review App deployments.
|
||||
If [Review App Stability](https://app.periscopedata.com/app/gitlab/496118/Engineering-Productivity-Sandbox?widget=6690556&udv=785399)
|
||||
dips this may be a signal that the `review-apps-ce/ee` cluster is unhealthy.
|
||||
Leading indicators may be healthcheck failures leading to restarts or majority failure for Review App deployments.
|
||||
|
||||
The following items may help diagnose this:
|
||||
The [Review Apps Overview dashboard](https://app.google.stackdriver.com/dashboards/6798952013815386466?project=gitlab-review-apps&timeDomain=1d)
|
||||
aids in identifying load spikes on the cluster, and if nodes are problematic or the entire cluster is trending towards unhealthy.
|
||||
|
||||
- [Review Apps Health dashboard](https://app.google.stackdriver.com/dashboards/6798952013815386466?project=gitlab-review-apps&timeDomain=1d)
|
||||
- Aids in identifying load spikes on the cluster, and if nodes are problematic or the entire cluster is trending towards unhealthy.
|
||||
- `kubectl top nodes | sort --key 3 --numeric` - can identify if node spikes are common or load on specific nodes which may get rebalanced by the Kubernetes scheduler.
|
||||
- `kubectl top pods | sort --key 2 --numeric` -
|
||||
- [K9s] - K9s is a powerful command line dashboard which allows you to filter by labels. This can help identify trends with apps exceeding the [review-app resource requests](https://gitlab.com/gitlab-org/gitlab/blob/master/scripts/review_apps/base-config.yaml). Kubernetes will schedule pods to nodes based on resource requests and allow for CPU usage up to the limits.
|
||||
- In K9s you can sort or add filters by typing the `/` character
|
||||
- `-lrelease=<review-app-slug>` - filters down to all pods for a release. This aids in determining what is having issues in a single deployment
|
||||
- `-lapp=<app>` - filters down to all pods for a specific app. This aids in determining resource usage by app.
|
||||
- You can scroll to a Kubernetes resource and hit `d`(describe), `s`(shell), `l`(logs) for a deeper inspection
|
||||
### Node count is always increasing (i.e. never stabilizing or decreasing)
|
||||
|
||||
**Potential cause:**
|
||||
|
||||
That could be a sign that the [`schedule:review-cleanup`][gitlab-ci-yml] job is
|
||||
failing to cleanup stale Review Apps and Kubernetes resources.
|
||||
|
||||
**Where to look for further debugging:**
|
||||
|
||||
Look at the latest `schedule:review-cleanup` job log, and identify look for any
|
||||
unexpected failure.
|
||||
|
||||
### p99 CPU utilization is at 100% for most of the nodes and/or many components
|
||||
|
||||
**Potential cause:**
|
||||
|
||||
This could be a sign that Helm is failing to deploy Review Apps. When Helm has a
|
||||
lot of `FAILED` releases, it seems that the CPU utilization is increasing, probably
|
||||
due to Helm or Kubernetes trying to recreate the components.
|
||||
|
||||
**Where to look for further debugging:**
|
||||
|
||||
Look at a recent `review-deploy` job log, and at the Tiller logs.
|
||||
|
||||
**Useful commands:**
|
||||
|
||||
```shell
|
||||
# Identify if node spikes are common or load on specific nodes which may get rebalanced by the Kubernetes scheduler
|
||||
› kubectl top nodes | sort --key 3 --numeric
|
||||
|
||||
# Identify pods under heavy CPU load
|
||||
› kubectl top pods | sort --key 2 --numeric
|
||||
```
|
||||
|
||||
### The `logging/user/events/FailedMount` chart is going up
|
||||
|
||||
**Potential cause:**
|
||||
|
||||
This could be a sign that there are too many stale secrets and/or config maps.
|
||||
|
||||
**Where to look for further debugging:**
|
||||
|
||||
Look at [the list of Configurations](https://console.cloud.google.com/kubernetes/config?project=gitlab-review-apps)
|
||||
or `kubectl get secret,cm --sort-by='{.metadata.creationTimestamp}' | grep 'review-'`.
|
||||
|
||||
Any secrets or config maps older than 5 days are suspect and should be deleted.
|
||||
|
||||
**Useful commands:**
|
||||
|
||||
```
|
||||
# List secrets and config maps ordered by created date
|
||||
› kubectl get secret,cm --sort-by='{.metadata.creationTimestamp}' | grep 'review-'
|
||||
|
||||
# Delete all secrets that are 5 to 9 days old
|
||||
› kubectl get secret --sort-by='{.metadata.creationTimestamp}' | grep '^review-' | grep '[5-9]d$' | cut -d' ' -f1 | xargs kubectl delete secret
|
||||
|
||||
# Delete all secrets that are 10 to 99 days old
|
||||
› kubectl get secret --sort-by='{.metadata.creationTimestamp}' | grep '^review-' | grep '[1-9][0-9]d$' | cut -d' ' -f1 | xargs kubectl delete secret
|
||||
|
||||
# Delete all config maps that are 5 to 9 days old
|
||||
› kubectl get cm --sort-by='{.metadata.creationTimestamp}' | grep 'review-' | grep -v 'dns-gitlab-review-app' | grep '[5-9]d$' | cut -d' ' -f1 | xargs kubectl delete cm
|
||||
|
||||
# Delete all config maps that are 10 to 99 days old
|
||||
› kubectl get cm --sort-by='{.metadata.creationTimestamp}' | grep 'review-' | grep -v 'dns-gitlab-review-app' | grep '[1-9][0-9]d$' | cut -d' ' -f1 | xargs kubectl delete cm
|
||||
```
|
||||
|
||||
### Using K9s
|
||||
|
||||
[K9s] is a powerful command line dashboard which allows you to filter by labels. This can help identify trends with apps exceeding the [review-app resource requests](https://gitlab.com/gitlab-org/gitlab/blob/master/scripts/review_apps/base-config.yaml). Kubernetes will schedule pods to nodes based on resource requests and allow for CPU usage up to the limits.
|
||||
|
||||
- In K9s you can sort or add filters by typing the `/` character
|
||||
- `-lrelease=<review-app-slug>` - filters down to all pods for a release. This aids in determining what is having issues in a single deployment
|
||||
- `-lapp=<app>` - filters down to all pods for a specific app. This aids in determining resource usage by app.
|
||||
- You can scroll to a Kubernetes resource and hit `d`(describe), `s`(shell), `l`(logs) for a deeper inspection
|
||||
|
||||
![K9s](img/k9s.png)
|
||||
|
||||
|
|
|
@ -223,6 +223,46 @@ The following table describes details of your subscription for groups:
|
|||
| Subscription start date | Date your subscription started. If this is for a Free plan, is the date you transitioned off your group's paid plan. |
|
||||
| Subscription end date | Date your current subscription will end. Does not apply to Free plans. |
|
||||
|
||||
#### CI pipeline minutes
|
||||
|
||||
CI pipeline minutes are the execution time for your [pipelines](../ci/pipelines.md) on our shared runners. Each [GitLab.com tier](https://about.gitlab.com/pricing/) includes a monthly quota of CI pipeline minutes. The quota is applied per group, shared across all members of that group, its subgroups and nested projects. To view the usage, navigate to the group's page, then **Settings > Usage Quotas**.
|
||||
|
||||
Only pipeline minutes for our shared runners are restricted. If you have a specific runner setup for your projects, there is no limit to your build time on GitLab.com.
|
||||
|
||||
The minutes limit only applies to private projects. The available quota is reset on the first of each calendar month at midnight UTC.
|
||||
|
||||
If you reach your limit, you can [purchase additional CI minutes](#extra-shared-runners-pipeline-minutes), or upgrade your account to [Silver or Gold](https://about.gitlab.com/pricing/). Note, your own runners can still be used even if you reach your limits.
|
||||
|
||||
##### How pipeline quota usage is calculated
|
||||
|
||||
Pipeline quota usage is calculated as the sum of the duration of each individual job. This is slightly different to how pipeline _duration_ is [calculated](https://docs.gitlab.com/ee/ci/pipelines.html#how-pipeline-duration-is-calculated). Pipeline quota usage doesn't consider the intersection of jobs.
|
||||
|
||||
A simple example is:
|
||||
|
||||
A (1, 3)
|
||||
B (2, 4)
|
||||
C (6, 7)
|
||||
|
||||
In the example:
|
||||
|
||||
A begins at 1 and ends at 3.
|
||||
B begins at 2 and ends at 4.
|
||||
C begins at 6 and ends at 7.
|
||||
Visually, it can be viewed as:
|
||||
|
||||
```
|
||||
0 1 2 3 4 5 6 7
|
||||
AAAAAAA
|
||||
BBBBBBB
|
||||
CCCC
|
||||
```
|
||||
|
||||
The sum of each individual job is being calculated therefore in this example, `8` runner minutes would be used for this pipeline:
|
||||
|
||||
```
|
||||
A + B + C = 3 + 3 + 2 => 8
|
||||
```
|
||||
|
||||
#### Extra Shared Runners pipeline minutes
|
||||
|
||||
If you're using GitLab.com, you can purchase additional CI minutes so your
|
||||
|
|
BIN
doc/topics/autodevops/img/autodevops_banner_v12_6.png
Normal file
BIN
doc/topics/autodevops/img/autodevops_banner_v12_6.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
|
@ -1340,26 +1340,28 @@ spec:
|
|||
service account for your project. For help debugging this issue, see
|
||||
[Troubleshooting failed deployment jobs](../../user/project/clusters/index.md#troubleshooting).
|
||||
|
||||
### Disable the banner instance wide
|
||||
### Auto DevOps banner
|
||||
|
||||
If an administrator would like to disable the banners on an instance level, this
|
||||
feature can be disabled either through the console:
|
||||
The following Auto DevOps banner will show for maintainers+ on new projects when Auto DevOps is not enabled
|
||||
|
||||
```sh
|
||||
sudo gitlab-rails console
|
||||
```
|
||||
![Auto DevOps banner](img/autodevops_banner_v12_6.png)
|
||||
|
||||
Then run:
|
||||
The banner can be disabled:
|
||||
|
||||
```ruby
|
||||
Feature.get(:auto_devops_banner_disabled).enable
|
||||
```
|
||||
- Per user when they dismiss it.
|
||||
- Project-wide by explicitly [disabling Auto DevOps](#enablingdisabling-auto-devops).
|
||||
- By a GitLab administrator for an entire GitLab instance by either:
|
||||
- Running the following in a Rails console:
|
||||
|
||||
Or through the HTTP API with an admin access token:
|
||||
```ruby
|
||||
Feature.get(:auto_devops_banner_disabled).enable
|
||||
```
|
||||
|
||||
```sh
|
||||
curl --data "value=true" --header "PRIVATE-TOKEN: personal_access_token" https://gitlab.example.com/api/v4/features/auto_devops_banner_disabled
|
||||
```
|
||||
- Through the REST API with an admin access token:
|
||||
|
||||
```sh
|
||||
curl --data "value=true" --header "PRIVATE-TOKEN: personal_access_token" https://gitlab.example.com/api/v4/features/auto_devops_banner_disabled
|
||||
```
|
||||
|
||||
[ce-37115]: https://gitlab.com/gitlab-org/gitlab-foss/issues/37115
|
||||
[docker-in-docker]: ../../docker/using_docker_build.md#use-docker-in-docker-executor
|
||||
|
|
|
@ -143,6 +143,7 @@ using environment variables.
|
|||
| `DS_RUN_ANALYZER_TIMEOUT` | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
|
||||
| `PIP_INDEX_URL` | Base URL of Python Package Index (default `https://pypi.org/simple`). |
|
||||
| `PIP_EXTRA_INDEX_URL` | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma separated. |
|
||||
| `PIP_REQUIREMENTS_FILE` | Pip requirements file to be scanned. |
|
||||
| `MAVEN_CLI_OPTS` | List of command line arguments that will be passed to the maven analyzer during the project's build phase (see example for [using private repos](#using-private-maven-repos)). |
|
||||
| `BUNDLER_AUDIT_UPDATE_DISABLED` | Disable automatic updates for the `bundler-audit` analyzer (default: `"false"`). Useful if you're running Dependency Scanning in an offline, air-gapped environment.|
|
||||
|
||||
|
|
|
@ -122,7 +122,7 @@ Then, you could run `npm publish` either locally or via GitLab CI/CD:
|
|||
|
||||
- **GitLab CI/CD:** Set an `NPM_TOKEN` [variable](../../../ci/variables/README.md)
|
||||
under your project's **Settings > CI/CD > Variables**.
|
||||
|
||||
|
||||
### Authenticating with a CI job token
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/9104) in GitLab Premium 12.5.
|
||||
|
@ -130,7 +130,7 @@ Then, you could run `npm publish` either locally or via GitLab CI/CD:
|
|||
If you’re using NPM with GitLab CI/CD, a CI job token can be used instead of a personal access token.
|
||||
The token will inherit the permissions of the user that generates the pipeline.
|
||||
|
||||
Add a corresponding section to your `.npmrc` file:
|
||||
Add a corresponding section to your `.npmrc` file:
|
||||
|
||||
```ini
|
||||
@foo:registry=https://gitlab.com/api/v4/packages/npm/
|
||||
|
@ -226,3 +226,19 @@ And the `.npmrc` file should look like:
|
|||
//gitlab.com/api/v4/packages/npm/:_authToken=<your_oauth_token>
|
||||
@foo:registry=https://gitlab.com/api/v4/packages/npm/
|
||||
```
|
||||
|
||||
## NPM dependencies metadata
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/11867) in GitLab Premium 12.6.
|
||||
|
||||
Starting from GitLab 12.6, new packages published to the GitLab NPM Registry expose the following attributes to the NPM client:
|
||||
|
||||
- name
|
||||
- version
|
||||
- dist-tags
|
||||
- dependencies
|
||||
- dependencies
|
||||
- devDependencies
|
||||
- bundleDependencies
|
||||
- peerDependencies
|
||||
- deprecated
|
||||
|
|
|
@ -73,37 +73,40 @@ For information about setting a maximum artifact size for a project, see
|
|||
By default we look for the `.gitlab-ci.yml` file in the project's root
|
||||
directory. If needed, you can specify an alternate path and file name, including locations outside the project.
|
||||
|
||||
Hosting the configuration file in a separate project will allow stricter control of the
|
||||
configuration file. You can limit access to the project hosting the configuration to only people
|
||||
with proper authorization, and users can use the configuration for their pipelines,
|
||||
without being able to modify it.
|
||||
To customize the path:
|
||||
|
||||
If the CI configuration will stay within the repository, but in a
|
||||
location different than the default,
|
||||
the path must be relative to the root directory. Examples of valid paths and file names:
|
||||
1. Go to the project's **Settings > CI / CD**.
|
||||
1. Expand the **General pipelines** section.
|
||||
1. Provide a value in the **Custom CI configuration path** field.
|
||||
1. Click **Save changes**.
|
||||
|
||||
If the CI configuration is stored within the repository in a non-default
|
||||
location, the path must be relative to the root directory. Examples of valid
|
||||
paths and file names include:
|
||||
|
||||
- `.gitlab-ci.yml` (default)
|
||||
- `.my-custom-file.yml`
|
||||
- `my/path/.gitlab-ci.yml`
|
||||
- `my/path/.my-custom-file.yml`
|
||||
|
||||
If the CI configuration will be hosted on an external site, the URL link must end with `.yml`:
|
||||
|
||||
- `http://example.com/generate/ci/config.yml`
|
||||
|
||||
If the CI configuration will be hosted in a different project within GitLab, the path must be relative
|
||||
to the root directory in the other project, with the group and project name added to the end:
|
||||
|
||||
- `.gitlab-ci.yml@mygroup/another-project`
|
||||
- `my/path/.my-custom-file.yml@mygroup/another-project`
|
||||
|
||||
If the CI configuration will be hosted on an external site, different than the GitLab instance,
|
||||
the URL link must end with `.yml`:
|
||||
Hosting the configuration file in a separate project allows stricter control of the
|
||||
configuration file. For example:
|
||||
|
||||
- `http://example.com/generate/ci/config.yml`
|
||||
- Create a public project to host the configuration file.
|
||||
- Give write permissions on the project only to users who are allowed to edit the file.
|
||||
|
||||
The path can be customized at a project level. To customize the path:
|
||||
|
||||
1. Go to the project's **Settings > CI / CD**.
|
||||
1. Expand the **General pipelines** section.
|
||||
1. Provide a value in the **Custom CI configuration path** field.
|
||||
1. Click **Save changes**.
|
||||
Other users and projects will be able to access the configuration file without being
|
||||
able to edit it.
|
||||
|
||||
## Test coverage parsing
|
||||
|
||||
|
|
|
@ -37,6 +37,10 @@ After the forking is done, you can start working on the newly created
|
|||
repository. There, you will have full [Owner](../../permissions.md)
|
||||
access, so you can set it up as you please.
|
||||
|
||||
CAUTION: **CAUTION:**
|
||||
From GitLab 12.6 onwards, if the [visibility of an upstream project is reduced](../../../public_access/public_access.md#reducing-visibility)
|
||||
in any way, the fork relationship with all its forks will be removed.
|
||||
|
||||
## Merging upstream
|
||||
|
||||
Once you are ready to send your code back to the main project, you need
|
||||
|
|
|
@ -10,7 +10,7 @@ module Gitlab
|
|||
class Environment < ::Gitlab::Config::Entry::Node
|
||||
include ::Gitlab::Config::Entry::Configurable
|
||||
|
||||
ALLOWED_KEYS = %i[name url action on_stop kubernetes].freeze
|
||||
ALLOWED_KEYS = %i[name url action on_stop auto_stop_in kubernetes].freeze
|
||||
|
||||
entry :kubernetes, Entry::Kubernetes, description: 'Kubernetes deployment configuration.'
|
||||
|
||||
|
@ -49,6 +49,7 @@ module Gitlab
|
|||
|
||||
validates :on_stop, type: String, allow_nil: true
|
||||
validates :kubernetes, type: Hash, allow_nil: true
|
||||
validates :auto_stop_in, duration: true, allow_nil: true
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -80,6 +81,10 @@ module Gitlab
|
|||
value[:kubernetes]
|
||||
end
|
||||
|
||||
def auto_stop_in
|
||||
value[:auto_stop_in]
|
||||
end
|
||||
|
||||
def value
|
||||
case @config
|
||||
when String then { name: @config, action: 'start' }
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue