Commit cfc792b9 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 93c6764d
VERSION merge=ours VERSION merge=ours
Dangerfile gitlab-language=ruby Dangerfile gitlab-language=ruby
*.pdf filter=lfs diff=lfs merge=lfs -text
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: stages:
- sync - sync
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
- .default-before_script - .default-before_script
- .assets-compile-cache - .assets-compile-cache
- .only:changes-code-backstage-qa - .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 stage: test
dependencies: ["setup-test-env"] dependencies: ["setup-test-env"]
needs: ["setup-test-env"] needs: ["setup-test-env"]
......
...@@ -202,7 +202,7 @@ ...@@ -202,7 +202,7 @@
- name: redis:alpine - name: redis:alpine
.use-pg10: .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: services:
- name: postgres:10.9 - name: postgres:10.9
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
...@@ -216,7 +216,7 @@ ...@@ -216,7 +216,7 @@
- name: docker.elastic.co/elasticsearch/elasticsearch:5.6.12 - name: docker.elastic.co/elasticsearch/elasticsearch:5.6.12
.use-pg10-ee: .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: services:
- name: postgres:10.9 - name: postgres:10.9
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import _ from 'underscore'; import _ from 'underscore';
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import { GlButton, GlTooltipDirective, GlTooltip, GlLoadingIcon } from '@gitlab/ui'; 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 ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue'; import FileIcon from '~/vue_shared/components/file_icon.vue';
...@@ -11,7 +11,7 @@ import { __, s__, sprintf } from '~/locale'; ...@@ -11,7 +11,7 @@ import { __, s__, sprintf } from '~/locale';
import { diffViewerModes } from '~/ide/constants'; import { diffViewerModes } from '~/ide/constants';
import EditButton from './edit_button.vue'; import EditButton from './edit_button.vue';
import DiffStats from './diff_stats.vue'; import DiffStats from './diff_stats.vue';
import { scrollToElement, contentTop } from '~/lib/utils/common_utils'; import { scrollToElement } from '~/lib/utils/common_utils';
export default { export default {
components: { components: {
...@@ -127,8 +127,6 @@ export default { ...@@ -127,8 +127,6 @@ export default {
}, },
mounted() { mounted() {
polyfillSticky(this.$refs.header); polyfillSticky(this.$refs.header);
const fileHeaderHeight = this.$refs.header.clientHeight;
stickyMonitor(this.$refs.header, contentTop() - fileHeaderHeight - 1, false);
}, },
methods: { methods: {
...mapActions('diffs', [ ...mapActions('diffs', [
......
...@@ -288,7 +288,7 @@ ...@@ -288,7 +288,7 @@
list-style: none; list-style: none;
padding: 0 1px; padding: 0 1px;
a:not(.help-link), > a,
button, button,
.menu-item { .menu-item {
@include dropdown-link; @include dropdown-link;
......
...@@ -334,10 +334,6 @@ span.idiff { ...@@ -334,10 +334,6 @@ span.idiff {
padding: $gl-padding-8 $gl-padding; padding: $gl-padding-8 $gl-padding;
margin: 0; margin: 0;
border-radius: $border-radius-default $border-radius-default 0 0; border-radius: $border-radius-default $border-radius-default 0 0;
&.is-stuck {
border-radius: 0;
}
} }
.file-header-content { .file-header-content {
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
.file-title-flex-parent { .file-title-flex-parent {
border-top-left-radius: $border-radius-default; border-top-left-radius: $border-radius-default;
border-top-right-radius: $border-radius-default; border-top-right-radius: $border-radius-default;
box-shadow: 0 -2px 0 0 var(--white);
cursor: pointer; cursor: pointer;
@media (min-width: map-get($grid-breakpoints, md)) { @media (min-width: map-get($grid-breakpoints, md)) {
......
...@@ -29,6 +29,8 @@ ...@@ -29,6 +29,8 @@
.border-color-default { border-color: $border-color; } .border-color-default { border-color: $border-color; }
.box-shadow-default { box-shadow: 0 2px 4px 0 $black-transparent; } .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-w-64 { width: px-to-rem($grid-size * 8); }
.gl-h-64 { height: 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; } .gl-bg-blue-500 { @include gl-bg-blue-500; }
...@@ -7,14 +7,15 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -7,14 +7,15 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :authorize_read_environment! before_action :authorize_read_environment!
before_action :authorize_create_environment!, only: [:new, :create] before_action :authorize_create_environment!, only: [:new, :create]
before_action :authorize_stop_environment!, only: [:stop] 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 :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 :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 before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do
push_frontend_feature_flag(:prometheus_computed_alerts) push_frontend_feature_flag(:prometheus_computed_alerts)
end end
after_action :expire_etag_cache, only: [:cancel_auto_stop]
def index def index
@environments = project.environments @environments = project.environments
...@@ -104,6 +105,27 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -104,6 +105,27 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end end
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 def terminal
# Currently, this acts as a hint to load the terminal details into the cache # 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 # if they aren't there already. In the future, users will need these details
...@@ -175,8 +197,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -175,8 +197,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end end
def expire_etag_cache def expire_etag_cache
return if request.format.json?
# this forces to reload json content # this forces to reload json content
Gitlab::EtagCaching::Store.new.tap do |store| Gitlab::EtagCaching::Store.new.tap do |store|
store.touch(project_environments_path(project, format: :json)) store.touch(project_environments_path(project, format: :json))
...@@ -222,6 +242,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -222,6 +242,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController
def authorize_stop_environment! def authorize_stop_environment!
access_denied! unless can?(current_user, :stop_environment, environment) access_denied! unless can?(current_user, :stop_environment, environment)
end end
def authorize_update_environment!
access_denied! unless can?(current_user, :update_environment, environment)
end
end end
Projects::EnvironmentsController.prepend_if_ee('EE::Projects::EnvironmentsController') Projects::EnvironmentsController.prepend_if_ee('EE::Projects::EnvironmentsController')
...@@ -8,7 +8,6 @@ class Projects::PagesDomainsController < Projects::ApplicationController ...@@ -8,7 +8,6 @@ class Projects::PagesDomainsController < Projects::ApplicationController
before_action :domain, except: [:new, :create] before_action :domain, except: [:new, :create]
def show def show
redirect_to edit_project_pages_domain_path(@project, @domain)
end end
def new def new
...@@ -24,17 +23,18 @@ class Projects::PagesDomainsController < Projects::ApplicationController ...@@ -24,17 +23,18 @@ class Projects::PagesDomainsController < Projects::ApplicationController
flash[:alert] = 'Failed to verify domain ownership' flash[:alert] = 'Failed to verify domain ownership'
end end
redirect_to edit_project_pages_domain_path(@project, @domain) redirect_to project_pages_domain_path(@project, @domain)
end end
def edit def edit
redirect_to project_pages_domain_path(@project, @domain)
end end
def create def create
@domain = @project.pages_domains.create(create_params) @domain = @project.pages_domains.create(create_params)
if @domain.valid? if @domain.valid?
redirect_to edit_project_pages_domain_path(@project, @domain) redirect_to project_pages_domain_path(@project, @domain)
else else
render 'new' render 'new'
end end
...@@ -46,7 +46,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController ...@@ -46,7 +46,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
status: :found, status: :found,
notice: 'Domain was updated' notice: 'Domain was updated'
else else
render 'edit' render 'show'
end end
end end
...@@ -68,7 +68,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController ...@@ -68,7 +68,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
flash[:alert] = @domain.errors.full_messages.join(', ') flash[:alert] = @domain.errors.full_messages.join(', ')
end end
redirect_to edit_project_pages_domain_path(@project, @domain) redirect_to project_pages_domain_path(@project, @domain)
end end
private private
......
...@@ -13,7 +13,7 @@ module Projects ...@@ -13,7 +13,7 @@ module Projects
Projects::UpdateService.new(project, current_user, update_params).tap do |service| Projects::UpdateService.new(project, current_user, update_params).tap do |service|
result = service.execute result = service.execute
if result[:status] == :success 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) run_autodevops_pipeline(service)
...@@ -39,7 +39,7 @@ module Projects ...@@ -39,7 +39,7 @@ module Projects
def reset_registration_token def reset_registration_token
@project.reset_runners_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 redirect_to namespace_project_settings_ci_cd_path
end end
...@@ -65,12 +65,14 @@ module Projects ...@@ -65,12 +65,14 @@ module Projects
return unless service.run_auto_devops_pipeline? return unless service.run_auto_devops_pipeline?
if @project.empty_repo? 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 return
end end
CreatePipelineWorker.perform_async(project.id, current_user.id, project.default_branch, :web, ignore_skip_ci: true, save_on_errors: false) 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 end
def define_variables def define_variables
......
# 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
# 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
# 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
# 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 ...@@ -15,6 +15,8 @@ module Types
Types::IssueType Types::IssueType
when MergeRequest when MergeRequest
Types::MergeRequestType Types::MergeRequestType
when Snippet
Types::SnippetType
else else
raise "Unknown GraphQL type for #{object}" raise "Unknown GraphQL type for #{object}"
end end
......
...@@ -10,13 +10,19 @@ module Types ...@@ -10,13 +10,19 @@ module Types
:remove_pages, :read_project, :create_merge_request_in, :remove_pages, :read_project, :create_merge_request_in,
:read_wiki, :read_project_member, :create_issue, :upload_file, :read_wiki, :read_project_member, :create_issue, :upload_file,
:read_cycle_analytics, :download_code, :download_wiki_code, :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, :request_access, :create_pipeline, :create_pipeline_schedule,
:create_merge_request_from, :create_wiki, :push_code, :create_merge_request_from, :create_wiki, :push_code,
:create_deployment, :push_to_delete_protected_branch, :create_deployment, :push_to_delete_protected_branch,
:admin_wiki, :admin_project, :update_pages, :admin_wiki, :admin_project, :update_pages,
:admin_remote_mirror, :create_label, :update_wiki, :destroy_wiki, :admin_remote_mirror, :create_label, :update_wiki, :destroy_wiki,
:create_pages, :destroy_pages, :read_pages_content, :admin_operations :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 end
end end
......
# 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
# 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 ...@@ -151,5 +151,11 @@ module Types
null: true, null: true,
description: 'Detailed version of a Sentry error on the project', description: 'Detailed version of a Sentry error on the project',
resolver: Resolvers::ErrorTracking::SentryDetailedErrorResolver resolver: Resolvers::ErrorTracking::SentryDetailedErrorResolver
field :snippets,
Types::SnippetType.connection_type,
null: true,
description: 'Snippets of the project',
resolver: Resolvers::Projects::SnippetsResolver
end end
end end
...@@ -29,6 +29,12 @@ module Types ...@@ -29,6 +29,12 @@ module Types
resolver: Resolvers::MetadataResolver, resolver: Resolvers::MetadataResolver,
description: 'Metadata about GitLab' 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 field :echo, GraphQL::STRING_TYPE, null: false, resolver: Resolvers::EchoResolver # rubocop:disable Graphql/Descriptions
end end
end end
# 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
# frozen_string_literal: true
module Types
module Snippets
class TypeEnum < BaseEnum
value 'personal', value: 'personal'
value 'project', value: 'project'
end
end
end
# 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 ...@@ -8,6 +8,8 @@ module Types
present_using UserPresenter present_using UserPresenter
expose_permissions Types::PermissionTypes::User
field :name, GraphQL::STRING_TYPE, null: false, field :name, GraphQL::STRING_TYPE, null: false,
description: 'Human-readable name of the user' description: 'Human-readable name of the user'
field :username, GraphQL::STRING_TYPE, null: false, field :username, GraphQL::STRING_TYPE, null: false,
...@@ -19,5 +21,11 @@ module Types ...@@ -19,5 +21,11 @@ module Types
field :todos, Types::TodoType.connection_type, null: false, field :todos, Types::TodoType.connection_type, null: false,
resolver: Resolvers::TodoResolver, resolver: Resolvers::TodoResolver,
description: 'Todos of the user' 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
end end
...@@ -32,6 +32,18 @@ module Emails ...@@ -32,6 +32,18 @@ module Emails
mail(to: @user.notification_email, subject: subject("GPG key was added to your account")) mail(to: @user.notification_email, subject: subject("GPG key was added to your account"))
end end
# rubocop: enable CodeReuse/ActiveRecord # 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
end end
......
...@@ -13,15 +13,21 @@ module Clusters ...@@ -13,15 +13,21 @@ module Clusters
include ::Clusters::Concerns::ApplicationStatus include ::Clusters::Concerns::ApplicationStatus
include ::Clusters::Concerns::ApplicationVersion include ::Clusters::Concerns::ApplicationVersion
include ::Clusters::Concerns::ApplicationData include ::Clusters::Concerns::ApplicationData
include AfterCommitQueue
default_value_for :version, VERSION 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 state_machine :status do
after_transition any => [:installed] do |application| after_transition any => [:installed] do |application|
application.cluster.projects.each do |project| application.run_after_commit do
project.find_or_initialize_service('prometheus').update!(active: true) Clusters::Applications::ActivateServiceWorker
.perform_async(application.cluster_id, ::PrometheusService.to_param) # rubocop:disable CodeReuse/ServiceClass
end end
end end
end end
...@@ -98,9 +104,8 @@ module Clusters ...@@ -98,9 +104,8 @@ module Clusters
private private
def disable_prometheus_integration def disable_prometheus_integration
cluster.projects.each do |project| ::Clusters::Applications::DeactivateServiceWorker
project.prometheus_service&.update!(active: false) .perform_async(cluster_id, ::PrometheusService.to_param) # rubocop:disable CodeReuse/ServiceClass
end
end end
def kube_client def kube_client
......
...@@ -34,6 +34,7 @@ module Clusters ...@@ -34,6 +34,7 @@ module Clusters
has_many :cluster_groups, class_name: 'Clusters::Group' has_many :cluster_groups, class_name: 'Clusters::Group'
has_many :groups, through: :cluster_groups, class_name: '::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 # we force autosave to happen when we save `Cluster` model
has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true
...@@ -177,6 +178,13 @@ module Clusters ...@@ -177,6 +178,13 @@ module Clusters
end end
end end
def all_projects
return projects if project_type?
return groups_projects if group_type?
::Project.all
end
def status_name def status_name
return cleanup_status_name if cleanup_errored? return cleanup_status_name if cleanup_errored?
return :cleanup_ongoing unless cleanup_not_started? return :cleanup_ongoing unless cleanup_not_started?
......
...@@ -17,6 +17,7 @@ module Ci ...@@ -17,6 +17,7 @@ module Ci
delegate :timeout, to: :metadata, prefix: true, allow_nil: true delegate :timeout, to: :metadata, prefix: true, allow_nil: true
delegate :interruptible, to: :metadata, prefix: false, allow_nil: true delegate :interruptible, to: :metadata, prefix: false, allow_nil: true
delegate :has_exposed_artifacts?, 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 before_create :ensure_metadata
end end
...@@ -47,8 +48,11 @@ module Ci ...@@ -47,8 +48,11 @@ module Ci
def options=(value) def options=(value)
write_metadata_attribute(:options, :config_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.tap do |metadata|
ensure_metadata.has_exposed_artifacts = value&.dig(:artifacts, :expose_as).present? # 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 end
def yaml_variables=(value) def yaml_variables=(value)
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
module Expirable module Expirable
extend ActiveSupport::Concern extend ActiveSupport::Concern
DAYS_TO_EXPIRE = 7
included do included do
scope :expired, -> { where('expires_at <= ?', Time.current) } scope :expired, -> { where('expires_at <= ?', Time.current) }
end end
...@@ -16,6 +18,6 @@ module Expirable ...@@ -16,6 +18,6 @@ module Expirable
end end
def expires_soon? def expires_soon?
expires? && expires_at < 7.days.from_now expires? && expires_at < DAYS_TO_EXPIRE.days.from_now
end end
end end
...@@ -162,6 +162,10 @@ class Environment < ApplicationRecord ...@@ -162,6 +162,10 @@ class Environment < ApplicationRecord
stop_action&.play(current_user) stop_action&.play(current_user)
end end
def reset_auto_stop
update_column(:auto_stop_at, nil)
end
def actions_for(environment) def actions_for(environment)
return [] unless manual_actions return [] unless manual_actions
...@@ -261,6 +265,17 @@ class Environment < ApplicationRecord ...@@ -261,6 +265,17 @@ class Environment < ApplicationRecord
end end
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 private
def generate_slug def generate_slug
......
...@@ -341,6 +341,6 @@ class Milestone < ApplicationRecord ...@@ -341,6 +341,6 @@ class Milestone < ApplicationRecord
end end
def issues_finder_params 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
end end
...@@ -16,6 +16,7 @@ class PersonalAccessToken < ApplicationRecord ...@@ -16,6 +16,7 @@ class PersonalAccessToken < ApplicationRecord
before_save :ensure_token before_save :ensure_token
scope :active, -> { where("revoked = false AND (expires_at >= NOW() OR expires_at IS NULL)") } 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 :inactive, -> { where("revoked = true OR expires_at < NOW()") }
scope :with_impersonation, -> { where(impersonation: true) } scope :with_impersonation, -> { where(impersonation: true) }
scope :without_impersonation, -> { where(impersonation: false) } scope :without_impersonation, -> { where(impersonation: false) }
......
...@@ -404,6 +404,7 @@ class Project < ApplicationRecord ...@@ -404,6 +404,7 @@ class Project < ApplicationRecord
scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) } 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_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
scope :with_statistics, -> { includes(:statistics) } 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_shared_runners, -> { where(shared_runners_enabled: true) }
scope :with_container_registry, -> { where(container_registry_enabled: true) } scope :with_container_registry, -> { where(container_registry_enabled: true) }
scope :inside_path, ->(path) do scope :inside_path, ->(path) do
...@@ -1256,8 +1257,9 @@ class Project < ApplicationRecord ...@@ -1256,8 +1257,9 @@ class Project < ApplicationRecord
def all_clusters def all_clusters
group_clusters = Clusters::Cluster.joins(:groups).where(cluster_groups: { group_id: ancestors_upto } ) 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 end
def items_for(entity) def items_for(entity)
......
...@@ -88,7 +88,7 @@ class PrometheusService < MonitoringService ...@@ -88,7 +88,7 @@ class PrometheusService < MonitoringService
return false if template? return false if template?
return false unless project return false unless project
project.clusters.enabled.any? { |cluster| cluster.application_prometheus_available? } project.all_clusters.enabled.any? { |cluster| cluster.application_prometheus_available? }
end end
def allow_local_api_url? def allow_local_api_url?
......
...@@ -310,6 +310,13 @@ class User < ApplicationRecord ...@@ -310,6 +310,13 @@ class User < ApplicationRecord
scope :with_dashboard, -> (dashboard) { where(dashboard: dashboard) } scope :with_dashboard, -> (dashboard) { where(dashboard: dashboard) }
scope :with_public_profile, -> { where(private_profile: false) } 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) def self.with_visible_profile(user)
return with_public_profile if user.nil? return with_public_profile if user.nil?
......
...@@ -27,4 +27,7 @@ class PersonalSnippetPolicy < BasePolicy ...@@ -27,4 +27,7 @@ class PersonalSnippetPolicy < BasePolicy
rule { can?(:create_note) }.enable :award_emoji rule { can?(:create_note) }.enable :award_emoji
rule { can?(:read_all_resources) }.enable :read_personal_snippet 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 end
...@@ -262,6 +262,7 @@ class ProjectPolicy < BasePolicy ...@@ -262,6 +262,7 @@ class ProjectPolicy < BasePolicy
enable :update_container_image enable :update_container_image
enable :destroy_container_image enable :destroy_container_image
enable :create_environment enable :create_environment
enable :update_environment
enable :create_deployment enable :create_deployment
enable :update_deployment enable :update_deployment
enable :create_release enable :create_release
...@@ -278,8 +279,6 @@ class ProjectPolicy < BasePolicy ...@@ -278,8 +279,6 @@ class ProjectPolicy < BasePolicy
enable :admin_board enable :admin_board
enable :push_to_delete_protected_branch enable :push_to_delete_protected_branch
enable :update_project_snippet enable :update_project_snippet
enable :update_environment
enable :update_deployment
enable :admin_project_snippet enable :admin_project_snippet
enable :admin_project_member enable :admin_project_member
enable :admin_note enable :admin_note
......
...@@ -45,6 +45,9 @@ class ProjectSnippetPolicy < BasePolicy ...@@ -45,6 +45,9 @@ class ProjectSnippetPolicy < BasePolicy
end end
rule { ~can?(:read_project_snippet) }.prevent :create_note 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 end
ProjectSnippetPolicy.prepend_if_ee('EE::ProjectSnippetPolicy') ProjectSnippetPolicy.prepend_if_ee('EE::ProjectSnippetPolicy')
# 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 ...@@ -24,6 +24,10 @@ class EnvironmentEntity < Grape::Entity
stop_project_environment_path(environment.project, environment) stop_project_environment_path(environment.project, environment)
end 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| expose :cluster_type, if: ->(environment, _) { cluster_platform_kubernetes? } do |environment|
cluster.cluster_type cluster.cluster_type
end end
...@@ -37,6 +41,7 @@ class EnvironmentEntity < Grape::Entity ...@@ -37,6 +41,7 @@ class EnvironmentEntity < Grape::Entity
end end
expose :created_at, :updated_at expose :created_at, :updated_at
expose :auto_stop_at, expose_nil: false
expose :can_stop do |environment| expose :can_stop do |environment|
environment.available? && can?(current_user, :stop_environment, environment) environment.available? && can?(current_user, :stop_environment, environment)
...@@ -54,6 +59,10 @@ class EnvironmentEntity < Grape::Entity ...@@ -54,6 +59,10 @@ class EnvironmentEntity < Grape::Entity
can?(request.current_user, :create_environment_terminal, environment) can?(request.current_user, :create_environment_terminal, environment)
end end
def can_update_environment?
can?(current_user, :update_environment, environment)
end
def cluster_platform_kubernetes? def cluster_platform_kubernetes?
deployment_platform && deployment_platform.is_a?(Clusters::Platforms::Kubernetes) deployment_platform && deployment_platform.is_a?(Clusters::Platforms::Kubernetes)
end end
......
...@@ -29,6 +29,7 @@ module Deployments ...@@ -29,6 +29,7 @@ module Deployments
environment.external_url = url environment.external_url = url
end end
renew_auto_stop_in
environment.fire_state_event(action) environment.fire_state_event(action)
if environment.save && !environment.stopped? if environment.save && !environment.stopped?
...@@ -63,6 +64,12 @@ module Deployments ...@@ -63,6 +64,12 @@ module Deployments
def action def action
environment_options[:action] || 'start' environment_options[:action] || 'start'
end end
def renew_auto_stop_in
return unless deployable
environment.auto_stop_in = deployable.environment_auto_stop_in
end
end end
end end
......
# 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 ...@@ -58,6 +58,14 @@ class NotificationService
end end
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: # When create an issue we should send an email to:
# #
# * issue assignee if their notification level is not Disabled # * issue assignee if their notification level is not Disabled
......
...@@ -6,17 +6,19 @@ ...@@ -6,17 +6,19 @@
%span.spinner.spinner-dark.spinner-sm{ 'aria-label': 'Loading' } %span.spinner.spinner-dark.spinner-sm{ 'aria-label': 'Loading' }
%span.prepend-left-4= s_('ClusterIntegration|Kubernetes cluster is being created...') %span.prepend-left-4= s_('ClusterIntegration|Kubernetes cluster is being created...')
.hidden.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' } .hidden.row.js-cluster-api-unreachable.gl-alert.gl-alert-warning{ role: 'alert' }
.col-11 = 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.') = 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' } .hidden.js-cluster-authentication-failure.js-cluster-api-unreachable.gl-alert.gl-alert-warning{ role: 'alert' }
.col-11 = 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.') = 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' } .hidden.js-cluster-success.bs-callout.bs-callout-success{ role: 'alert' }
= s_("ClusterIntegration|Kubernetes cluster was successfully created.") = s_("ClusterIntegration|Kubernetes cluster was successfully created.")
-# We currently only support `alert`, `notice`, `success`, 'toast'
.flash-container.flash-container-page.sticky .flash-container.flash-container-page.sticky
-# We currently only support `alert`, `notice`, `success`
- flash.each do |key, value| - flash.each do |key, value|
-# Don't show a flash message if the message is nil - if key == 'toast' && value
- if value .js-toast-message{ data: { message: value } }
- elsif value
%div{ class: "flash-#{key} mb-2" } %div{ class: "flash-#{key} mb-2" }
%span= value %span= value
%div{ class: "close-icon-wrapper js-close-icon" } %div{ class: "close-icon-wrapper js-close-icon" }
......
...@@ -144,8 +144,16 @@ ...@@ -144,8 +144,16 @@
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
= issue_tracker.title = 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 - 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 = link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests', data: { qa_selector: 'merge_requests_link' } do
.nav-icon-container .nav-icon-container
= sprite_icon('git-merge') = sprite_icon('git-merge')
......
%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 }
<%= _('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 @@ ...@@ -5,7 +5,7 @@
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= stylesheet_link_tag 'page_bundles/xterm' = 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 } #stop-environment-modal.modal.fade{ tabindex: -1 }
.modal-dialog .modal-dialog
.modal-content .modal-content
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
= render 'projects/environments/metrics_button', environment: @environment = render 'projects/environments/metrics_button', environment: @environment
- if can?(current_user, :update_environment, @environment) - if can?(current_user, :update_environment, @environment)
= link_to _('Edit'), edit_project_environment_path(@project, @environment), class: 'btn' = 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', = button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal',
target: '#stop-environment-modal' } do target: '#stop-environment-modal' } do
= sprite_icon('stop') = sprite_icon('stop')
......
...@@ -21,11 +21,11 @@ ...@@ -21,11 +21,11 @@
%span.badge.badge-danger %span.badge.badge-danger
= s_('GitLabPages|Expired') = s_('GitLabPages|Expired')
%div %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" = 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? - if verification_enabled && domain.unverified?
%li.list-group-item.bs-callout-warning %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 - 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, = 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, link_start: details_link_start,
......
- 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'
- add_to_breadcrumbs _("Pages"), project_pages_path(@project) - add_to_breadcrumbs _("Pages"), project_pages_path(@project)
- breadcrumb_title @domain.domain - breadcrumb_title @domain.domain
- page_title "#{@domain.domain}", _('Pages Domains') - page_title @domain.domain
- dns_record = "#{@domain.domain} CNAME #{@domain.project.pages_subdomain}.#{Settings.pages.host}."
- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled? - verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
...@@ -11,51 +10,12 @@ ...@@ -11,51 +10,12 @@
.container-fluid.container-limited .container-fluid.container-limited
= _("This domain is not verified. You will need to verify ownership before access is enabled.") = _("This domain is not verified. You will need to verify ownership before access is enabled.")
%h3.page-title.with-button %h3.page-title
= link_to _('Edit'), edit_project_pages_domain_path(@project, @domain), class: 'btn btn-success float-right' = _('Pages Domain')
= _("Pages Domain") = render 'projects/pages_domains/helper_text'
%div
.table-holder = form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f|
%table.table = render 'form', { f: f }
%tr .form-actions.d-flex.justify-content-between
%td = f.submit _('Save Changes'), class: "btn btn-success"
= _("Domain") = link_to _('Cancel'), project_pages_path(@project), class: 'btn btn-default btn-inverse'
%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
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
.block.milestone{ data: { qa_selector: 'milestone_block' } } .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' } } .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') = 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? - if milestone.present?
= milestone[:title] = milestone[:title]
- else - else
...@@ -45,7 +45,7 @@ ...@@ -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: "" } = 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 .value.hide-collapsed
- if milestone.present? - 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 - else
%span.no-value %span.no-value
= _('None') = _('None')
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
- cronjob:pages_domain_verification_cron - cronjob:pages_domain_verification_cron
- cronjob:pages_domain_removal_cron - cronjob:pages_domain_removal_cron
- cronjob:pages_domain_ssl_renewal_cron - cronjob:pages_domain_ssl_renewal_cron
- cronjob:personal_access_tokens_expiring
- cronjob:pipeline_schedule - cronjob:pipeline_schedule
- cronjob:prune_old_events - cronjob:prune_old_events
- cronjob:remove_expired_group_links - cronjob:remove_expired_group_links
...@@ -51,6 +52,8 @@ ...@@ -51,6 +52,8 @@
- gcp_cluster:clusters_cleanup_app - gcp_cluster:clusters_cleanup_app
- gcp_cluster:clusters_cleanup_project_namespace - gcp_cluster:clusters_cleanup_project_namespace
- gcp_cluster:clusters_cleanup_service_account - gcp_cluster:clusters_cleanup_service_account
- gcp_cluster:clusters_applications_activate_service
- gcp_cluster:clusters_applications_deactivate_service
- github_import_advance_stage - github_import_advance_stage
- github_importer:github_import_import_diff_note - github_importer:github_import_import_diff_note
......
# 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
# 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
# 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
---
title: Activate projects Prometheus service integration when Prometheus managed application is installed on shared cluster
merge_request:
author:
type: fixed
---
title: Display Labels item in sidebar when Issues are disabled
merge_request: 20817
author:
type: fixed
---
title: Convert flash alerts to toasts
merge_request: 20356
author:
type: added
---
title: Update padding for cluster alert warning
merge_request: 20036
author: George Tsiolis
type: fixed
---
title: Retrieve issues from subgroups when rendering group milestone
merge_request: 21024
author:
type: fixed
---
title: Add dependency scanning flag for specifying pip requirements file for scanning.
merge_request: 21219
author:
type: added
---
title: Add Snippet GraphQL resolver endpoints
merge_request: 20613
author:
type: added
---
title: Add Personal Access Token expiration reminder
merge_request: 19296
author:
type: added
---
title: Add feature to allow specifying userWithId strategies per environment spec
merge_request: 20325
author:
type: added
...@@ -366,6 +366,9 @@ production: &base ...@@ -366,6 +366,9 @@ production: &base
# Send admin emails once a week # Send admin emails once a week
admin_email_worker: admin_email_worker:
cron: "0 0 * * 0" 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 # Remove outdated repository archives
repository_archive_cache_worker: repository_archive_cache_worker:
......
...@@ -407,6 +407,9 @@ Settings.cron_jobs['repository_check_worker']['job_class'] = 'RepositoryCheck::D ...@@ -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'] ||= Settingslogic.new({})
Settings.cron_jobs['admin_email_worker']['cron'] ||= '0 0 * * 0' Settings.cron_jobs['admin_email_worker']['cron'] ||= '0 0 * * 0'
Settings.cron_jobs['admin_email_worker']['job_class'] = 'AdminEmailWorker' 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'] ||= Settingslogic.new({})
Settings.cron_jobs['repository_archive_cache_worker']['cron'] ||= '0 * * * *' Settings.cron_jobs['repository_archive_cache_worker']['cron'] ||= '0 * * * *'
Settings.cron_jobs['repository_archive_cache_worker']['job_class'] = 'RepositoryArchiveCacheWorker' Settings.cron_jobs['repository_archive_cache_worker']['job_class'] = 'RepositoryArchiveCacheWorker'
......
...@@ -224,6 +224,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -224,6 +224,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :environments, except: [:destroy] do resources :environments, except: [:destroy] do
member do member do
post :stop post :stop
post :cancel_auto_stop
get :terminal get :terminal
get :metrics get :metrics
get :additional_metrics get :additional_metrics
......
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
...@@ -717,6 +717,7 @@ ActiveRecord::Schema.define(version: 2019_12_04_093410) do ...@@ -717,6 +717,7 @@ ActiveRecord::Schema.define(version: 2019_12_04_093410) do
t.jsonb "config_options" t.jsonb "config_options"
t.jsonb "config_variables" t.jsonb "config_variables"
t.boolean "has_exposed_artifacts" 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", 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_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)" 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 ...@@ -1447,6 +1448,7 @@ ActiveRecord::Schema.define(version: 2019_12_04_093410) do
t.string "environment_type" t.string "environment_type"
t.string "state", default: "available", null: false t.string "state", default: "available", null: false
t.string "slug", 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 ["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", "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 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 ...@@ -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 t.index ["package_id"], name: "index_packages_conan_metadata_on_package_id", unique: true
end 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| create_table "packages_maven_metadata", force: :cascade do |t|
t.bigint "package_id", null: false t.bigint "package_id", null: false
t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "created_at", null: false
...@@ -2847,12 +2863,6 @@ ActiveRecord::Schema.define(version: 2019_12_04_093410) do ...@@ -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" t.index ["package_id", "file_name"], name: "index_packages_package_files_on_package_id_and_file_name"
end 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| create_table "packages_package_tags", force: :cascade do |t|
t.integer "package_id", null: false t.integer "package_id", null: false
t.string "name", limit: 255, null: false t.string "name", limit: 255, null: false
...@@ -2867,6 +2877,7 @@ ActiveRecord::Schema.define(version: 2019_12_04_093410) do ...@@ -2867,6 +2877,7 @@ ActiveRecord::Schema.define(version: 2019_12_04_093410) do
t.string "version" t.string "version"
t.integer "package_type", limit: 2, null: false 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 ["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" t.index ["project_id"], name: "index_packages_packages_on_project_id"
end end
...@@ -2929,6 +2940,7 @@ ActiveRecord::Schema.define(version: 2019_12_04_093410) do ...@@ -2929,6 +2940,7 @@ ActiveRecord::Schema.define(version: 2019_12_04_093410) do
t.string "scopes", default: "--- []\n", null: false t.string "scopes", default: "--- []\n", null: false
t.boolean "impersonation", default: false, null: false t.boolean "impersonation", default: false, null: false
t.string "token_digest" 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 ["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", "expires_at"], name: "index_pat_on_user_id_and_expires_at"
t.index ["user_id"], name: "index_personal_access_tokens_on_user_id" 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 ...@@ -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 "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_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_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_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_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_package_tags", "packages_packages", column: "package_id", on_delete: :cascade
add_foreign_key "packages_packages", "projects", on_delete: :cascade add_foreign_key "packages_packages", "projects", on_delete: :cascade
add_foreign_key "pages_domain_acme_orders", "pages_domains", 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 ...@@ -51,7 +51,7 @@ We need to make Docker Registry send notification events to the
'threshold' => 5, 'threshold' => 5,
'backoff' => '1s', 'backoff' => '1s',
'headers' => { '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 ...@@ -59,7 +59,7 @@ We need to make Docker Registry send notification events to the
NOTE: **Note:** NOTE: **Note:**
If you use an external Registry (not the one integrated with GitLab), you must add 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 notification secret in `registry.notification_secret` section of
`/etc/gitlab/gitlab.rb` file. `/etc/gitlab/gitlab.rb` file.
...@@ -100,7 +100,7 @@ generate a short-lived JWT that is pull-only-capable to access the ...@@ -100,7 +100,7 @@ generate a short-lived JWT that is pull-only-capable to access the
```ruby ```ruby
gitlab_rails['geo_registry_replication_enabled'] = true 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: 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: ...@@ -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. `namespace` : Within a namespace it is also possible to fetch `projects`.
1. `currentUser`: Information about the currently logged in user. 1. `currentUser`: Information about the currently logged in user.
1. `metaData`: Metadata about GitLab and the GraphQL API. 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 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). [`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 { ...@@ -4512,6 +4512,41 @@ type Project {
""" """
sharedRunnersEnabled: Boolean 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 (deprecated) Does this project have snippets enabled?. Use `snippets_access_level` instead
""" """
...@@ -4675,9 +4710,9 @@ type ProjectPermissions { ...@@ -4675,9 +4710,9 @@ type ProjectPermissions {
createPipelineSchedule: Boolean! 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 Whether or not a user can perform `create_wiki` on this resource
...@@ -4882,6 +4917,61 @@ type Query { ...@@ -4882,6 +4917,61 @@ type Query {
""" """
fullPath: ID! fullPath: ID!
): Project ): 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 { ...@@ -5137,6 +5227,193 @@ enum SentryErrorStatus {
UNRESOLVED 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 { type Submodule implements Entry {
flatPath: String! flatPath: String!
id: ID! id: ID!
...@@ -5602,6 +5879,11 @@ type TreeEntryEdge { ...@@ -5602,6 +5879,11 @@ type TreeEntryEdge {
node: TreeEntry node: TreeEntry
} }
enum TypeEnum {
personal
project
}
""" """
Autogenerated input type of UpdateEpic Autogenerated input type of UpdateEpic
""" """
...@@ -5740,6 +6022,46 @@ type User { ...@@ -5740,6 +6022,46 @@ type User {
""" """
name: String! 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 Todos of the user
""" """
...@@ -5795,6 +6117,11 @@ type User { ...@@ -5795,6 +6117,11 @@ type User {
type: [TodoTargetEnum!] type: [TodoTargetEnum!]
): TodoConnection! ): TodoConnection!
"""
Permissions for the current user on the resource
"""
userPermissions: UserPermissions!
""" """
Username of the user. Unique within this instance of GitLab Username of the user. Unique within this instance of GitLab
""" """
...@@ -5839,4 +6166,17 @@ type UserEdge { ...@@ -5839,4 +6166,17 @@ type UserEdge {
The item at the end of the edge. The item at the end of the edge.
""" """
node: User node: User
}
type UserPermissions {
"""
Whether or not a user can perform `create_snippet` on this resource
"""
createSnippet: Boolean!
}
enum VisibilityScopesEnum {
internal
private
public
} }
\ No newline at end of file
...@@ -689,7 +689,6 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -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 | | `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 | | `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 | | `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 | | `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 | | `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 | | `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 ...@@ -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 | | `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 | | `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 | | `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 | | `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 | | `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 | | `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 ...@@ -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 | | `time` | Time! | Time the error frequency stats were recorded |
| `count` | Int! | Count of errors received since the previously recorded time | | `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 ### Submodule
| Name | Type | Description | | Name | Type | Description |
...@@ -892,7 +921,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -892,7 +921,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `userPermissions` | UserPermissions! | Permissions for the current user on the resource |
| `name` | String! | Human-readable name of the user | | `name` | String! | Human-readable name of the user |
| `username` | String! | Username of the user. Unique within this instance of GitLab | | `username` | String! | Username of the user. Unique within this instance of GitLab |
| `avatarUrl` | String! | URL of the user's avatar | | `avatarUrl` | String! | URL of the user's avatar |
| `webUrl` | String! | Web URL of the user | | `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 |
...@@ -86,12 +86,15 @@ In case, you have a high-priority merge request (e.g. critical patch) to be merg ...@@ -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. 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. 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 However, every time you merge a merge request immediately, it could affect the
existing merge train to be reconstructed, specifically, it regenerates expected existing merge train to be reconstructed, specifically, it regenerates expected
merge commits and pipelines. This means, merging immediately essentially wastes 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 ## Troubleshooting
......
...@@ -23,19 +23,12 @@ The goal of the Package group is to build a set of features that, within three y ...@@ -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. | | [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. | | [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. | | [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. | | [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. | | [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. | | [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. | | [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. | | [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. | | [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. | | [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. | | [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. | | [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`. ...@@ -186,22 +186,89 @@ secure note named `gitlab-{ce,ee} Review App's root password`.
`review-qa-raise-e-12chm0-migrations.1-nqwtx`. `review-qa-raise-e-12chm0-migrations.1-nqwtx`.
1. Click on the `Container logs` link. 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 If [Review App Stability](https://app.periscopedata.com/app/gitlab/496118/Engineering-Productivity-Sandbox?widget=6690556&udv=785399)
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. 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)
- [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.
- 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. ### Node count is always increasing (i.e. never stabilizing or decreasing)
- `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. **Potential cause:**
- 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 That could be a sign that the [`schedule:review-cleanup`][gitlab-ci-yml] job is
- `-lapp=<app>` - filters down to all pods for a specific app. This aids in determining resource usage by app. failing to cleanup stale Review Apps and Kubernetes resources.
- You can scroll to a Kubernetes resource and hit `d`(describe), `s`(shell), `l`(logs) for a deeper inspection
**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) ![K9s](img/k9s.png)
......
...@@ -223,6 +223,46 @@ The following table describes details of your subscription for groups: ...@@ -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 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. | | 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 #### Extra Shared Runners pipeline minutes
If you're using GitLab.com, you can purchase additional CI minutes so your If you're using GitLab.com, you can purchase additional CI minutes so your
......
...@@ -1340,26 +1340,28 @@ spec: ...@@ -1340,26 +1340,28 @@ spec:
service account for your project. For help debugging this issue, see service account for your project. For help debugging this issue, see
[Troubleshooting failed deployment jobs](../../user/project/clusters/index.md#troubleshooting). [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 The following Auto DevOps banner will show for maintainers+ on new projects when Auto DevOps is not enabled
feature can be disabled either through the console:
```sh ![Auto DevOps banner](img/autodevops_banner_v12_6.png)
sudo gitlab-rails console
```
Then run: The banner can be disabled:
```ruby - Per user when they dismiss it.
Feature.get(:auto_devops_banner_disabled).enable - 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:
```ruby
Feature.get(:auto_devops_banner_disabled).enable
```
Or through the HTTP API with an admin access token: - Through the REST API with an admin access token:
```sh ```sh
curl --data "value=true" --header "PRIVATE-TOKEN: personal_access_token" https://gitlab.example.com/api/v4/features/auto_devops_banner_disabled 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 [ce-37115]: https://gitlab.com/gitlab-org/gitlab-foss/issues/37115
[docker-in-docker]: ../../docker/using_docker_build.md#use-docker-in-docker-executor [docker-in-docker]: ../../docker/using_docker_build.md#use-docker-in-docker-executor
......
...@@ -143,6 +143,7 @@ using environment variables. ...@@ -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`. | | `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_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_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)). | | `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.| | `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: ...@@ -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) - **GitLab CI/CD:** Set an `NPM_TOKEN` [variable](../../../ci/variables/README.md)
under your project's **Settings > CI/CD > Variables**. under your project's **Settings > CI/CD > Variables**.
### Authenticating with a CI job token ### Authenticating with a CI job token
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/9104) in GitLab Premium 12.5. > [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: ...@@ -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. 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. 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 ```ini
@foo:registry=https://gitlab.com/api/v4/packages/npm/ @foo:registry=https://gitlab.com/api/v4/packages/npm/
...@@ -226,3 +226,19 @@ And the `.npmrc` file should look like: ...@@ -226,3 +226,19 @@ And the `.npmrc` file should look like:
//gitlab.com/api/v4/packages/npm/:_authToken=<your_oauth_token> //gitlab.com/api/v4/packages/npm/:_authToken=<your_oauth_token>
@foo:registry=https://gitlab.com/api/v4/packages/npm/ @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 ...@@ -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 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. 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 To customize the path:
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.
If the CI configuration will stay within the repository, but in a 1. Go to the project's **Settings > CI / CD**.
location different than the default, 1. Expand the **General pipelines** section.
the path must be relative to the root directory. Examples of valid paths and file names: 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) - `.gitlab-ci.yml` (default)
- `.my-custom-file.yml` - `.my-custom-file.yml`
- `my/path/.gitlab-ci.yml` - `my/path/.gitlab-ci.yml`
- `my/path/.my-custom-file.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 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: to the root directory in the other project, with the group and project name added to the end:
- `.gitlab-ci.yml@mygroup/another-project` - `.gitlab-ci.yml@mygroup/another-project`
- `my/path/.my-custom-file.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, Hosting the configuration file in a separate project allows stricter control of the
the URL link must end with `.yml`: configuration file. For example:
- `http://example.com/generate/ci/config.yml`
The path can be customized at a project level. To customize the path: - 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.
1. Go to the project's **Settings > CI / CD**. Other users and projects will be able to access the configuration file without being
1. Expand the **General pipelines** section. able to edit it.
1. Provide a value in the **Custom CI configuration path** field.
1. Click **Save changes**.
## Test coverage parsing ## Test coverage parsing
......
...@@ -37,6 +37,10 @@ After the forking is done, you can start working on the newly created ...@@ -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) repository. There, you will have full [Owner](../../permissions.md)
access, so you can set it up as you please. 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 ## Merging upstream
Once you are ready to send your code back to the main project, you need Once you are ready to send your code back to the main project, you need
......
...@@ -10,7 +10,7 @@ module Gitlab ...@@ -10,7 +10,7 @@ module Gitlab
class Environment < ::Gitlab::Config::Entry::Node class Environment < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable 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.' entry :kubernetes, Entry::Kubernetes, description: 'Kubernetes deployment configuration.'
...@@ -49,6 +49,7 @@ module Gitlab ...@@ -49,6 +49,7 @@ module Gitlab
validates :on_stop, type: String, allow_nil: true validates :on_stop, type: String, allow_nil: true
validates :kubernetes, type: Hash, allow_nil: true validates :kubernetes, type: Hash, allow_nil: true
validates :auto_stop_in, duration: true, allow_nil: true
end end
end end
...@@ -80,6 +81,10 @@ module Gitlab ...@@ -80,6 +81,10 @@ module Gitlab
value[:kubernetes] value[:kubernetes]
end end
def auto_stop_in
value[:auto_stop_in]
end
def value def value
case @config case @config
when String then { name: @config, action: 'start' } when String then { name: @config, action: 'start' }
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment