diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-20 18:10:29 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-20 18:10:29 +0300 |
commit | ef8c47e97e1c178291e4857314a3f53875d75062 (patch) | |
tree | 89ef0c1cd34e864aad1346a0ee07bccb81a9ac65 | |
parent | f2ebc27236ecef67e67da1da864bbf58e9b9c267 (diff) |
Add latest changes from gitlab-org/gitlab@master
94 files changed, 1566 insertions, 980 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml index 6a20ddc96c3..297eb5a5893 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -51,7 +51,6 @@ rules: # new ones, to ease migration to v7, so violations of each can be fixed # separately. vue/no-mutating-props: off - vue/one-component-per-file: off vue/no-lone-template: off # END eslint-plugin-vue@7 overrides overrides: diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 8c20f999400..a6c6f8ed977 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -8.59.0 +8.60.0 @@ -462,7 +462,7 @@ group :ed25519 do end # Gitaly GRPC protocol definitions -gem 'gitaly', '~> 13.8.0.pre.rc2' +gem 'gitaly', '~> 13.8.0.pre.rc3' gem 'grpc', '~> 1.30.2' diff --git a/Gemfile.lock b/Gemfile.lock index b370024b95b..74d88417541 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -417,7 +417,7 @@ GEM rails (>= 3.2.0) git (1.7.0) rchardet (~> 1.8) - gitaly (13.8.0.pre.rc2) + gitaly (13.8.0.pre.rc3) grpc (~> 1.0) github-markup (1.7.0) gitlab-chronic (0.10.5) @@ -1357,7 +1357,7 @@ DEPENDENCIES gettext (~> 3.3) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.3) - gitaly (~> 13.8.0.pre.rc2) + gitaly (~> 13.8.0.pre.rc3) github-markup (~> 1.7.0) gitlab-chronic (~> 0.10.5) gitlab-experiment (~> 0.4.5) diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue index 909f1afd9f6..43fe54d6e38 100644 --- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue +++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue @@ -2,60 +2,42 @@ import { GlAlert } from '@gitlab/ui'; import { __, sprintf } from '~/locale'; import ServiceDeskSetting from './service_desk_setting.vue'; -import ServiceDeskService from '../services/service_desk_service'; -import eventHub from '../event_hub'; +import axios from '~/lib/utils/axios_utils'; export default { - name: 'ServiceDeskRoot', components: { GlAlert, ServiceDeskSetting, }, - props: { + inject: { initialIsEnabled: { - type: Boolean, - required: true, + default: false, }, endpoint: { - type: String, - required: true, + default: '', }, - incomingEmail: { - type: String, - required: false, + initialIncomingEmail: { default: '', }, customEmail: { - type: String, - required: false, default: '', }, customEmailEnabled: { - type: Boolean, - required: false, + default: false, }, selectedTemplate: { - type: String, - required: false, default: '', }, outgoingName: { - type: String, - required: false, default: '', }, projectKey: { - type: String, - required: false, default: '', }, templates: { - type: Array, - required: false, - default: () => [], + default: [], }, }, - data() { return { isEnabled: this.initialIsEnabled, @@ -63,28 +45,21 @@ export default { isAlertShowing: false, alertVariant: 'danger', alertMessage: '', + incomingEmail: this.initialIncomingEmail, updatedCustomEmail: this.customEmail, }; }, - - created() { - eventHub.$on('serviceDeskEnabledCheckboxToggled', this.onEnableToggled); - eventHub.$on('serviceDeskTemplateSave', this.onSaveTemplate); - this.service = new ServiceDeskService(this.endpoint); - }, - - beforeDestroy() { - eventHub.$off('serviceDeskEnabledCheckboxToggled', this.onEnableToggled); - eventHub.$off('serviceDeskTemplateSave', this.onSaveTemplate); - }, - methods: { onEnableToggled(isChecked) { this.isEnabled = isChecked; this.incomingEmail = ''; - this.service - .toggleServiceDesk(isChecked) + const body = { + service_desk_enabled: isChecked, + }; + + return axios + .put(this.endpoint, body) .then(({ data }) => { const email = data.service_desk_address; if (isChecked && !email) { @@ -104,8 +79,16 @@ export default { onSaveTemplate({ selectedTemplate, outgoingName, projectKey }) { this.isTemplateSaving = true; - this.service - .updateTemplate({ selectedTemplate, outgoingName, projectKey }, this.isEnabled) + + const body = { + issue_template_key: selectedTemplate, + outgoing_name: outgoingName, + project_key: projectKey, + service_desk_enabled: this.isEnabled, + }; + + return axios + .put(this.endpoint, body) .then(({ data }) => { this.updatedCustomEmail = data?.service_desk_address; this.showAlert(__('Changes saved.'), 'success'); @@ -150,6 +133,8 @@ export default { :initial-project-key="projectKey" :templates="templates" :is-template-saving="isTemplateSaving" + @save="onSaveTemplate" + @toggle="onEnableToggled" /> </div> </template> diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue index a850374fc88..39d9a6a4239 100644 --- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue +++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue @@ -1,12 +1,9 @@ <script> import { GlButton, GlFormSelect, GlToggle, GlLoadingIcon, GlSprintf } from '@gitlab/ui'; import { __ } from '~/locale'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; -import eventHub from '../event_hub'; export default { - name: 'ServiceDeskSetting', components: { ClipboardButton, GlButton, @@ -15,7 +12,6 @@ export default { GlLoadingIcon, GlSprintf, }, - mixins: [glFeatureFlagsMixin()], props: { isEnabled: { type: Boolean, @@ -84,10 +80,10 @@ export default { }, methods: { onCheckboxToggle(isChecked) { - eventHub.$emit('serviceDeskEnabledCheckboxToggled', isChecked); + this.$emit('toggle', isChecked); }, onSaveTemplate() { - eventHub.$emit('serviceDeskTemplateSave', { + this.$emit('save', { selectedTemplate: this.selectedTemplate, outgoingName: this.outgoingName, projectKey: this.projectKey, @@ -111,7 +107,11 @@ export default { </label> <div v-if="isEnabled" class="row mt-3"> <div class="col-md-9 mb-0"> - <strong id="incoming-email-describer" class="d-block mb-1"> + <strong + id="incoming-email-describer" + class="gl-display-block gl-mb-1" + data-testid="incoming-email-describer" + > {{ __('Email address to use for Support Desk') }} </strong> <template v-if="email"> @@ -128,11 +128,7 @@ export default { disabled="true" /> <div class="input-group-append"> - <clipboard-button - :title="__('Copy')" - :text="email" - css-class="input-group-text qa-clipboard-button" - /> + <clipboard-button :title="__('Copy')" :text="email" css-class="input-group-text" /> </div> </div> <span v-if="hasCustomEmail" class="form-text text-muted"> diff --git a/app/assets/javascripts/projects/settings_service_desk/event_hub.js b/app/assets/javascripts/projects/settings_service_desk/event_hub.js deleted file mode 100644 index e31806ad199..00000000000 --- a/app/assets/javascripts/projects/settings_service_desk/event_hub.js +++ /dev/null @@ -1,3 +0,0 @@ -import createEventHub from '~/helpers/event_hub_factory'; - -export default createEventHub(); diff --git a/app/assets/javascripts/projects/settings_service_desk/index.js b/app/assets/javascripts/projects/settings_service_desk/index.js index 8f9828dd73d..f842ffaaa2b 100644 --- a/app/assets/javascripts/projects/settings_service_desk/index.js +++ b/app/assets/javascripts/projects/settings_service_desk/index.js @@ -3,43 +3,37 @@ import { parseBoolean } from '~/lib/utils/common_utils'; import ServiceDeskRoot from './components/service_desk_root.vue'; export default () => { - const serviceDeskRootElement = document.querySelector('.js-service-desk-setting-root'); - if (serviceDeskRootElement) { - // eslint-disable-next-line no-new - new Vue({ - el: serviceDeskRootElement, - components: { - ServiceDeskRoot, - }, - data() { - const { dataset } = serviceDeskRootElement; - return { - initialIsEnabled: parseBoolean(dataset.enabled), - endpoint: dataset.endpoint, - incomingEmail: dataset.incomingEmail, - customEmail: dataset.customEmail, - customEmailEnabled: parseBoolean(dataset.customEmailEnabled), - selectedTemplate: dataset.selectedTemplate, - outgoingName: dataset.outgoingName, - projectKey: dataset.projectKey, - templates: JSON.parse(dataset.templates), - }; - }, - render(createElement) { - return createElement('service-desk-root', { - props: { - initialIsEnabled: this.initialIsEnabled, - endpoint: this.endpoint, - incomingEmail: this.incomingEmail, - customEmail: this.customEmail, - customEmailEnabled: this.customEmailEnabled, - selectedTemplate: this.selectedTemplate, - outgoingName: this.outgoingName, - projectKey: this.projectKey, - templates: this.templates, - }, - }); - }, - }); + const el = document.querySelector('.js-service-desk-setting-root'); + + if (!el) { + return false; } + + const { + customEmail, + customEmailEnabled, + enabled, + endpoint, + incomingEmail, + outgoingName, + projectKey, + selectedTemplate, + templates, + } = el.dataset; + + return new Vue({ + el, + provide: { + customEmail, + customEmailEnabled: parseBoolean(customEmailEnabled), + endpoint, + initialIncomingEmail: incomingEmail, + initialIsEnabled: parseBoolean(enabled), + outgoingName, + projectKey, + selectedTemplate, + templates: JSON.parse(templates), + }, + render: (createElement) => createElement(ServiceDeskRoot), + }); }; diff --git a/app/assets/javascripts/projects/settings_service_desk/services/service_desk_service.js b/app/assets/javascripts/projects/settings_service_desk/services/service_desk_service.js deleted file mode 100644 index b68c5bb876f..00000000000 --- a/app/assets/javascripts/projects/settings_service_desk/services/service_desk_service.js +++ /dev/null @@ -1,23 +0,0 @@ -import axios from '~/lib/utils/axios_utils'; - -class ServiceDeskService { - constructor(endpoint) { - this.endpoint = endpoint; - } - - toggleServiceDesk(enable) { - return axios.put(this.endpoint, { service_desk_enabled: enable }); - } - - updateTemplate({ selectedTemplate, outgoingName, projectKey = '' }, isEnabled) { - const body = { - issue_template_key: selectedTemplate, - outgoing_name: outgoingName, - project_key: projectKey, - service_desk_enabled: isEnabled, - }; - return axios.put(this.endpoint, body); - } -} - -export default ServiceDeskService; diff --git a/app/assets/javascripts/registry/explorer/components/list_page/cli_commands.vue b/app/assets/javascripts/registry/explorer/components/list_page/cli_commands.vue index 07ee3c6083b..823fa4ab413 100644 --- a/app/assets/javascripts/registry/explorer/components/list_page/cli_commands.vue +++ b/app/assets/javascripts/registry/explorer/components/list_page/cli_commands.vue @@ -1,4 +1,6 @@ <script> +// Work around for https://github.com/vuejs/eslint-plugin-vue/issues/1411 +/* eslint-disable vue/one-component-per-file */ import { GlDropdown } from '@gitlab/ui'; import Tracking from '~/tracking'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; diff --git a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue index 6d21936791c..a445a85a2f3 100644 --- a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue +++ b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue @@ -1,4 +1,6 @@ <script> +// Work around for https://github.com/vuejs/eslint-plugin-vue/issues/1411 +/* eslint-disable vue/one-component-per-file */ import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { __ } from '~/locale'; import Tracking from '~/tracking'; diff --git a/app/assets/javascripts/vue_shared/components/registry/list_item.vue b/app/assets/javascripts/vue_shared/components/registry/list_item.vue index 8965dba3e83..b4930db7e98 100644 --- a/app/assets/javascripts/vue_shared/components/registry/list_item.vue +++ b/app/assets/javascripts/vue_shared/components/registry/list_item.vue @@ -54,7 +54,7 @@ export default { class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1" :class="optionalClasses" > - <div class="gl-display-flex gl-align-items-center gl-py-5"> + <div class="gl-display-flex gl-align-items-center gl-py-3"> <div v-if="$slots['left-action']" class="gl-w-7 gl-display-none gl-display-sm-flex gl-justify-content-start gl-pl-2" @@ -64,9 +64,7 @@ export default { <div class="gl-display-flex gl-xs-flex-direction-column gl-justify-content-space-between gl-align-items-stretch gl-flex-fill-1" > - <div - class="gl-display-flex gl-flex-direction-column gl-justify-content-space-between gl-xs-mb-3 gl-min-w-0 gl-flex-grow-1" - > + <div class="gl-display-flex gl-flex-direction-column gl-xs-mb-3 gl-min-w-0 gl-flex-grow-1"> <div v-if="$slots['left-primary']" class="gl-display-flex gl-align-items-center gl-text-body gl-font-weight-bold gl-min-h-6 gl-min-w-0" @@ -83,7 +81,7 @@ export default { </div> <div v-if="$slots['left-secondary']" - class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1 gl-min-h-6 gl-min-w-0 gl-flex-fill-1" + class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-min-h-6 gl-min-w-0 gl-flex-fill-1" > <slot name="left-secondary"></slot> </div> @@ -99,7 +97,7 @@ export default { </div> <div v-if="$slots['right-secondary']" - class="gl-display-flex gl-align-items-center gl-mt-1 gl-min-h-6" + class="gl-display-flex gl-align-items-center gl-min-h-6" > <slot name="right-secondary"></slot> </div> diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 81a70470c65..019d827798c 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -42,8 +42,7 @@ .login-box, .omniauth-container { box-shadow: 0 0 0 1px $border-color; - border-bottom-right-radius: $border-radius; - border-bottom-left-radius: $border-radius; + border-radius: $border-radius; padding: 15px; .login-heading h3 { diff --git a/app/assets/stylesheets/startup/startup-signin.scss b/app/assets/stylesheets/startup/startup-signin.scss index af43c532b7c..608740078b6 100644 --- a/app/assets/stylesheets/startup/startup-signin.scss +++ b/app/assets/stylesheets/startup/startup-signin.scss @@ -2093,8 +2093,7 @@ table.code { .login-page .login-box, .login-page .omniauth-container { box-shadow: 0 0 0 1px #dbdbdb; - border-bottom-right-radius: 0.25rem; - border-bottom-left-radius: 0.25rem; + border-radius: 0.25rem; padding: 15px; } .login-page .login-box .nav .active a, diff --git a/app/finders/repositories/commits_with_trailer_finder.rb b/app/finders/repositories/commits_with_trailer_finder.rb new file mode 100644 index 00000000000..4bd643c345b --- /dev/null +++ b/app/finders/repositories/commits_with_trailer_finder.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module Repositories + # Finder for obtaining commits between two refs, with a Git trailer set. + class CommitsWithTrailerFinder + # The maximum number of commits to retrieve per page. + # + # This value is arbitrarily chosen. Lowering it means more Gitaly calls, but + # less data being loaded into memory at once. Increasing it has the opposite + # effect. + # + # This amount is based around the number of commits that usually go in a + # GitLab release. Some examples for GitLab's own releases: + # + # * 13.6.0: 4636 commits + # * 13.5.0: 5912 commits + # * 13.4.0: 5541 commits + # + # Using this limit should result in most (very large) projects only needing + # 5-10 Gitaly calls, while keeping memory usage at a reasonable amount. + COMMITS_PER_PAGE = 1024 + + # The `project` argument specifies the project for which to obtain the + # commits. + # + # The `from` and `to` arguments specify the range of commits to include. The + # commit specified in `from` won't be included itself. The commit specified + # in `to` _is_ included. + # + # The `per_page` argument specifies how many commits are retrieved in a single + # Gitaly API call. + def initialize(project:, from:, to:, per_page: COMMITS_PER_PAGE) + @project = project + @from = from + @to = to + @per_page = per_page + end + + # Fetches all commits that have the given trailer set. + # + # The commits are yielded to the supplied block in batches. This allows + # other code to process these commits in batches too, instead of first + # having to load all commits into memory. + # + # Example: + # + # CommitsWithTrailerFinder.new(...).each_page('Signed-off-by') do |commits| + # commits.each do |commit| + # ... + # end + # end + def each_page(trailer) + return to_enum(__method__, trailer) unless block_given? + + offset = 0 + response = fetch_commits + + while response.any? + commits = [] + + response.each do |commit| + commits.push(commit) if commit.trailers.key?(trailer) + end + + yield commits + + offset += response.length + response = fetch_commits(offset) + end + end + + private + + def fetch_commits(offset = 0) + range = "#{@from}..#{@to}" + + @project + .repository + .commits(range, limit: @per_page, offset: offset, trailers: true) + end + end +end diff --git a/app/graphql/types/notes/discussion_type.rb b/app/graphql/types/notes/discussion_type.rb index a51d253097d..9b863990849 100644 --- a/app/graphql/types/notes/discussion_type.rb +++ b/app/graphql/types/notes/discussion_type.rb @@ -3,24 +3,26 @@ module Types module Notes class DiscussionType < BaseObject + DiscussionID = ::Types::GlobalIDType[::Discussion] + graphql_name 'Discussion' authorize :read_note implements(Types::ResolvableInterface) - field :id, GraphQL::ID_TYPE, null: false, + field :id, DiscussionID, null: false, description: "ID of this discussion" - field :reply_id, GraphQL::ID_TYPE, null: false, + field :reply_id, DiscussionID, null: false, description: 'ID used to reply to this discussion' field :created_at, Types::TimeType, null: false, description: "Timestamp of the discussion's creation" field :notes, Types::Notes::NoteType.connection_type, null: false, description: 'All notes in the discussion' - # The gem we use to generate Global IDs is hard-coded to work with - # `id` properties. To generate a GID for the `reply_id` property, - # we must use the ::Gitlab::GlobalId module. + # DiscussionID.coerce_result is suitable here, but will always mark this + # as being a 'Discussion'. Using `GlobalId.build` guarantees that we get + # the correct class, and that it matches `id`. def reply_id ::Gitlab::GlobalId.build(object, id: object.reply_id) end diff --git a/app/graphql/types/notes/note_type.rb b/app/graphql/types/notes/note_type.rb index 84b61340e93..dea55fe7f9e 100644 --- a/app/graphql/types/notes/note_type.rb +++ b/app/graphql/types/notes/note_type.rb @@ -11,7 +11,7 @@ module Types implements(Types::ResolvableInterface) - field :id, GraphQL::ID_TYPE, null: false, + field :id, ::Types::GlobalIDType[::Note], null: false, description: 'ID of the note' field :project, Types::ProjectType, diff --git a/app/models/merge_request_context_commit.rb b/app/models/merge_request_context_commit.rb index 59cc82cfaf5..e081a96dc10 100644 --- a/app/models/merge_request_context_commit.rb +++ b/app/models/merge_request_context_commit.rb @@ -12,6 +12,9 @@ class MergeRequestContextCommit < ApplicationRecord validates :sha, presence: true validates :sha, uniqueness: { message: 'has already been added' } + serialize :trailers, Serializers::JSON # rubocop:disable Cop/ActiveRecordSerialize + validates :trailers, json_schema: { filename: 'git_trailers' } + # Sort by committed date in descending order to ensure latest commits comes on the top scope :order_by_committed_date_desc, -> { order('committed_date DESC') } diff --git a/app/models/merge_request_diff_commit.rb b/app/models/merge_request_diff_commit.rb index 9f6933d0879..cf2d07b8561 100644 --- a/app/models/merge_request_diff_commit.rb +++ b/app/models/merge_request_diff_commit.rb @@ -10,6 +10,9 @@ class MergeRequestDiffCommit < ApplicationRecord sha_attribute :sha alias_attribute :id, :sha + serialize :trailers, Serializers::JSON # rubocop:disable Cop/ActiveRecordSerialize + validates :trailers, json_schema: { filename: 'git_trailers' } + # Deprecated; use `bulk_insert!` from `BulkInsertSafe` mixin instead. # cf. https://gitlab.com/gitlab-org/gitlab/issues/207989 for progress def self.create_bulk(merge_request_diff_id, commits) @@ -23,7 +26,8 @@ class MergeRequestDiffCommit < ApplicationRecord relative_order: index, sha: Gitlab::Database::ShaAttribute.serialize(sha), # rubocop:disable Cop/ActiveRecordSerialize authored_date: Gitlab::Database.sanitize_timestamp(commit_hash[:authored_date]), - committed_date: Gitlab::Database.sanitize_timestamp(commit_hash[:committed_date]) + committed_date: Gitlab::Database.sanitize_timestamp(commit_hash[:committed_date]), + trailers: commit_hash.fetch(:trailers, {}).to_json ) end diff --git a/app/models/project.rb b/app/models/project.rb index ec790798806..bfa85fc9d99 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1314,21 +1314,11 @@ class Project < ApplicationRecord end def external_issue_tracker - if has_external_issue_tracker.nil? - cache_has_external_issue_tracker - end + cache_has_external_issue_tracker if has_external_issue_tracker.nil? - if has_external_issue_tracker? - strong_memoize(:external_issue_tracker) do - services.external_issue_trackers.first - end - else - nil - end - end + return unless has_external_issue_tracker? - def cache_has_external_issue_tracker - update_column(:has_external_issue_tracker, services.external_issue_trackers.any?) if Gitlab::Database.read_write? + @external_issue_tracker ||= services.external_issue_trackers.first end def external_references_supported? @@ -2690,6 +2680,10 @@ class Project < ApplicationRecord def cache_has_external_wiki update_column(:has_external_wiki, services.external_wikis.any?) if Gitlab::Database.read_write? end + + def cache_has_external_issue_tracker + update_column(:has_external_issue_tracker, services.external_issue_trackers.any?) if Gitlab::Database.read_write? + end end Project.prepend_if_ee('EE::Project') diff --git a/app/models/repository.rb b/app/models/repository.rb index c19448332f8..6e57e5c6620 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -151,7 +151,8 @@ class Repository all: !!opts[:all], first_parent: !!opts[:first_parent], order: opts[:order], - literal_pathspec: opts.fetch(:literal_pathspec, true) + literal_pathspec: opts.fetch(:literal_pathspec, true), + trailers: opts[:trailers] } commits = Gitlab::Git::Commit.where(options) diff --git a/app/models/service.rb b/app/models/service.rb index e5626462dd3..2b92b2cd7d8 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -46,7 +46,6 @@ class Service < ApplicationRecord after_initialize :initialize_properties after_commit :reset_updated_properties - after_commit :cache_project_has_external_issue_tracker belongs_to :project, inverse_of: :services belongs_to :group, inverse_of: :services @@ -438,10 +437,6 @@ class Service < ApplicationRecord ProjectServiceWorker.perform_async(id, data) end - def external_issue_tracker? - category == :issue_tracker && active? - end - def external_wiki? type == 'ExternalWikiService' && active? end @@ -461,12 +456,6 @@ class Service < ApplicationRecord errors.add(:project_id, 'The service cannot belong to both a project and a group') if project_id && group_id end - def cache_project_has_external_issue_tracker - if project && !project.destroyed? - project.cache_has_external_issue_tracker - end - end - def valid_recipients? activated? && !importing? end diff --git a/app/services/bulk_create_integration_service.rb b/app/services/bulk_create_integration_service.rb index df78c3645c7..ae756d0856e 100644 --- a/app/services/bulk_create_integration_service.rb +++ b/app/services/bulk_create_integration_service.rb @@ -11,8 +11,6 @@ class BulkCreateIntegrationService service_list = ServiceList.new(batch, service_hash, association).to_array Service.transaction do - run_callbacks(batch) if association == 'project' - results = bulk_insert(*service_list) if integration.data_fields_present? @@ -33,14 +31,6 @@ class BulkCreateIntegrationService klass.insert_all(items_to_insert, returning: [:id]) end - # rubocop: disable CodeReuse/ActiveRecord - def run_callbacks(batch) - if integration.external_issue_tracker? - Project.where(id: batch.select(:id)).update_all(has_external_issue_tracker: true) - end - end - # rubocop: enable CodeReuse/ActiveRecord - def service_hash if integration.template? integration.to_service_hash diff --git a/app/services/feature_flags/base_service.rb b/app/services/feature_flags/base_service.rb index c11c465252e..f48f95e2550 100644 --- a/app/services/feature_flags/base_service.rb +++ b/app/services/feature_flags/base_service.rb @@ -41,7 +41,6 @@ module FeatureFlags def sync_to_jira(feature_flag) return unless feature_flag.present? - return unless Feature.enabled?(:jira_sync_feature_flags, feature_flag.project) seq_id = ::Atlassian::JiraConnect::Client.generate_update_sequence_id feature_flag.run_after_commit do diff --git a/app/services/merge_requests/add_context_service.rb b/app/services/merge_requests/add_context_service.rb index bb82fa23468..b693f8509a2 100644 --- a/app/services/merge_requests/add_context_service.rb +++ b/app/services/merge_requests/add_context_service.rb @@ -66,7 +66,8 @@ module MergeRequests relative_order: index, sha: sha, authored_date: Gitlab::Database.sanitize_timestamp(commit_hash[:authored_date]), - committed_date: Gitlab::Database.sanitize_timestamp(commit_hash[:committed_date]) + committed_date: Gitlab::Database.sanitize_timestamp(commit_hash[:committed_date]), + trailers: commit_hash.fetch(:trailers, {}).to_json ) end end diff --git a/app/validators/json_schemas/git_trailers.json b/app/validators/json_schemas/git_trailers.json new file mode 100644 index 00000000000..18ac97226a7 --- /dev/null +++ b/app/validators/json_schemas/git_trailers.json @@ -0,0 +1,9 @@ +{ + "description": "Git trailer key/value pairs", + "type": "object", + "patternProperties": { + ".*": { + "type": "string" + } + } +} diff --git a/app/views/notify/provisioned_member_access_granted_email.haml b/app/views/notify/provisioned_member_access_granted_email.html.haml index 2f2fd33145a..515254d1454 100644 --- a/app/views/notify/provisioned_member_access_granted_email.haml +++ b/app/views/notify/provisioned_member_access_granted_email.html.haml @@ -16,7 +16,8 @@ %td.text-content %p = _('By authenticating with an account tied to an Enterprise e-mail address, it is understood that this account is an Enterprise User. ') - = _('To ensure no loss of personal content, an Individual User should create a separate account under their own personal email address, not tied to the Enterprise email domain or name-space.') + = _('To ensure no loss of personal content, this account should only be used for matters related to %{group_name}.') % { group_name: member_source.human_name } + = _('For individual use, create a separate account under your personal email address, not tied to the Enterprise email domain or group.') - unless @user.confirmed? %p = _('To get started, click the link below to confirm your account.') diff --git a/app/views/notify/provisioned_member_access_granted_email.erb b/app/views/notify/provisioned_member_access_granted_email.text.erb index 485ee5a5242..b143b8d6f54 100644 --- a/app/views/notify/provisioned_member_access_granted_email.erb +++ b/app/views/notify/provisioned_member_access_granted_email.text.erb @@ -7,7 +7,8 @@ <%= _('By authenticating with an account tied to an Enterprise e-mail address, it is understood that this account is an Enterprise User. ') %> -<%= _('To ensure no loss of personal content, an Individual User should create a separate account under their own personal email address, not tied to the Enterprise email domain or name-space.') %> +<%= _('To ensure no loss of personal content, this account should only be used for matters related to %{group_name}.') % { group_name: member_source.human_name } %> +<%= _('For individual use, create a separate account under your personal email address, not tied to the Enterprise email domain or group.') %> <%- unless @user.confirmed? %> <%= _('To get started, click the link below to confirm your account.') %> <%= confirmation_url(@user, confirmation_token: @user.confirmation_token) %> diff --git a/app/workers/jira_connect/sync_feature_flags_worker.rb b/app/workers/jira_connect/sync_feature_flags_worker.rb index 7e98d0eada7..496b9f1626d 100644 --- a/app/workers/jira_connect/sync_feature_flags_worker.rb +++ b/app/workers/jira_connect/sync_feature_flags_worker.rb @@ -14,7 +14,6 @@ module JiraConnect feature_flag = ::Operations::FeatureFlag.find_by_id(feature_flag_id) return unless feature_flag - return unless Feature.enabled?(:jira_sync_feature_flags, feature_flag.project) ::JiraConnect::SyncService .new(feature_flag.project) diff --git a/changelogs/unreleased/290715-has_external_issue_tracker_trigger.yml b/changelogs/unreleased/290715-has_external_issue_tracker_trigger.yml new file mode 100644 index 00000000000..d9d40c99c5c --- /dev/null +++ b/changelogs/unreleased/290715-has_external_issue_tracker_trigger.yml @@ -0,0 +1,5 @@ +--- +title: Add PostgreSQL trigger to maintain projects.has_external_issue_tracker +merge_request: 51852 +author: +type: changed diff --git a/changelogs/unreleased/294007-bump-workhorse.yml b/changelogs/unreleased/294007-bump-workhorse.yml new file mode 100644 index 00000000000..9c9cc468715 --- /dev/null +++ b/changelogs/unreleased/294007-bump-workhorse.yml @@ -0,0 +1,5 @@ +--- +title: Update Workhorse to v8.60.0 +merge_request: 51965 +author: +type: changed diff --git a/changelogs/unreleased/296948-container-and-package-list-ui-polish-condense-spacing-to-match-iss.yml b/changelogs/unreleased/296948-container-and-package-list-ui-polish-condense-spacing-to-match-iss.yml new file mode 100644 index 00000000000..74d5504951b --- /dev/null +++ b/changelogs/unreleased/296948-container-and-package-list-ui-polish-condense-spacing-to-match-iss.yml @@ -0,0 +1,5 @@ +--- +title: Reduce vertical padding of registry list item +merge_request: 51961 +author: +type: changed diff --git a/changelogs/unreleased/296990-enable-jira-ff-sync.yml b/changelogs/unreleased/296990-enable-jira-ff-sync.yml new file mode 100644 index 00000000000..ac9b71bc37d --- /dev/null +++ b/changelogs/unreleased/296990-enable-jira-ff-sync.yml @@ -0,0 +1,5 @@ +--- +title: Enable synchronization of feature flags to Jira +merge_request: 51796 +author: +type: added diff --git a/changelogs/unreleased/gitaly-trailer-parsing.yml b/changelogs/unreleased/gitaly-trailer-parsing.yml new file mode 100644 index 00000000000..096ef4a94a7 --- /dev/null +++ b/changelogs/unreleased/gitaly-trailer-parsing.yml @@ -0,0 +1,5 @@ +--- +title: Add finder for getting commits with a trailer set +merge_request: 49243 +author: +type: added diff --git a/changelogs/unreleased/yo-fix-border-radius.yml b/changelogs/unreleased/yo-fix-border-radius.yml new file mode 100644 index 00000000000..f63d79b4d49 --- /dev/null +++ b/changelogs/unreleased/yo-fix-border-radius.yml @@ -0,0 +1,5 @@ +--- +title: Fix top border-radius of the login box +merge_request: 51950 +author: Yogi (@yo) +type: fixed diff --git a/config/feature_flags/development/jira_sync_feature_flags.yml b/config/feature_flags/development/jira_sync_feature_flags.yml deleted file mode 100644 index c5925766919..00000000000 --- a/config/feature_flags/development/jira_sync_feature_flags.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: jira_sync_feature_flags -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50390 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/296990 -milestone: '13.8' -type: development -group: group::ecosystem -default_enabled: false diff --git a/db/migrate/20210106155209_add_merge_request_diff_commit_trailers.rb b/db/migrate/20210106155209_add_merge_request_diff_commit_trailers.rb new file mode 100644 index 00000000000..906efa58bcd --- /dev/null +++ b/db/migrate/20210106155209_add_merge_request_diff_commit_trailers.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# See https://docs.gitlab.com/ee/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddMergeRequestDiffCommitTrailers < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + with_lock_retries do + add_column :merge_request_diff_commits, :trailers, :jsonb, default: {}, null: false + end + end + + def down + with_lock_retries do + remove_column :merge_request_diff_commits, :trailers + end + end +end diff --git a/db/migrate/20210107154615_add_merge_request_context_commit_trailers.rb b/db/migrate/20210107154615_add_merge_request_context_commit_trailers.rb new file mode 100644 index 00000000000..e7bd7c2ea56 --- /dev/null +++ b/db/migrate/20210107154615_add_merge_request_context_commit_trailers.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# See https://docs.gitlab.com/ee/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddMergeRequestContextCommitTrailers < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def change + add_column :merge_request_context_commits, :trailers, :jsonb, default: {}, null: false + end +end diff --git a/db/migrate/20210117210226_add_has_external_issue_tracker_trigger.rb b/db/migrate/20210117210226_add_has_external_issue_tracker_trigger.rb new file mode 100644 index 00000000000..20fe0ee0fd1 --- /dev/null +++ b/db/migrate/20210117210226_add_has_external_issue_tracker_trigger.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +class AddHasExternalIssueTrackerTrigger < ActiveRecord::Migration[6.0] + include Gitlab::Database::SchemaHelpers + + DOWNTIME = false + FUNCTION_NAME = 'set_has_external_issue_tracker' + TRIGGER_ON_INSERT_NAME = 'trigger_has_external_issue_tracker_on_insert' + TRIGGER_ON_UPDATE_NAME = 'trigger_has_external_issue_tracker_on_update' + TRIGGER_ON_DELETE_NAME = 'trigger_has_external_issue_tracker_on_delete' + + def up + create_trigger_function(FUNCTION_NAME, replace: true) do + <<~SQL + UPDATE projects SET has_external_issue_tracker = ( + EXISTS + ( + SELECT 1 + FROM services + WHERE project_id = COALESCE(NEW.project_id, OLD.project_id) + AND active = TRUE + AND category = 'issue_tracker' + ) + ) + WHERE projects.id = COALESCE(NEW.project_id, OLD.project_id); + RETURN NULL; + SQL + end + + execute(<<~SQL) + CREATE TRIGGER #{TRIGGER_ON_INSERT_NAME} + AFTER INSERT ON services + FOR EACH ROW + WHEN (NEW.category = 'issue_tracker' AND NEW.active = TRUE AND NEW.project_id IS NOT NULL) + EXECUTE FUNCTION #{FUNCTION_NAME}(); + SQL + + execute(<<~SQL) + CREATE TRIGGER #{TRIGGER_ON_UPDATE_NAME} + AFTER UPDATE ON services + FOR EACH ROW + WHEN (NEW.category = 'issue_tracker' AND OLD.active != NEW.active AND NEW.project_id IS NOT NULL) + EXECUTE FUNCTION #{FUNCTION_NAME}(); + SQL + + execute(<<~SQL) + CREATE TRIGGER #{TRIGGER_ON_DELETE_NAME} + AFTER DELETE ON services + FOR EACH ROW + WHEN (OLD.category = 'issue_tracker' AND OLD.active = TRUE AND OLD.project_id IS NOT NULL) + EXECUTE FUNCTION #{FUNCTION_NAME}(); + SQL + end + + def down + drop_trigger(:services, TRIGGER_ON_INSERT_NAME) + drop_trigger(:services, TRIGGER_ON_UPDATE_NAME) + drop_trigger(:services, TRIGGER_ON_DELETE_NAME) + drop_function(FUNCTION_NAME) + end +end diff --git a/db/schema_migrations/20210106155209 b/db/schema_migrations/20210106155209 new file mode 100644 index 00000000000..10dde1cf874 --- /dev/null +++ b/db/schema_migrations/20210106155209 @@ -0,0 +1 @@ +4aeff45663a9f5a41a8dd92298afb4b0b57aa6f190f4648455df2fa1e39e174f
\ No newline at end of file diff --git a/db/schema_migrations/20210107154615 b/db/schema_migrations/20210107154615 new file mode 100644 index 00000000000..f8b6f97d3fe --- /dev/null +++ b/db/schema_migrations/20210107154615 @@ -0,0 +1 @@ +3e867ceefcab4f043b89d3c04e6e0a1182423039e1a9245e611128efe77c0e88
\ No newline at end of file diff --git a/db/schema_migrations/20210117210226 b/db/schema_migrations/20210117210226 new file mode 100644 index 00000000000..a68f7f6d93e --- /dev/null +++ b/db/schema_migrations/20210117210226 @@ -0,0 +1 @@ +c0d22d00d52a516347930e1a36f350113c0949214925176f08ceed81999746bd
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 38965f00aa6..261e9a1e73b 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -10,6 +10,26 @@ CREATE EXTENSION IF NOT EXISTS btree_gist; CREATE EXTENSION IF NOT EXISTS pg_trgm; +CREATE FUNCTION set_has_external_issue_tracker() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN +UPDATE projects SET has_external_issue_tracker = ( + EXISTS + ( + SELECT 1 + FROM services + WHERE project_id = COALESCE(NEW.project_id, OLD.project_id) + AND active = TRUE + AND category = 'issue_tracker' + ) + ) +WHERE projects.id = COALESCE(NEW.project_id, OLD.project_id); +RETURN NULL; + +END +$$; + CREATE FUNCTION set_has_external_wiki() RETURNS trigger LANGUAGE plpgsql AS $$ @@ -13863,7 +13883,8 @@ CREATE TABLE merge_request_context_commits ( committer_name text, committer_email text, message text, - merge_request_id bigint + merge_request_id bigint, + trailers jsonb DEFAULT '{}'::jsonb NOT NULL ); CREATE SEQUENCE merge_request_context_commits_id_seq @@ -13885,7 +13906,8 @@ CREATE TABLE merge_request_diff_commits ( author_email text, committer_name text, committer_email text, - message text + message text, + trailers jsonb DEFAULT '{}'::jsonb NOT NULL ); CREATE TABLE merge_request_diff_details ( @@ -23637,6 +23659,12 @@ ALTER INDEX product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_p CREATE TRIGGER table_sync_trigger_ee39a25f9d AFTER INSERT OR DELETE OR UPDATE ON audit_events FOR EACH ROW EXECUTE PROCEDURE table_sync_function_2be879775d(); +CREATE TRIGGER trigger_has_external_issue_tracker_on_delete AFTER DELETE ON services FOR EACH ROW WHEN ((((old.category)::text = 'issue_tracker'::text) AND (old.active = true) AND (old.project_id IS NOT NULL))) EXECUTE PROCEDURE set_has_external_issue_tracker(); + +CREATE TRIGGER trigger_has_external_issue_tracker_on_insert AFTER INSERT ON services FOR EACH ROW WHEN ((((new.category)::text = 'issue_tracker'::text) AND (new.active = true) AND (new.project_id IS NOT NULL))) EXECUTE PROCEDURE set_has_external_issue_tracker(); + +CREATE TRIGGER trigger_has_external_issue_tracker_on_update AFTER UPDATE ON services FOR EACH ROW WHEN ((((new.category)::text = 'issue_tracker'::text) AND (old.active <> new.active) AND (new.project_id IS NOT NULL))) EXECUTE PROCEDURE set_has_external_issue_tracker(); + CREATE TRIGGER trigger_has_external_wiki_on_delete AFTER DELETE ON services FOR EACH ROW WHEN ((((old.type)::text = 'ExternalWikiService'::text) AND (old.project_id IS NOT NULL))) EXECUTE PROCEDURE set_has_external_wiki(); CREATE TRIGGER trigger_has_external_wiki_on_insert AFTER INSERT ON services FOR EACH ROW WHEN (((new.active = true) AND ((new.type)::text = 'ExternalWikiService'::text) AND (new.project_id IS NOT NULL))) EXECUTE PROCEDURE set_has_external_wiki(); diff --git a/doc/.vale/gitlab/Admin.yml b/doc/.vale/gitlab/Admin.yml index 96325ad2ef4..2300fa38e77 100644 --- a/doc/.vale/gitlab/Admin.yml +++ b/doc/.vale/gitlab/Admin.yml @@ -7,6 +7,7 @@ extends: substitution message: 'Use "administration", "administrator", "administer", or "Admin Area" instead of "admin" or "admin area".' link: https://docs.gitlab.com/ee/development/documentation/styleguide.html +# Do not set `level: error`, as our docs refer to other docs which use "admin" and "Admin" level: warning ignorecase: true swap: diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index cf20d98ba83..64dd074a912 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -7878,7 +7878,7 @@ type Discussion implements ResolvableInterface { """ ID of this discussion """ - id: ID! + id: DiscussionID! """ All notes in the discussion @@ -7908,7 +7908,7 @@ type Discussion implements ResolvableInterface { """ ID used to reply to this discussion """ - replyId: ID! + replyId: DiscussionID! """ Indicates if the object can be resolved @@ -16154,7 +16154,7 @@ type Note implements ResolvableInterface { """ ID of the note """ - id: ID! + id: NoteID! """ The position of this note on a diff diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index f129382bd1e..790c641a6c0 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -21766,7 +21766,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "ID", + "name": "DiscussionID", "ofType": null } }, @@ -21841,7 +21841,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "ID", + "name": "DiscussionID", "ofType": null } }, @@ -47730,7 +47730,7 @@ "name": null, "ofType": { "kind": "SCALAR", - "name": "ID", + "name": "NoteID", "ofType": null } }, diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 3a74bbeefda..31e9e46b6be 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1301,9 +1301,9 @@ Aggregated summary of changes. | Field | Type | Description | | ----- | ---- | ----------- | | `createdAt` | Time! | Timestamp of the discussion's creation | -| `id` | ID! | ID of this discussion | +| `id` | DiscussionID! | ID of this discussion | | `notes` | NoteConnection! | All notes in the discussion | -| `replyId` | ID! | ID used to reply to this discussion | +| `replyId` | DiscussionID! | ID used to reply to this discussion | | `resolvable` | Boolean! | Indicates if the object can be resolved | | `resolved` | Boolean! | Indicates if the object is resolved | | `resolvedAt` | Time | Timestamp of when the object was resolved | @@ -2429,7 +2429,7 @@ Autogenerated return type of NamespaceIncreaseStorageTemporarily. | `confidential` | Boolean | Indicates if this note is confidential | | `createdAt` | Time! | Timestamp of the note creation | | `discussion` | Discussion | The discussion this note is a part of | -| `id` | ID! | ID of the note | +| `id` | NoteID! | ID of the note | | `position` | DiffPosition | The position of this note on a diff | | `project` | Project | Project associated with the note | | `resolvable` | Boolean! | Indicates if the object can be resolved | diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md index 306a16bd873..cafcc15d879 100644 --- a/doc/user/project/integrations/jira.md +++ b/doc/user/project/integrations/jira.md @@ -20,7 +20,13 @@ Features include: - **Mention that a commit or MR resolves or closes a specific Jira issue** and when it's merged to the default branch: - The GitLab MR displays a note that it closed the Jira issue. Prior to the merge, MRs indicate which issue they close. - The Jira issue shows the activity and is closed or otherwise transitioned as specified in your GitLab settings. -- **View a list of Jira issues directly in GitLab** **(PREMIUM)** +- **Run a pipeline** on an MR linked to a Jira issue: + - The Jira issue shows the current pipeline status (in the sidebar as "builds"). +- **Deploy to an environment** from an MR linked to a Jira issue: + - The Jira issue shows the status of the deployment (in the sidebar as "deployments"). +- **Create or modify a feature flag** that mentions a Jira issue in its description: + - The Jira issue shows the details of the feature-flag (in the sidebar as "feature flags"). +- **View a list of Jira issues** directly in GitLab **(PREMIUM)** For additional features, you can install the [Jira Development Panel integration](../../../integration/jira_development_panel.md). @@ -29,6 +35,9 @@ This enables you to: - In a Jira issue, display relevant GitLab information in the [development panel](https://support.atlassian.com/jira-software-cloud/docs/view-development-information-for-an-issue/), including related branches, commits, and merge requests. - Use Jira [Smart Commits](https://confluence.atlassian.com/fisheye/using-smart-commits-960155400.html) in GitLab to add Jira comments, log time spent on the issue, or apply any issue transition. +Some features (such as showing pipeline, deployment, and feature flags in Jira +issues) are only available when using the [Jira Development Panel integration](../../../integration/jira_development_panel.md). + See the [feature comparison](jira_integrations.md#feature-comparison) for more details. ## Configuration diff --git a/doc/user/project/merge_requests/getting_started.md b/doc/user/project/merge_requests/getting_started.md index dc5e1f81a63..271f8b42f36 100644 --- a/doc/user/project/merge_requests/getting_started.md +++ b/doc/user/project/merge_requests/getting_started.md @@ -176,8 +176,7 @@ This feature might not be available to you. Check the **version history** note a When editing the **Reviewers** field in a new or existing merge request, GitLab displays the name of the matching [approval rule](merge_request_approvals.md#approval-rules) -below the name of each suggested reviewer. [Code Owners](../code_owners.md) are displayed as **Code Owner** without group detail. -We intend to iterate on this feature in future releases. +below the name of each suggested reviewer. [Code Owners](../code_owners.md) are displayed as `Codeowner` without group detail. This example shows reviewers and approval rules when creating a new merge request: diff --git a/lib/atlassian/jira_connect/client.rb b/lib/atlassian/jira_connect/client.rb index 8b31c82510c..241f6e6cbc5 100644 --- a/lib/atlassian/jira_connect/client.rb +++ b/lib/atlassian/jira_connect/client.rb @@ -33,8 +33,6 @@ module Atlassian private def store_ff_info(project:, feature_flags:, **opts) - return unless Feature.enabled?(:jira_sync_feature_flags, project) - items = feature_flags.map { |flag| ::Atlassian::JiraConnect::Serializers::FeatureFlagEntity.represent(flag, opts) } items.reject! { |item| item.issue_keys.empty? } diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index 0bc7ecccf5e..35c3dc5b0b3 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -16,7 +16,7 @@ module Gitlab SERIALIZE_KEYS = [ :id, :message, :parent_ids, :authored_date, :author_name, :author_email, - :committed_date, :committer_name, :committer_email + :committed_date, :committer_name, :committer_email, :trailers ].freeze attr_accessor(*SERIALIZE_KEYS) @@ -389,6 +389,7 @@ module Gitlab @committer_name = commit.committer.name.dup @committer_email = commit.committer.email.dup @parent_ids = Array(commit.parent_ids) + @trailers = Hash[commit.trailers.map { |t| [t.key, t.value] }] end # Gitaly provides a UNIX timestamp in author.date.seconds, and a timezone diff --git a/lib/gitlab/git/rugged_impl/commit.rb b/lib/gitlab/git/rugged_impl/commit.rb index 0eff35ab1c4..0607b151de2 100644 --- a/lib/gitlab/git/rugged_impl/commit.rb +++ b/lib/gitlab/git/rugged_impl/commit.rb @@ -103,6 +103,7 @@ module Gitlab @committer_name = committer[:name] @committer_email = committer[:email] @parent_ids = commit.parents.map(&:oid) + @trailers = Hash[commit.trailers] end end end diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index ea940150941..ef5221a8042 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -335,7 +335,8 @@ module Gitlab all: !!options[:all], first_parent: !!options[:first_parent], global_options: parse_global_options!(options), - disable_walk: true # This option is deprecated. The 'walk' implementation is being removed. + disable_walk: true, # This option is deprecated. The 'walk' implementation is being removed. + trailers: options[:trailers] ) request.after = GitalyClient.timestamp(options[:after]) if options[:after] request.before = GitalyClient.timestamp(options[:before]) if options[:before] diff --git a/lib/gitlab/global_id.rb b/lib/gitlab/global_id.rb index e8a6006dce1..7e9412236cf 100644 --- a/lib/gitlab/global_id.rb +++ b/lib/gitlab/global_id.rb @@ -19,8 +19,8 @@ module Gitlab value when URI::GID GlobalID.new(value) - when Integer - raise CoerceError, 'Cannot coerce Integer' unless model_name.present? + when Integer, String + raise CoerceError, "Cannot coerce #{value.class}" unless model_name.present? GlobalID.new(::Gitlab::GlobalId.build(model_name: model_name, id: value)) else diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 57a5ab17b25..317678714e3 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -12650,6 +12650,9 @@ msgstr "" msgid "For help setting up the Service Desk for your instance, please contact an administrator." msgstr "" +msgid "For individual use, create a separate account under your personal email address, not tied to the Enterprise email domain or group." +msgstr "" + msgid "For internal projects, any logged in user except external users can view pipelines and access job details (output logs and artifacts)" msgstr "" @@ -29658,7 +29661,7 @@ msgstr "" msgid "To define internal users, first enable new users set to external" msgstr "" -msgid "To ensure no loss of personal content, an Individual User should create a separate account under their own personal email address, not tied to the Enterprise email domain or name-space." +msgid "To ensure no loss of personal content, this account should only be used for matters related to %{group_name}." msgstr "" msgid "To further protect your account, consider configuring a %{mfa_link_start}two-factor authentication%{mfa_link_end} method." diff --git a/spec/finders/repositories/commits_with_trailer_finder_spec.rb b/spec/finders/repositories/commits_with_trailer_finder_spec.rb new file mode 100644 index 00000000000..0c457aae340 --- /dev/null +++ b/spec/finders/repositories/commits_with_trailer_finder_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Repositories::CommitsWithTrailerFinder do + let(:project) { create(:project, :repository) } + + describe '#each_page' do + it 'only yields commits with the given trailer' do + finder = described_class.new( + project: project, + from: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d', + to: 'c7fbe50c7c7419d9701eebe64b1fdacc3df5b9dd' + ) + + commits = finder.each_page('Signed-off-by').to_a.flatten + + expect(commits.length).to eq(1) + expect(commits.first.id).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + expect(commits.first.trailers).to eq( + 'Signed-off-by' => 'Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>' + ) + end + + it 'supports paginating of commits' do + finder = described_class.new( + project: project, + from: 'c1acaa58bbcbc3eafe538cb8274ba387047b69f8', + to: '5937ac0a7beb003549fc5fd26fc247adbce4a52e', + per_page: 1 + ) + + commits = finder.each_page('Signed-off-by') + + expect(commits.count).to eq(4) + end + end +end diff --git a/spec/frontend/clusters/components/applications_spec.js b/spec/frontend/clusters/components/applications_spec.js index cf89246c1a5..431b042cce7 100644 --- a/spec/frontend/clusters/components/applications_spec.js +++ b/spec/frontend/clusters/components/applications_spec.js @@ -16,7 +16,7 @@ describe('Applications', () => { gon.features = gon.features || {}; }); - const createApp = ({ applications, type, propsData } = {}, isShallow) => { + const createComponent = ({ applications, type, propsData } = {}, isShallow) => { const mountMethod = isShallow ? shallowMount : mount; wrapper = mountMethod(Applications, { @@ -29,7 +29,7 @@ describe('Applications', () => { }); }; - const createShallowApp = (options) => createApp(options, true); + const createShallowComponent = (options) => createComponent(options, true); const findByTestId = (id) => wrapper.find(`[data-testid="${id}"]`); afterEach(() => { wrapper.destroy(); @@ -37,7 +37,7 @@ describe('Applications', () => { describe('Project cluster applications', () => { beforeEach(() => { - createApp({ type: CLUSTER_TYPE.PROJECT }); + createComponent({ type: CLUSTER_TYPE.PROJECT }); }); it('renders a row for Ingress', () => { @@ -82,7 +82,7 @@ describe('Applications', () => { describe('Group cluster applications', () => { beforeEach(() => { - createApp({ type: CLUSTER_TYPE.GROUP }); + createComponent({ type: CLUSTER_TYPE.GROUP }); }); it('renders a row for Ingress', () => { @@ -128,7 +128,7 @@ describe('Applications', () => { describe('Instance cluster applications', () => { beforeEach(() => { - createApp({ type: CLUSTER_TYPE.INSTANCE }); + createComponent({ type: CLUSTER_TYPE.INSTANCE }); }); it('renders a row for Ingress', () => { @@ -174,14 +174,14 @@ describe('Applications', () => { describe('Helm application', () => { it('does not render a row for Helm Tiller', () => { - createApp(); + createComponent(); expect(wrapper.find('.js-cluster-application-row-helm').exists()).toBe(false); }); }); describe('Ingress application', () => { it('shows the correct warning message', () => { - createApp(); + createComponent(); expect(findByTestId('ingressCostWarning').element).toMatchSnapshot(); }); @@ -195,7 +195,7 @@ describe('Applications', () => { }, }; - beforeEach(() => createShallowApp(propsData)); + beforeEach(() => createShallowComponent(propsData)); it('renders IngressModsecuritySettings', () => { const modsecuritySettings = wrapper.find(IngressModsecuritySettings); @@ -206,7 +206,7 @@ describe('Applications', () => { describe('when installed', () => { describe('with ip address', () => { it('renders ip address with a clipboard button', () => { - createApp({ + createComponent({ applications: { ingress: { title: 'Ingress', @@ -225,7 +225,7 @@ describe('Applications', () => { describe('with hostname', () => { it('renders hostname with a clipboard button', () => { - createApp({ + createComponent({ applications: { ingress: { title: 'Ingress', @@ -255,7 +255,7 @@ describe('Applications', () => { describe('without ip address', () => { it('renders an input text with a loading icon and an alert text', () => { - createApp({ + createComponent({ applications: { ingress: { title: 'Ingress', @@ -272,7 +272,7 @@ describe('Applications', () => { describe('before installing', () => { it('does not render the IP address', () => { - createApp(); + createComponent(); expect(wrapper.text()).not.toContain('Ingress IP Address'); expect(wrapper.find('.js-endpoint').exists()).toBe(false); @@ -282,13 +282,13 @@ describe('Applications', () => { describe('Cert-Manager application', () => { it('shows the correct description', () => { - createApp(); + createComponent(); expect(findByTestId('certManagerDescription').element).toMatchSnapshot(); }); describe('when not installed', () => { it('renders email & allows editing', () => { - createApp({ + createComponent({ applications: { cert_manager: { title: 'Cert-Manager', @@ -305,7 +305,7 @@ describe('Applications', () => { describe('when installed', () => { it('renders email in readonly', () => { - createApp({ + createComponent({ applications: { cert_manager: { title: 'Cert-Manager', @@ -324,7 +324,7 @@ describe('Applications', () => { describe('Jupyter application', () => { describe('with ingress installed with ip & jupyter installable', () => { it('renders hostname active input', () => { - createApp({ + createComponent({ applications: { ingress: { title: 'Ingress', @@ -342,7 +342,7 @@ describe('Applications', () => { describe('with ingress installed without external ip', () => { it('does not render hostname input', () => { - createApp({ + createComponent({ applications: { ingress: { title: 'Ingress', status: 'installed' }, }, @@ -356,7 +356,7 @@ describe('Applications', () => { describe('with ingress & jupyter installed', () => { it('renders readonly input', () => { - createApp({ + createComponent({ applications: { ingress: { title: 'Ingress', @@ -375,7 +375,7 @@ describe('Applications', () => { describe('without ingress installed', () => { beforeEach(() => { - createApp(); + createComponent(); }); it('does not render input', () => { @@ -388,7 +388,7 @@ describe('Applications', () => { describe('Prometheus application', () => { it('shows the correct description', () => { - createApp(); + createComponent(); expect(findByTestId('prometheusDescription').element).toMatchSnapshot(); }); }); @@ -414,14 +414,14 @@ describe('Applications', () => { let knativeDomainEditor; beforeEach(() => { - createShallowApp(propsData); + createShallowComponent(propsData); jest.spyOn(eventHub, '$emit'); knativeDomainEditor = wrapper.find(KnativeDomainEditor); }); it('shows the correct description', async () => { - createApp(); + createComponent(); wrapper.setProps({ providerType: PROVIDER_TYPE.GCP, preInstalledKnative: true, @@ -487,7 +487,7 @@ describe('Applications', () => { }, }; - beforeEach(() => createShallowApp(propsData)); + beforeEach(() => createShallowComponent(propsData)); it('renders the correct Component', () => { const crossplane = wrapper.find(CrossplaneProviderStack); @@ -495,7 +495,7 @@ describe('Applications', () => { }); it('shows the correct description', () => { - createApp(); + createComponent(); expect(findByTestId('crossplaneDescription').element).toMatchSnapshot(); }); }); @@ -503,7 +503,7 @@ describe('Applications', () => { describe('Elastic Stack application', () => { describe('with elastic stack installable', () => { it('renders the install button enabled', () => { - createApp(); + createComponent(); expect( wrapper @@ -517,7 +517,7 @@ describe('Applications', () => { describe('elastic stack installed', () => { it('renders uninstall button', () => { - createApp({ + createComponent({ applications: { elastic_stack: { title: 'Elastic Stack', status: 'installed' }, }, @@ -535,7 +535,7 @@ describe('Applications', () => { }); describe('Fluentd application', () => { - beforeEach(() => createShallowApp()); + beforeEach(() => createShallowComponent()); it('renders the correct Component', () => { expect(wrapper.find(FluentdOutputSettings).exists()).toBe(true); @@ -544,7 +544,7 @@ describe('Applications', () => { describe('Cilium application', () => { it('shows the correct description', () => { - createApp({ propsData: { ciliumHelpPath: 'cilium-help-path' } }); + createComponent({ propsData: { ciliumHelpPath: 'cilium-help-path' } }); expect(findByTestId('ciliumDescription').element).toMatchSnapshot(); }); }); diff --git a/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap index 5faae5690db..df49f256aa3 100644 --- a/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap +++ b/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap @@ -6,7 +6,7 @@ exports[`packages_list_row renders 1`] = ` data-qa-selector="package_row" > <div - class="gl-display-flex gl-align-items-center gl-py-5" + class="gl-display-flex gl-align-items-center gl-py-3" > <!----> @@ -14,7 +14,7 @@ exports[`packages_list_row renders 1`] = ` class="gl-display-flex gl-xs-flex-direction-column gl-justify-content-space-between gl-align-items-stretch gl-flex-fill-1" > <div - class="gl-display-flex gl-flex-direction-column gl-justify-content-space-between gl-xs-mb-3 gl-min-w-0 gl-flex-grow-1" + class="gl-display-flex gl-flex-direction-column gl-xs-mb-3 gl-min-w-0 gl-flex-grow-1" > <div class="gl-display-flex gl-align-items-center gl-text-body gl-font-weight-bold gl-min-h-6 gl-min-w-0" @@ -40,7 +40,7 @@ exports[`packages_list_row renders 1`] = ` </div> <div - class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1 gl-min-h-6 gl-min-w-0 gl-flex-fill-1" + class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-min-h-6 gl-min-w-0 gl-flex-fill-1" > <div class="gl-display-flex" @@ -85,7 +85,7 @@ exports[`packages_list_row renders 1`] = ` </div> <div - class="gl-display-flex gl-align-items-center gl-mt-1 gl-min-h-6" + class="gl-display-flex gl-align-items-center gl-min-h-6" > <span> <gl-sprintf-stub diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js index c83b1852147..f9fbb1b3016 100644 --- a/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js +++ b/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js @@ -1,20 +1,36 @@ -import { mount } from '@vue/test-utils'; +import { GlAlert } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import AxiosMockAdapter from 'axios-mock-adapter'; import waitForPromises from 'helpers/wait_for_promises'; -import ServiceDeskRoot from '~/projects/settings_service_desk/components/service_desk_root.vue'; -import ServiceDeskSetting from '~/projects/settings_service_desk/components/service_desk_setting.vue'; import axios from '~/lib/utils/axios_utils'; import httpStatusCodes from '~/lib/utils/http_status'; +import ServiceDeskRoot from '~/projects/settings_service_desk/components/service_desk_root.vue'; +import ServiceDeskSetting from '~/projects/settings_service_desk/components/service_desk_setting.vue'; describe('ServiceDeskRoot', () => { - const endpoint = '/gitlab-org/gitlab-test/service_desk'; - const initialIncomingEmail = 'servicedeskaddress@example.com'; let axiosMock; let wrapper; let spy; + const provideData = { + customEmail: 'custom.email@example.com', + customEmailEnabled: true, + endpoint: '/gitlab-org/gitlab-test/service_desk', + initialIncomingEmail: 'servicedeskaddress@example.com', + initialIsEnabled: true, + outgoingName: 'GitLab Support Bot', + projectKey: 'key', + selectedTemplate: 'Bug', + templates: ['Bug', 'Documentation'], + }; + + const getAlertText = () => wrapper.find(GlAlert).text(); + + const createComponent = () => shallowMount(ServiceDeskRoot, { provide: provideData }); + beforeEach(() => { axiosMock = new AxiosMockAdapter(axios); + spy = jest.spyOn(axios, 'put'); }); afterEach(() => { @@ -25,156 +41,122 @@ describe('ServiceDeskRoot', () => { } }); - it('sends a request to toggle service desk off when the toggle is clicked from the on state', () => { - axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK); + describe('ServiceDeskSetting component', () => { + it('is rendered', () => { + wrapper = createComponent(); + + expect(wrapper.find(ServiceDeskSetting).props()).toEqual({ + customEmail: provideData.customEmail, + customEmailEnabled: provideData.customEmailEnabled, + incomingEmail: provideData.initialIncomingEmail, + initialOutgoingName: provideData.outgoingName, + initialProjectKey: provideData.projectKey, + initialSelectedTemplate: provideData.selectedTemplate, + isEnabled: provideData.initialIsEnabled, + isTemplateSaving: false, + templates: provideData.templates, + }); + }); + + describe('toggle event', () => { + describe('when toggling service desk on', () => { + beforeEach(async () => { + wrapper = createComponent(); - spy = jest.spyOn(axios, 'put'); + wrapper.find(ServiceDeskSetting).vm.$emit('toggle', true); - wrapper = mount(ServiceDeskRoot, { - propsData: { - initialIsEnabled: true, - initialIncomingEmail, - endpoint, - }, - }); + await waitForPromises(); + }); + + it('sends a request to turn service desk on', () => { + axiosMock.onPut(provideData.endpoint).replyOnce(httpStatusCodes.OK); - wrapper.find('button.gl-toggle').trigger('click'); + expect(spy).toHaveBeenCalledWith(provideData.endpoint, { service_desk_enabled: true }); + }); - return wrapper.vm - .$nextTick() - .then(waitForPromises) - .then(() => { - expect(spy).toHaveBeenCalledWith(endpoint, { service_desk_enabled: false }); + it('shows a message when there is an error', () => { + axiosMock.onPut(provideData.endpoint).networkError(); + + expect(getAlertText()).toContain('An error occurred while enabling Service Desk.'); + }); }); - }); - it('sends a request to toggle service desk on when the toggle is clicked from the off state', () => { - axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK); + describe('when toggling service desk off', () => { + beforeEach(async () => { + wrapper = createComponent(); - spy = jest.spyOn(axios, 'put'); + wrapper.find(ServiceDeskSetting).vm.$emit('toggle', false); - wrapper = mount(ServiceDeskRoot, { - propsData: { - initialIsEnabled: false, - initialIncomingEmail: '', - endpoint, - }, - }); + await waitForPromises(); + }); - wrapper.find('button.gl-toggle').trigger('click'); + it('sends a request to turn service desk off', () => { + axiosMock.onPut(provideData.endpoint).replyOnce(httpStatusCodes.OK); - return wrapper.vm.$nextTick(() => { - expect(spy).toHaveBeenCalledWith(endpoint, { service_desk_enabled: true }); - }); - }); + expect(spy).toHaveBeenCalledWith(provideData.endpoint, { service_desk_enabled: false }); + }); - it('shows an error message when there is an issue toggling service desk on', () => { - axiosMock.onPut(endpoint).networkError(); + it('shows a message when there is an error', () => { + axiosMock.onPut(provideData.endpoint).networkError(); - wrapper = mount(ServiceDeskRoot, { - propsData: { - initialIsEnabled: false, - initialIncomingEmail: '', - endpoint, - }, + expect(getAlertText()).toContain('An error occurred while disabling Service Desk.'); + }); + }); }); - wrapper.find('button.gl-toggle').trigger('click'); + describe('save event', () => { + describe('successful request', () => { + beforeEach(async () => { + axiosMock.onPut(provideData.endpoint).replyOnce(httpStatusCodes.OK); - return wrapper.vm - .$nextTick() - .then(waitForPromises) - .then(() => { - expect(wrapper.html()).toContain('An error occurred while enabling Service Desk.'); - }); - }); + wrapper = createComponent(); - it('sends a request to update template when the "Save template" button is clicked', () => { - axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK); + const payload = { + selectedTemplate: 'Bug', + outgoingName: 'GitLab Support Bot', + projectKey: 'key', + }; - spy = jest.spyOn(axios, 'put'); + wrapper.find(ServiceDeskSetting).vm.$emit('save', payload); - wrapper = mount(ServiceDeskRoot, { - propsData: { - initialIsEnabled: true, - endpoint, - initialIncomingEmail, - selectedTemplate: 'Bug', - outgoingName: 'GitLab Support Bot', - templates: ['Bug', 'Documentation'], - projectKey: 'key', - }, - }); + await waitForPromises(); + }); - wrapper.find('button.btn-success').trigger('click'); + it('sends a request to update template', async () => { + expect(spy).toHaveBeenCalledWith(provideData.endpoint, { + issue_template_key: 'Bug', + outgoing_name: 'GitLab Support Bot', + project_key: 'key', + service_desk_enabled: true, + }); + }); - return wrapper.vm.$nextTick(() => { - expect(spy).toHaveBeenCalledWith(endpoint, { - issue_template_key: 'Bug', - outgoing_name: 'GitLab Support Bot', - project_key: 'key', - service_desk_enabled: true, + it('shows success message', () => { + expect(getAlertText()).toContain('Changes saved.'); + }); }); - }); - }); - it('saves the template when the "Save template" button is clicked', () => { - axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK); - - wrapper = mount(ServiceDeskRoot, { - propsData: { - initialIsEnabled: true, - endpoint, - initialIncomingEmail, - selectedTemplate: 'Bug', - templates: ['Bug', 'Documentation'], - }, - }); + describe('unsuccessful request', () => { + beforeEach(async () => { + axiosMock.onPut(provideData.endpoint).networkError(); - wrapper.find('button.btn-success').trigger('click'); + wrapper = createComponent(); - return wrapper.vm - .$nextTick() - .then(waitForPromises) - .then(() => { - expect(wrapper.html()).toContain('Changes saved.'); - }); - }); + const payload = { + selectedTemplate: 'Bug', + outgoingName: 'GitLab Support Bot', + projectKey: 'key', + }; - it('shows an error message when there is an issue saving the template', () => { - axiosMock.onPut(endpoint).networkError(); - - wrapper = mount(ServiceDeskRoot, { - propsData: { - initialIsEnabled: true, - endpoint, - initialIncomingEmail, - selectedTemplate: 'Bug', - templates: ['Bug', 'Documentation'], - }, - }); + wrapper.find(ServiceDeskSetting).vm.$emit('save', payload); - wrapper.find('button.btn-success').trigger('click'); + await waitForPromises(); + }); - return wrapper.vm - .$nextTick() - .then(waitForPromises) - .then(() => { - expect(wrapper.html()).toContain('An error occured while saving changes:'); + it('shows an error message', () => { + expect(getAlertText()).toContain('An error occured while saving changes:'); + }); }); - }); - - it('passes customEmail through updatedCustomEmail correctly', () => { - const customEmail = 'foo'; - - wrapper = mount(ServiceDeskRoot, { - propsData: { - initialIsEnabled: true, - endpoint, - customEmail, - }, }); - - expect(wrapper.find(ServiceDeskSetting).props('customEmail')).toEqual(customEmail); }); }); diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js index ddd9a7b2fad..51d656e0ea4 100644 --- a/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js +++ b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js @@ -1,63 +1,68 @@ import { shallowMount, mount } from '@vue/test-utils'; -import { GlLoadingIcon } from '@gitlab/ui'; -import eventHub from '~/projects/settings_service_desk/event_hub'; +import { GlButton, GlFormSelect, GlLoadingIcon, GlToggle } from '@gitlab/ui'; +import { nextTick } from 'vue'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import ServiceDeskSetting from '~/projects/settings_service_desk/components/service_desk_setting.vue'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; describe('ServiceDeskSetting', () => { let wrapper; + const findButton = () => wrapper.find(GlButton); + const findClipboardButton = () => wrapper.find(ClipboardButton); + const findIncomingEmail = () => wrapper.findByTestId('incoming-email'); + const findIncomingEmailLabel = () => wrapper.findByTestId('incoming-email-describer'); + const findLoadingIcon = () => wrapper.find(GlLoadingIcon); + const findTemplateDropdown = () => wrapper.find(GlFormSelect); + const findToggle = () => wrapper.find(GlToggle); + + const createComponent = ({ props = {}, mountFunction = shallowMount } = {}) => + extendedWrapper( + mountFunction(ServiceDeskSetting, { + propsData: { + isEnabled: true, + ...props, + }, + }), + ); + afterEach(() => { if (wrapper) { wrapper.destroy(); } }); - const findTemplateDropdown = () => wrapper.find('#service-desk-template-select'); - const findIncomingEmail = () => wrapper.find('[data-testid="incoming-email"]'); - describe('when isEnabled=true', () => { describe('only isEnabled', () => { describe('as project admin', () => { beforeEach(() => { - wrapper = shallowMount(ServiceDeskSetting, { - propsData: { - isEnabled: true, - }, - }); + wrapper = createComponent(); }); it('should see activation checkbox', () => { - expect(wrapper.find('#service-desk-checkbox').exists()).toBe(true); + expect(findToggle().exists()).toBe(true); }); it('should see main panel with the email info', () => { - expect(wrapper.find('#incoming-email-describer').exists()).toBe(true); + expect(findIncomingEmailLabel().exists()).toBe(true); }); it('should see loading spinner and not the incoming email', () => { - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + expect(findLoadingIcon().exists()).toBe(true); expect(findIncomingEmail().exists()).toBe(false); }); }); }); describe('service desk toggle', () => { - it('emits an event to turn on Service Desk when clicked', () => { - const eventSpy = jest.fn(); - eventHub.$on('serviceDeskEnabledCheckboxToggled', eventSpy); - - wrapper = mount(ServiceDeskSetting, { - propsData: { - isEnabled: false, - }, - }); + it('emits an event to turn on Service Desk when clicked', async () => { + wrapper = createComponent(); - wrapper.find('#service-desk-checkbox').trigger('click'); + findToggle().vm.$emit('change', true); - expect(eventSpy).toHaveBeenCalledWith(true); + await nextTick(); - eventHub.$off('serviceDeskEnabledCheckboxToggled', eventSpy); - eventSpy.mockRestore(); + expect(wrapper.emitted('toggle')[0]).toEqual([true]); }); }); @@ -65,23 +70,23 @@ describe('ServiceDeskSetting', () => { const incomingEmail = 'foo@bar.com'; beforeEach(() => { - wrapper = mount(ServiceDeskSetting, { - propsData: { - isEnabled: true, - incomingEmail, - }, + wrapper = createComponent({ + props: { incomingEmail }, }); }); it('should see email and not the loading spinner', () => { expect(findIncomingEmail().element.value).toEqual(incomingEmail); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + expect(findLoadingIcon().exists()).toBe(false); }); it('renders a copy to clipboard button', () => { - expect(wrapper.find('.qa-clipboard-button').exists()).toBe(true); - expect(wrapper.find('.qa-clipboard-button').element.dataset.clipboardText).toBe( - incomingEmail, + expect(findClipboardButton().exists()).toBe(true); + expect(findClipboardButton().props()).toEqual( + expect.objectContaining({ + title: 'Copy', + text: incomingEmail, + }), ); }); }); @@ -92,12 +97,8 @@ describe('ServiceDeskSetting', () => { const customEmail = 'custom@bar.com'; beforeEach(() => { - wrapper = mount(ServiceDeskSetting, { - propsData: { - isEnabled: true, - incomingEmail, - customEmail, - }, + wrapper = createComponent({ + props: { incomingEmail, customEmail }, }); }); @@ -110,12 +111,8 @@ describe('ServiceDeskSetting', () => { const email = 'foo@bar.com'; beforeEach(() => { - wrapper = mount(ServiceDeskSetting, { - propsData: { - isEnabled: true, - incomingEmail: email, - customEmail: email, - }, + wrapper = createComponent({ + props: { incomingEmail: email, customEmail: email }, }); }); @@ -127,21 +124,13 @@ describe('ServiceDeskSetting', () => { describe('templates dropdown', () => { it('renders a dropdown to choose a template', () => { - wrapper = shallowMount(ServiceDeskSetting, { - propsData: { - isEnabled: true, - }, - }); + wrapper = createComponent(); - expect(wrapper.find('#service-desk-template-select').exists()).toBe(true); + expect(findTemplateDropdown().exists()).toBe(true); }); it('renders a dropdown with a default value of ""', () => { - wrapper = mount(ServiceDeskSetting, { - propsData: { - isEnabled: true, - }, - }); + wrapper = createComponent({ mountFunction: mount }); expect(findTemplateDropdown().element.value).toEqual(''); }); @@ -149,23 +138,18 @@ describe('ServiceDeskSetting', () => { it('renders a dropdown with a value of "Bug" when it is the initial value', () => { const templates = ['Bug', 'Documentation', 'Security release']; - wrapper = mount(ServiceDeskSetting, { - propsData: { - isEnabled: true, - initialSelectedTemplate: 'Bug', - templates, - }, + wrapper = createComponent({ + props: { initialSelectedTemplate: 'Bug', templates }, + mountFunction: mount, }); expect(findTemplateDropdown().element.value).toEqual('Bug'); }); it('renders a dropdown with no options when the project has no templates', () => { - wrapper = mount(ServiceDeskSetting, { - propsData: { - isEnabled: true, - templates: [], - }, + wrapper = createComponent({ + props: { templates: [] }, + mountFunction: mount, }); // The dropdown by default has one empty option @@ -174,11 +158,10 @@ describe('ServiceDeskSetting', () => { it('renders a dropdown with options when the project has templates', () => { const templates = ['Bug', 'Documentation', 'Security release']; - wrapper = mount(ServiceDeskSetting, { - propsData: { - isEnabled: true, - templates, - }, + + wrapper = createComponent({ + props: { templates }, + mountFunction: mount, }); // An empty-named template is prepended so the user can select no template @@ -199,78 +182,59 @@ describe('ServiceDeskSetting', () => { describe('save button', () => { it('renders a save button to save a template', () => { - wrapper = mount(ServiceDeskSetting, { - propsData: { - isEnabled: true, - }, - }); + wrapper = createComponent(); - expect(wrapper.find('button.btn-success').text()).toContain('Save changes'); + expect(findButton().text()).toContain('Save changes'); }); - it('emits a save event with the chosen template when the save button is clicked', () => { - const eventSpy = jest.fn(); - eventHub.$on('serviceDeskTemplateSave', eventSpy); - - wrapper = mount(ServiceDeskSetting, { - propsData: { - isEnabled: true, + it('emits a save event with the chosen template when the save button is clicked', async () => { + wrapper = createComponent({ + props: { initialSelectedTemplate: 'Bug', initialOutgoingName: 'GitLab Support Bot', initialProjectKey: 'key', }, }); - wrapper.find('button.btn-success').trigger('click'); + findButton().vm.$emit('click'); + + await nextTick(); - expect(eventSpy).toHaveBeenCalledWith({ + const payload = { selectedTemplate: 'Bug', outgoingName: 'GitLab Support Bot', projectKey: 'key', - }); + }; - eventHub.$off('serviceDeskTemplateSave', eventSpy); - eventSpy.mockRestore(); + expect(wrapper.emitted('save')[0]).toEqual([payload]); }); }); describe('when isEnabled=false', () => { beforeEach(() => { - wrapper = shallowMount(ServiceDeskSetting, { - propsData: { - isEnabled: false, - }, + wrapper = createComponent({ + props: { isEnabled: false }, }); }); it('does not render email panel', () => { - expect(wrapper.find('#incoming-email-describer').exists()).toBe(false); + expect(findIncomingEmailLabel().exists()).toBe(false); }); it('does not render template dropdown', () => { - expect(wrapper.find('#service-desk-template-select').exists()).toBe(false); + expect(findTemplateDropdown().exists()).toBe(false); }); it('does not render template save button', () => { - expect(wrapper.find('button.btn-success').exists()).toBe(false); + expect(findButton().exists()).toBe(false); }); - it('emits an event to turn on Service Desk when the toggle is clicked', () => { - const eventSpy = jest.fn(); - eventHub.$on('serviceDeskEnabledCheckboxToggled', eventSpy); - - wrapper = mount(ServiceDeskSetting, { - propsData: { - isEnabled: true, - }, - }); - - wrapper.find('#service-desk-checkbox').trigger('click'); + it('emits an event to turn on Service Desk when the toggle is clicked', async () => { + findToggle().vm.$emit('change', false); - expect(eventSpy).toHaveBeenCalledWith(false); + await nextTick(); - eventHub.$off('serviceDeskEnabledCheckboxToggled', eventSpy); - eventSpy.mockRestore(); + expect(wrapper.emitted('toggle')[0]).toEqual([false]); }); }); }); diff --git a/spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js b/spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js deleted file mode 100644 index d5340df03fe..00000000000 --- a/spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js +++ /dev/null @@ -1,111 +0,0 @@ -import AxiosMockAdapter from 'axios-mock-adapter'; -import ServiceDeskService from '~/projects/settings_service_desk/services/service_desk_service'; -import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes from '~/lib/utils/http_status'; - -describe('ServiceDeskService', () => { - const endpoint = `/gitlab-org/gitlab-test/service_desk`; - const dummyResponse = { message: 'Dummy response' }; - const errorMessage = 'Network Error'; - let axiosMock; - let service; - - beforeEach(() => { - axiosMock = new AxiosMockAdapter(axios); - service = new ServiceDeskService(endpoint); - }); - - afterEach(() => { - axiosMock.restore(); - }); - - describe('toggleServiceDesk', () => { - it('makes a request to set service desk', () => { - axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse); - - return service.toggleServiceDesk(true).then((response) => { - expect(response.data).toEqual(dummyResponse); - }); - }); - - it('fails on error response', () => { - axiosMock.onPut(endpoint).networkError(); - - return service.toggleServiceDesk(true).catch((error) => { - expect(error.message).toBe(errorMessage); - }); - }); - - it('makes a request with the expected body', () => { - axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse); - - const spy = jest.spyOn(axios, 'put'); - - service.toggleServiceDesk(true); - - expect(spy).toHaveBeenCalledWith(endpoint, { - service_desk_enabled: true, - }); - - spy.mockRestore(); - }); - }); - - describe('updateTemplate', () => { - it('makes a request to update template', () => { - axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse); - - return service - .updateTemplate( - { - selectedTemplate: 'Bug', - outgoingName: 'GitLab Support Bot', - }, - true, - ) - .then((response) => { - expect(response.data).toEqual(dummyResponse); - }); - }); - - it('fails on error response', () => { - axiosMock.onPut(endpoint).networkError(); - - return service - .updateTemplate( - { - selectedTemplate: 'Bug', - outgoingName: 'GitLab Support Bot', - }, - true, - ) - .catch((error) => { - expect(error.message).toBe(errorMessage); - }); - }); - - it('makes a request with the expected body', () => { - axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse); - - const spy = jest.spyOn(axios, 'put'); - - service.updateTemplate( - { - selectedTemplate: 'Bug', - outgoingName: 'GitLab Support Bot', - projectKey: 'key', - }, - true, - ); - - expect(spy).toHaveBeenCalledWith(endpoint, { - issue_template_key: 'Bug', - outgoing_name: 'GitLab Support Bot', - project_key: 'key', - service_desk_enabled: true, - }); - - spy.mockRestore(); - }); - }); -}); diff --git a/spec/frontend/tracking_spec.js b/spec/frontend/tracking_spec.js index a516a4a8269..6fd1a330bb2 100644 --- a/spec/frontend/tracking_spec.js +++ b/spec/frontend/tracking_spec.js @@ -1,3 +1,5 @@ +// Work around for https://github.com/vuejs/eslint-plugin-vue/issues/1411 +/* eslint-disable vue/one-component-per-file */ import { setHTMLFixture } from 'helpers/fixtures'; import Tracking, { initUserTracking, initDefaultTrackers } from '~/tracking'; diff --git a/spec/lib/atlassian/jira_connect/client_spec.rb b/spec/lib/atlassian/jira_connect/client_spec.rb index 63f6573818b..2a4363ff2af 100644 --- a/spec/lib/atlassian/jira_connect/client_spec.rb +++ b/spec/lib/atlassian/jira_connect/client_spec.rb @@ -303,24 +303,6 @@ RSpec.describe Atlassian::JiraConnect::Client do expect(response['errorMessages']).to eq(['a: X', 'a: Y', 'b: Z']) end end - - it 'does not call the API if the feature flag is not enabled' do - stub_feature_flags(jira_sync_feature_flags: false) - - expect(subject).not_to receive(:post) - - subject.send(:store_ff_info, project: project, feature_flags: feature_flags) - end - - it 'does call the API if the feature flag enabled for the project' do - stub_feature_flags(jira_sync_feature_flags: project) - - expect(subject).to receive(:post).with('/rest/featureflags/0.1/bulk', { - flags: Array, properties: Hash - }).and_call_original - - subject.send(:store_ff_info, project: project, feature_flags: feature_flags) - end end describe '#store_build_info' do diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 8961cdcae7d..49f1e6e994f 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -720,7 +720,8 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do committer_name: "Dmitriy Zaporozhets", id: SeedRepo::Commit::ID, message: "tree css fixes", - parent_ids: ["874797c3a73b60d2187ed6e2fcabd289ff75171e"] + parent_ids: ["874797c3a73b60d2187ed6e2fcabd289ff75171e"], + trailers: {} } end end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index a93ee051ccf..79769f7ba7a 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -231,6 +231,7 @@ MergeRequestDiffCommit: - committer_name - committer_email - message +- trailers MergeRequestDiffFile: - merge_request_diff_id - relative_order @@ -255,6 +256,7 @@ MergeRequestContextCommit: - committer_email - message - merge_request_id +- trailers MergeRequestContextCommitDiffFile: - sha - relative_order diff --git a/spec/migrations/add_has_external_issue_tracker_trigger_spec.rb b/spec/migrations/add_has_external_issue_tracker_trigger_spec.rb new file mode 100644 index 00000000000..72983c7cfbf --- /dev/null +++ b/spec/migrations/add_has_external_issue_tracker_trigger_spec.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe AddHasExternalIssueTrackerTrigger do + let(:migration) { described_class.new } + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:services) { table(:services) } + + before do + @namespace = namespaces.create!(name: 'foo', path: 'foo') + @project = projects.create!(namespace_id: @namespace.id) + end + + describe '#up' do + before do + migrate! + end + + describe 'INSERT trigger' do + it 'sets `has_external_issue_tracker` to true when active `issue_tracker` is inserted' do + expect do + services.create!(category: 'issue_tracker', active: true, project_id: @project.id) + end.to change { @project.reload.has_external_issue_tracker }.to(true) + end + + it 'does not set `has_external_issue_tracker` to true when service is for a different project' do + different_project = projects.create!(namespace_id: @namespace.id) + + expect do + services.create!(category: 'issue_tracker', active: true, project_id: different_project.id) + end.not_to change { @project.reload.has_external_issue_tracker } + end + + it 'does not set `has_external_issue_tracker` to true when inactive `issue_tracker` is inserted' do + expect do + services.create!(category: 'issue_tracker', active: false, project_id: @project.id) + end.not_to change { @project.reload.has_external_issue_tracker } + end + + it 'does not set `has_external_issue_tracker` to true when a non-`issue tracker` active service is inserted' do + expect do + services.create!(category: 'my_type', active: true, project_id: @project.id) + end.not_to change { @project.reload.has_external_issue_tracker } + end + end + + describe 'UPDATE trigger' do + it 'sets `has_external_issue_tracker` to true when `issue_tracker` is made active' do + service = services.create!(category: 'issue_tracker', active: false, project_id: @project.id) + + expect do + service.update!(active: true) + end.to change { @project.reload.has_external_issue_tracker }.to(true) + end + + it 'sets `has_external_issue_tracker` to false when `issue_tracker` is made inactive' do + service = services.create!(category: 'issue_tracker', active: true, project_id: @project.id) + + expect do + service.update!(active: false) + end.to change { @project.reload.has_external_issue_tracker }.to(false) + end + + it 'sets `has_external_issue_tracker` to false when `issue_tracker` is made inactive, and an inactive `issue_tracker` exists' do + services.create!(category: 'issue_tracker', active: false, project_id: @project.id) + service = services.create!(category: 'issue_tracker', active: true, project_id: @project.id) + + expect do + service.update!(active: false) + end.to change { @project.reload.has_external_issue_tracker }.to(false) + end + + it 'does not change `has_external_issue_tracker` when `issue_tracker` is made inactive, if an active `issue_tracker` exists' do + services.create!(category: 'issue_tracker', active: true, project_id: @project.id) + service = services.create!(category: 'issue_tracker', active: true, project_id: @project.id) + + expect do + service.update!(active: false) + end.not_to change { @project.reload.has_external_issue_tracker } + end + + it 'does not change `has_external_issue_tracker` when service is for a different project' do + different_project = projects.create!(namespace_id: @namespace.id) + service = services.create!(category: 'issue_tracker', active: false, project_id: different_project.id) + + expect do + service.update!(active: true) + end.not_to change { @project.reload.has_external_issue_tracker } + end + end + + describe 'DELETE trigger' do + it 'sets `has_external_issue_tracker` to false when `issue_tracker` is deleted' do + service = services.create!(category: 'issue_tracker', active: true, project_id: @project.id) + + expect do + service.delete + end.to change { @project.reload.has_external_issue_tracker }.to(false) + end + + it 'sets `has_external_issue_tracker` to false when `issue_tracker` is deleted, if an inactive `issue_tracker` still exists' do + services.create!(category: 'issue_tracker', active: false, project_id: @project.id) + service = services.create!(category: 'issue_tracker', active: true, project_id: @project.id) + + expect do + service.delete + end.to change { @project.reload.has_external_issue_tracker }.to(false) + end + + it 'does not change `has_external_issue_tracker` when `issue_tracker` is deleted, if an active `issue_tracker` still exists' do + services.create!(category: 'issue_tracker', active: true, project_id: @project.id) + service = services.create!(category: 'issue_tracker', active: true, project_id: @project.id) + + expect do + service.delete + end.not_to change { @project.reload.has_external_issue_tracker } + end + + it 'does not change `has_external_issue_tracker` when service is for a different project' do + different_project = projects.create!(namespace_id: @namespace.id) + service = services.create!(category: 'issue_tracker', active: true, project_id: different_project.id) + + expect do + service.delete + end.not_to change { @project.reload.has_external_issue_tracker } + end + end + end + + describe '#down' do + before do + migration.up + migration.down + end + + it 'drops the INSERT trigger' do + expect do + services.create!(category: 'issue_tracker', active: true, project_id: @project.id) + end.not_to change { @project.reload.has_external_issue_tracker } + end + + it 'drops the UPDATE trigger' do + service = services.create!(category: 'issue_tracker', active: false, project_id: @project.id) + @project.update!(has_external_issue_tracker: false) + + expect do + service.update!(active: true) + end.not_to change { @project.reload.has_external_issue_tracker } + end + + it 'drops the DELETE trigger' do + service = services.create!(category: 'issue_tracker', active: true, project_id: @project.id) + @project.update!(has_external_issue_tracker: true) + + expect do + service.delete + end.not_to change { @project.reload.has_external_issue_tracker } + end + end +end diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb index 2edf44ecdc4..a24628b0f9d 100644 --- a/spec/models/merge_request_diff_commit_spec.rb +++ b/spec/models/merge_request_diff_commit_spec.rb @@ -48,7 +48,8 @@ RSpec.describe MergeRequestDiffCommit do "committer_email": "dmitriy.zaporozhets@gmail.com", "merge_request_diff_id": merge_request_diff_id, "relative_order": 0, - "sha": Gitlab::Database::ShaAttribute.serialize("5937ac0a7beb003549fc5fd26fc247adbce4a52e") + "sha": Gitlab::Database::ShaAttribute.serialize("5937ac0a7beb003549fc5fd26fc247adbce4a52e"), + "trailers": {}.to_json }, { "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", @@ -60,7 +61,8 @@ RSpec.describe MergeRequestDiffCommit do "committer_email": "dmitriy.zaporozhets@gmail.com", "merge_request_diff_id": merge_request_diff_id, "relative_order": 1, - "sha": Gitlab::Database::ShaAttribute.serialize("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") + "sha": Gitlab::Database::ShaAttribute.serialize("570e7b2abdd848b95f2f578043fc23bd6f6fd24d"), + "trailers": {}.to_json } ] end @@ -92,7 +94,8 @@ RSpec.describe MergeRequestDiffCommit do "committer_email": "alejorro70@gmail.com", "merge_request_diff_id": merge_request_diff_id, "relative_order": 0, - "sha": Gitlab::Database::ShaAttribute.serialize("ba3343bc4fa403a8dfbfcab7fc1a8c29ee34bd69") + "sha": Gitlab::Database::ShaAttribute.serialize("ba3343bc4fa403a8dfbfcab7fc1a8c29ee34bd69"), + "trailers": {}.to_json }] end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 1cf197322f5..b8a3f3bef6e 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -754,9 +754,8 @@ RSpec.describe MergeRequest, factory_default: :keep do context 'when both internal and external issue trackers are enabled' do before do - subject.project.has_external_issue_tracker = true - subject.project.save! create(:jira_service, project: subject.project) + subject.project.reload end it 'does not cache issues from external trackers' do @@ -1263,9 +1262,10 @@ RSpec.describe MergeRequest, factory_default: :keep do describe '#issues_mentioned_but_not_closing' do let(:closing_issue) { create :issue, project: subject.project } let(:mentioned_issue) { create :issue, project: subject.project } - let(:commit) { double('commit', safe_message: "Fixes #{closing_issue.to_reference}") } + subject { create(:merge_request, source_project: create(:project)) } + it 'detects issues mentioned in description but not closed' do subject.project.add_developer(subject.author) subject.description = "Is related to #{mentioned_issue.to_reference} and #{closing_issue.to_reference}" @@ -1279,13 +1279,12 @@ RSpec.describe MergeRequest, factory_default: :keep do end context 'when the project has an external issue tracker' do - subject { create(:merge_request, source_project: create(:project, :repository)) } - before do subject.project.add_developer(subject.author) commit = double(:commit, safe_message: 'Fixes TEST-3') create(:jira_service, project: subject.project) + subject.project.reload allow(subject).to receive(:commits).and_return([commit]) allow(subject).to receive(:description).and_return('Is related to TEST-2 and TEST-3') @@ -1469,6 +1468,8 @@ RSpec.describe MergeRequest, factory_default: :keep do end describe '#default_merge_commit_message' do + subject { create(:merge_request, source_project: create(:project)) } + it 'includes merge information as the title' do request = build(:merge_request, source_branch: 'source', target_branch: 'target') @@ -1645,7 +1646,7 @@ RSpec.describe MergeRequest, factory_default: :keep do end it_behaves_like 'an editable mentionable' do - subject { create(:merge_request, :simple) } + subject { create(:merge_request, :simple, source_project: create(:project, :repository)) } let(:backref_text) { "merge request #{subject.to_reference}" } let(:set_mentionable_text) { ->(txt) { subject.description = txt } } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index a2b51684d4d..8d0b7336955 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1002,103 +1002,125 @@ RSpec.describe Project, factory_default: :keep do end end - describe '#external_issue_tracker' do - let(:project) { create(:project) } - let(:ext_project) { create(:redmine_project) } + describe '#has_wiki?' do + let(:no_wiki_project) { create(:project, :wiki_disabled, has_external_wiki: false) } + let(:wiki_enabled_project) { create(:project) } + let(:external_wiki_project) { create(:project, has_external_wiki: true) } - context 'on existing projects with no value for has_external_issue_tracker' do - before do - project.update_column(:has_external_issue_tracker, nil) - ext_project.update_column(:has_external_issue_tracker, nil) + it 'returns true if project is wiki enabled or has external wiki' do + expect(wiki_enabled_project).to have_wiki + expect(external_wiki_project).to have_wiki + expect(no_wiki_project).not_to have_wiki + end + end + + describe '#default_owner' do + let_it_be(:owner) { create(:user) } + let_it_be(:namespace) { create(:namespace, owner: owner) } + + context 'the project does not have a group' do + let(:project) { build(:project, namespace: namespace) } + + it 'is the namespace owner' do + expect(project.default_owner).to eq(owner) end + end - it 'updates the has_external_issue_tracker boolean' do - expect do - project.external_issue_tracker - end.to change { project.reload.has_external_issue_tracker }.to(false) + context 'the project is in a group' do + let(:group) { build(:group) } + let(:project) { build(:project, group: group, namespace: namespace) } - expect do - ext_project.external_issue_tracker - end.to change { ext_project.reload.has_external_issue_tracker }.to(true) + it 'is the group owner' do + allow(group).to receive(:default_owner).and_return(Object.new) + + expect(project.default_owner).to eq(group.default_owner) end end + end + + describe '#external_issue_tracker' do + it 'sets Project#has_external_issue_tracker when it is nil' do + project_with_no_tracker = create(:project, has_external_issue_tracker: nil) + project_with_tracker = create(:redmine_project, has_external_issue_tracker: nil) + + expect do + project_with_no_tracker.external_issue_tracker + end.to change { project_with_no_tracker.reload.has_external_issue_tracker }.from(nil).to(false) + + expect do + project_with_tracker.external_issue_tracker + end.to change { project_with_tracker.reload.has_external_issue_tracker }.from(nil).to(true) + end it 'returns nil and does not query services when there is no external issue tracker' do - expect(project).not_to receive(:services) + project = create(:project) + expect(project).not_to receive(:services) expect(project.external_issue_tracker).to eq(nil) end it 'retrieves external_issue_tracker querying services and cache it when there is external issue tracker' do - ext_project.reload # Factory returns a project with changed attributes - expect(ext_project).to receive(:services).once.and_call_original + project = create(:redmine_project) - 2.times { expect(ext_project.external_issue_tracker).to be_a_kind_of(RedmineService) } + expect(project).to receive(:services).once.and_call_original + 2.times { expect(project.external_issue_tracker).to be_a_kind_of(RedmineService) } end end - describe '#cache_has_external_issue_tracker' do - let_it_be(:project) { create(:project, has_external_issue_tracker: nil) } - - it 'stores true if there is any external_issue_tracker' do - services = double(:service, external_issue_trackers: [RedmineService.new]) - expect(project).to receive(:services).and_return(services) + describe '#has_external_issue_tracker' do + let_it_be(:project) { create(:project) } - expect do - project.cache_has_external_issue_tracker - end.to change { project.has_external_issue_tracker}.to(true) + def subject + project.reload.has_external_issue_tracker end - it 'stores false if there is no external_issue_tracker' do - services = double(:service, external_issue_trackers: []) - expect(project).to receive(:services).and_return(services) + it 'is false when external issue tracker service is not active' do + create(:service, project: project, category: 'issue_tracker', active: false) - expect do - project.cache_has_external_issue_tracker - end.to change { project.has_external_issue_tracker}.to(false) + is_expected.to eq(false) end - it 'does not cache data when in a read-only GitLab instance' do - allow(Gitlab::Database).to receive(:read_only?) { true } + it 'is false when other service is active' do + create(:service, project: project, category: 'not_issue_tracker', active: true) - expect do - project.cache_has_external_issue_tracker - end.not_to change { project.has_external_issue_tracker } + is_expected.to eq(false) end - end - describe '#has_wiki?' do - let(:no_wiki_project) { create(:project, :wiki_disabled, has_external_wiki: false) } - let(:wiki_enabled_project) { create(:project) } - let(:external_wiki_project) { create(:project, has_external_wiki: true) } - - it 'returns true if project is wiki enabled or has external wiki' do - expect(wiki_enabled_project).to have_wiki - expect(external_wiki_project).to have_wiki - expect(no_wiki_project).not_to have_wiki - end - end + context 'when there is an active external issue tracker service' do + let!(:service) do + create(:service, project: project, type: 'JiraService', category: 'issue_tracker', active: true) + end - describe '#default_owner' do - let_it_be(:owner) { create(:user) } - let_it_be(:namespace) { create(:namespace, owner: owner) } + specify { is_expected.to eq(true) } - context 'the project does not have a group' do - let(:project) { build(:project, namespace: namespace) } + it 'becomes false when external issue tracker service is destroyed' do + expect do + Service.find(service.id).delete + end.to change { subject }.to(false) + end - it 'is the namespace owner' do - expect(project.default_owner).to eq(owner) + it 'becomes false when external issue tracker service becomes inactive' do + expect do + service.update_column(:active, false) + end.to change { subject }.to(false) end - end - context 'the project is in a group' do - let(:group) { build(:group) } - let(:project) { build(:project, group: group, namespace: namespace) } + context 'when there are two active external issue tracker services' do + let_it_be(:second_service) do + create(:service, project: project, type: 'CustomIssueTracker', category: 'issue_tracker', active: true) + end - it 'is the group owner' do - allow(group).to receive(:default_owner).and_return(Object.new) + it 'does not become false when external issue tracker service is destroyed' do + expect do + Service.find(service.id).delete + end.not_to change { subject } + end - expect(project.default_owner).to eq(group.default_owner) + it 'does not become false when external issue tracker service becomes inactive' do + expect do + service.update_column(:active, false) + end.not_to change { subject } + end end end end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 04b3920cd6c..25e947b6222 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -753,38 +753,6 @@ RSpec.describe Service do end end - describe "callbacks" do - let!(:service) do - RedmineService.new( - project: project, - active: true, - properties: { - project_url: 'http://redmine/projects/project_name_in_redmine', - issues_url: "http://redmine/#{project.id}/project_name_in_redmine/:id", - new_issue_url: 'http://redmine/projects/project_name_in_redmine/issues/new' - } - ) - end - - describe "on create" do - it "updates the has_external_issue_tracker boolean" do - expect do - service.save! - end.to change { service.project.has_external_issue_tracker }.from(false).to(true) - end - end - - describe "on update" do - it "updates the has_external_issue_tracker boolean" do - service.save! - - expect do - service.update(active: false) - end.to change { service.project.has_external_issue_tracker }.from(true).to(false) - end - end - end - describe '#api_field_names' do let(:fake_service) do Class.new(Service) do @@ -864,20 +832,6 @@ RSpec.describe Service do end end - describe '#external_issue_tracker?' do - where(:category, :active, :result) do - :issue_tracker | true | true - :issue_tracker | false | false - :common | true | false - end - - with_them do - it 'returns the right result' do - expect(build(:service, category: category, active: active).external_issue_tracker?).to eq(result) - end - end - end - describe '#external_wiki?' do where(:type, :active, :result) do 'ExternalWikiService' | true | true diff --git a/spec/requests/api/graphql/issue/issue_spec.rb b/spec/requests/api/graphql/issue/issue_spec.rb index 42c8e0cc9c0..09e89f65882 100644 --- a/spec/requests/api/graphql/issue/issue_spec.rb +++ b/spec/requests/api/graphql/issue/issue_spec.rb @@ -24,6 +24,20 @@ RSpec.describe 'Query.issue(id)' do end end + it_behaves_like 'a noteable graphql type we can query' do + let(:noteable) { issue } + let(:project) { issue.project } + let(:path_to_noteable) { [:issue] } + + before do + project.add_guest(current_user) + end + + def query(fields) + graphql_query_for('issue', issue_params, fields) + end + end + context 'when the user does not have access to the issue' do it 'returns nil' do project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) diff --git a/spec/requests/api/graphql/project/issue/designs/notes_spec.rb b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb index a671ddc7ab1..7148750b6cb 100644 --- a/spec/requests/api/graphql/project/issue/designs/notes_spec.rb +++ b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb @@ -22,6 +22,23 @@ RSpec.describe 'Getting designs related to an issue' do end end + it_behaves_like 'a noteable graphql type we can query' do + let(:noteable) { design } + let(:note_factory) { :diff_note_on_design } + let(:discussion_factory) { :diff_note_on_design } + let(:path_to_noteable) { [:issue, :design_collection, :designs, :nodes, 0] } + + before do + project.add_developer(current_user) + end + + def query(fields) + graphql_query_for(:issue, { id: global_id_of(issue) }, <<~FIELDS) + designCollection { designs { nodes { #{fields} } } } + FIELDS + end + end + it 'is not too deep for anonymous users' do note_fields = <<~FIELDS id @@ -37,7 +54,7 @@ RSpec.describe 'Getting designs related to an issue' do expect(note_data['id']).to eq(note.to_global_id.to_s) end - def query(note_fields = all_graphql_fields_for(Note)) + def query(note_fields = all_graphql_fields_for(Note, max_depth: 1)) design_node = <<~NODE designs { nodes { diff --git a/spec/services/bulk_create_integration_service_spec.rb b/spec/services/bulk_create_integration_service_spec.rb index 674382ee14f..3ac993972c6 100644 --- a/spec/services/bulk_create_integration_service_spec.rb +++ b/spec/services/bulk_create_integration_service_spec.rb @@ -43,46 +43,6 @@ RSpec.describe BulkCreateIntegrationService do end end - shared_examples 'updates project callbacks' do - it 'updates projects#has_external_issue_tracker for issue tracker services' do - described_class.new(integration, batch, association).execute - - expect(project.reload.has_external_issue_tracker).to eq(true) - expect(excluded_project.reload.has_external_issue_tracker).to eq(false) - end - - context 'with an external wiki integration' do - before do - integration.update!(category: 'common', type: 'ExternalWikiService') - end - - it 'updates projects#has_external_wiki for external wiki services' do - described_class.new(integration, batch, association).execute - - expect(project.reload.has_external_wiki).to eq(true) - expect(excluded_project.reload.has_external_wiki).to eq(false) - end - end - end - - shared_examples 'does not update project callbacks' do - it 'does not update projects#has_external_issue_tracker for issue tracker services' do - described_class.new(integration, batch, association).execute - - expect(project.reload.has_external_issue_tracker).to eq(false) - end - - context 'with an inactive external wiki integration' do - let(:integration) { create(:external_wiki_service, :instance, active: false) } - - it 'does not update projects#has_external_wiki for external wiki services' do - described_class.new(integration, batch, association).execute - - expect(project.reload.has_external_wiki).to eq(false) - end - end - end - context 'passing an instance-level integration' do let(:integration) { instance_integration } let(:inherit_from_id) { integration.id } @@ -95,15 +55,6 @@ RSpec.describe BulkCreateIntegrationService do it_behaves_like 'creates integration from batch ids' it_behaves_like 'updates inherit_from_id' - it_behaves_like 'updates project callbacks' - - context 'when integration is not active' do - before do - integration.update!(active: false) - end - - it_behaves_like 'does not update project callbacks' - end end context 'with a group association' do @@ -130,7 +81,6 @@ RSpec.describe BulkCreateIntegrationService do it_behaves_like 'creates integration from batch ids' it_behaves_like 'updates inherit_from_id' - it_behaves_like 'updates project callbacks' end context 'with a group association' do @@ -157,7 +107,6 @@ RSpec.describe BulkCreateIntegrationService do let(:inherit_from_id) { integration.id } it_behaves_like 'creates integration from batch ids' - it_behaves_like 'updates project callbacks' end end end diff --git a/spec/services/feature_flags/create_service_spec.rb b/spec/services/feature_flags/create_service_spec.rb index e115d8098c9..128fab114fe 100644 --- a/spec/services/feature_flags/create_service_spec.rb +++ b/spec/services/feature_flags/create_service_spec.rb @@ -66,18 +66,6 @@ RSpec.describe FeatureFlags::CreateService do subject end - context 'the feature flag is disabled' do - before do - stub_feature_flags(jira_sync_feature_flags: false) - end - - it 'does not sync the feature flag to Jira' do - expect(::JiraConnect::SyncFeatureFlagsWorker).not_to receive(:perform_async) - - subject - end - end - it 'creates audit event' do expected_message = 'Created feature flag <strong>feature_flag</strong> '\ 'with description <strong>"description"</strong>. '\ diff --git a/spec/services/feature_flags/update_service_spec.rb b/spec/services/feature_flags/update_service_spec.rb index 8c4055ddd9e..9639cf3081d 100644 --- a/spec/services/feature_flags/update_service_spec.rb +++ b/spec/services/feature_flags/update_service_spec.rb @@ -26,18 +26,6 @@ RSpec.describe FeatureFlags::UpdateService do expect(subject[:status]).to eq(:success) end - context 'the feature flag is disabled' do - before do - stub_feature_flags(jira_sync_feature_flags: false) - end - - it 'does not sync the feature flag to Jira' do - expect(::JiraConnect::SyncFeatureFlagsWorker).not_to receive(:perform_async) - - subject - end - end - it 'syncs the feature flag to Jira' do expect(::JiraConnect::SyncFeatureFlagsWorker).to receive(:perform_async).with(Integer, Integer) diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index dc545f57d23..3cf45143594 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -84,6 +84,7 @@ RSpec.describe Issues::CloseService do let!(:external_issue_tracker) { create(:jira_service, project: project) } it 'closes the issue on the external issue tracker' do + project.reload expect(project.external_issue_tracker).to receive(:close_issue) described_class.new(project, user).close_issue(external_issue) @@ -94,6 +95,7 @@ RSpec.describe Issues::CloseService do let!(:external_issue_tracker) { create(:jira_service, project: project, active: false) } it 'does not close the issue on the external issue tracker' do + project.reload expect(project.external_issue_tracker).not_to receive(:close_issue) described_class.new(project, user).close_issue(external_issue) @@ -104,6 +106,7 @@ RSpec.describe Issues::CloseService do let!(:external_issue_tracker) { create(:bugzilla_service, project: project) } it 'does not close the issue on the external issue tracker' do + project.reload expect(project.external_issue_tracker).not_to receive(:close_issue) described_class.new(project, user).close_issue(external_issue) diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index f83b8d98ce8..22b3456708f 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -252,6 +252,7 @@ RSpec.describe MergeRequests::BuildService do issue.update!(iid: 123) else create(:"#{issue_tracker}_service", project: project) + project.reload end end @@ -351,6 +352,7 @@ RSpec.describe MergeRequests::BuildService do issue.update!(iid: 123) else create(:"#{issue_tracker}_service", project: project) + project.reload end end diff --git a/spec/services/system_notes/issuables_service_spec.rb b/spec/services/system_notes/issuables_service_spec.rb index 96afca2f2cb..ae18bc23c17 100644 --- a/spec/services/system_notes/issuables_service_spec.rb +++ b/spec/services/system_notes/issuables_service_spec.rb @@ -729,12 +729,14 @@ RSpec.describe ::SystemNotes::IssuablesService do it 'is false with issue tracker supporting referencing' do create(:jira_service, project: project) + project.reload expect(service.cross_reference_disallowed?(noteable)).to be_falsey end it 'is true with issue tracker not supporting referencing' do create(:bugzilla_service, project: project) + project.reload expect(service.cross_reference_disallowed?(noteable)).to be_truthy end diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index 35c298a4d48..013a870796c 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -510,8 +510,12 @@ module GraphqlHelpers end end - def global_id_of(model) - model.to_global_id.to_s + def global_id_of(model, id: nil, model_name: nil) + if id || model_name + ::Gitlab::GlobalId.build(model, id: id, model_name: model_name).to_s + else + model.to_global_id.to_s + end end def missing_required_argument(path, argument) diff --git a/spec/support/shared_examples/models/concerns/can_housekeep_repository_shared_examples.rb b/spec/support/shared_examples/models/concerns/can_housekeep_repository_shared_examples.rb deleted file mode 100644 index 2f0b95427d2..00000000000 --- a/spec/support/shared_examples/models/concerns/can_housekeep_repository_shared_examples.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples 'can housekeep repository' do - context 'with a clean redis state', :clean_gitlab_redis_shared_state do - describe '#pushes_since_gc' do - context 'without any pushes' do - it 'returns 0' do - expect(resource.pushes_since_gc).to eq(0) - end - end - - context 'with a number of pushes' do - it 'returns the number of pushes' do - 3.times { resource.increment_pushes_since_gc } - - expect(resource.pushes_since_gc).to eq(3) - end - end - end - - describe '#increment_pushes_since_gc' do - it 'increments the number of pushes since the last GC' do - 3.times { resource.increment_pushes_since_gc } - - expect(resource.pushes_since_gc).to eq(3) - end - end - - describe '#reset_pushes_since_gc' do - it 'resets the number of pushes since the last GC' do - 3.times { resource.increment_pushes_since_gc } - - resource.reset_pushes_since_gc - - expect(resource.pushes_since_gc).to eq(0) - end - end - - describe '#pushes_since_gc_redis_shared_state_key' do - it 'returns the proper redis key format' do - expect(resource.send(:pushes_since_gc_redis_shared_state_key)).to eq("#{resource_key}/#{resource.id}/pushes_since_gc") - end - end - end -end diff --git a/spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb new file mode 100644 index 00000000000..9cf5bc04f65 --- /dev/null +++ b/spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +# Requires `query(fields)`, `path_to_noteable`, `project`, and `noteable` bindings +RSpec.shared_examples 'a noteable graphql type we can query' do + let(:note_factory) { :note } + let(:discussion_factory) { :discussion_note } + + describe '.discussions' do + let(:fields) do + "discussions { nodes { #{all_graphql_fields_for('Discussion')} } }" + end + + def expected + noteable.discussions.map do |discussion| + include( + 'id' => global_id_of(discussion), + 'replyId' => global_id_of(discussion, id: discussion.reply_id), + 'createdAt' => discussion.created_at.iso8601, + 'notes' => include( + 'nodes' => have_attributes(size: discussion.notes.size) + ) + ) + end + end + + it 'can fetch discussions' do + create(discussion_factory, project: project, noteable: noteable) + + post_graphql(query(fields), current_user: current_user) + + expect(graphql_data_at(*path_to_noteable, :discussions, :nodes)) + .to match_array(expected) + end + end + + describe '.notes' do + let(:fields) do + "notes { nodes { #{all_graphql_fields_for('Note', max_depth: 2)} } }" + end + + def expected + noteable.notes.map do |note| + include( + 'id' => global_id_of(note), + 'project' => include('id' => global_id_of(project)), + 'author' => include('id' => global_id_of(note.author)), + 'createdAt' => note.created_at.iso8601, + 'body' => eq(note.note) + ) + end + end + + it 'can fetch notes' do + create(note_factory, project: project, noteable: noteable) + + post_graphql(query(fields), current_user: current_user) + + expect(graphql_data_at(*path_to_noteable, :notes, :nodes)) + .to match_array(expected) + end + end +end diff --git a/spec/workers/jira_connect/sync_feature_flags_worker_spec.rb b/spec/workers/jira_connect/sync_feature_flags_worker_spec.rb index 035f4ebdd3c..038eed7b9f1 100644 --- a/spec/workers/jira_connect/sync_feature_flags_worker_spec.rb +++ b/spec/workers/jira_connect/sync_feature_flags_worker_spec.rb @@ -32,29 +32,5 @@ RSpec.describe ::JiraConnect::SyncFeatureFlagsWorker do subject end end - - context 'when the feature flag is disabled' do - before do - stub_feature_flags(jira_sync_feature_flags: false) - end - - it 'does not call the sync service' do - expect_next(::JiraConnect::SyncService).not_to receive(:execute) - - subject - end - end - - context 'when the feature flag is enabled for this project' do - before do - stub_feature_flags(jira_sync_feature_flags: feature_flag.project) - end - - it 'calls the sync service' do - expect_next(::JiraConnect::SyncService).to receive(:execute) - - subject - end - end end end diff --git a/workhorse/CHANGELOG b/workhorse/CHANGELOG index 42c13465ba9..a871f671201 100644 --- a/workhorse/CHANGELOG +++ b/workhorse/CHANGELOG @@ -1,5 +1,17 @@ # Changelog for gitlab-workhorse +## v8.60.0 + +### Added +- Support Git HTTP on toplevel repositories + https://gitlab.com/gitlab-org/gitlab-workhorse/-/merge_requests/670 +- Update GoCloud to v0.21.1+ + https://gitlab.com/gitlab-org/gitlab-workhorse/-/merge_requests/675 + +### Changed +- Allow blank S3 regions to be used + https://gitlab.com/gitlab-org/gitlab-workhorse/-/merge_requests/677 + ## v8.59.0 ### Fixed diff --git a/workhorse/VERSION b/workhorse/VERSION index 8c20f999400..a6c6f8ed977 100644 --- a/workhorse/VERSION +++ b/workhorse/VERSION @@ -1 +1 @@ -8.59.0 +8.60.0 diff --git a/workhorse/gitaly_test.go b/workhorse/gitaly_test.go index 95d6907ac6a..3c3c957063c 100644 --- a/workhorse/gitaly_test.go +++ b/workhorse/gitaly_test.go @@ -169,6 +169,57 @@ func TestGetInfoRefsProxiedToGitalyInterruptedStream(t *testing.T) { waitDone(t, done) } +func TestGetInfoRefsRouting(t *testing.T) { + gitalyServer, socketPath := startGitalyServer(t, codes.OK) + defer gitalyServer.GracefulStop() + + apiResponse := gitOkBody(t) + apiResponse.GitalyServer.Address = "unix:" + socketPath + ts := testAuthServer(t, nil, nil, 200, apiResponse) + defer ts.Close() + + ws := startWorkhorseServer(ts.URL) + defer ws.Close() + + testCases := []struct { + method string + path string + match bool + }{ + {"GET", "/toplevel.git/info/refs?service=git-receive-pack", true}, + {"GET", "/toplevel.wiki.git/info/refs?service=git-upload-pack", true}, + {"GET", "/toplevel/child/project.git/info/refs?service=git-receive-pack", true}, + {"GET", "/toplevel/child/project.wiki.git/info/refs?service=git-upload-pack", true}, + {"GET", "/toplevel/child/project/snippets/123.git/info/refs?service=git-receive-pack", true}, + {"GET", "/snippets/123.git/info/refs?service=git-upload-pack", true}, + {"GET", "/foo/bar.git/info/refs", false}, + {"GET", "/foo/bar.git/info/refs?service=git-zzz-pack", false}, + {"GET", "/.git/info/refs?service=git-upload-pack", false}, + {"POST", "/toplevel.git/info/refs?service=git-receive-pack", false}, + } + + for _, tc := range testCases { + t.Run(tc.path, func(t *testing.T) { + req, err := http.NewRequest(tc.method, ws.URL+tc.path, nil) + require.NoError(t, err) + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + body := string(testhelper.ReadAll(t, resp.Body)) + + if tc.match { + require.Equal(t, 200, resp.StatusCode) + require.Contains(t, body, "\x00", "expect response generated by test gitaly server") + } else { + require.Equal(t, 204, resp.StatusCode) + require.Empty(t, body, "normal request has empty response body") + } + }) + } +} + func waitDone(t *testing.T, done chan struct{}) { t.Helper() select { @@ -259,6 +310,65 @@ func TestPostReceivePackProxiedToGitalyInterrupted(t *testing.T) { waitDone(t, done) } +func TestPostReceivePackRouting(t *testing.T) { + gitalyServer, socketPath := startGitalyServer(t, codes.OK) + defer gitalyServer.GracefulStop() + + apiResponse := gitOkBody(t) + apiResponse.GitalyServer.Address = "unix:" + socketPath + ts := testAuthServer(t, nil, nil, 200, apiResponse) + defer ts.Close() + + ws := startWorkhorseServer(ts.URL) + defer ws.Close() + + testCases := []struct { + method string + path string + contentType string + match bool + }{ + {"POST", "/toplevel.git/git-receive-pack", "application/x-git-receive-pack-request", true}, + {"POST", "/toplevel.wiki.git/git-receive-pack", "application/x-git-receive-pack-request", true}, + {"POST", "/toplevel/child/project.git/git-receive-pack", "application/x-git-receive-pack-request", true}, + {"POST", "/toplevel/child/project.wiki.git/git-receive-pack", "application/x-git-receive-pack-request", true}, + {"POST", "/toplevel/child/project/snippets/123.git/git-receive-pack", "application/x-git-receive-pack-request", true}, + {"POST", "/snippets/123.git/git-receive-pack", "application/x-git-receive-pack-request", true}, + {"POST", "/foo/bar/git-receive-pack", "application/x-git-receive-pack-request", false}, + {"POST", "/foo/bar.git/git-zzz-pack", "application/x-git-receive-pack-request", false}, + {"POST", "/.git/git-receive-pack", "application/x-git-receive-pack-request", false}, + {"POST", "/toplevel.git/git-receive-pack", "application/x-git-upload-pack-request", false}, + {"GET", "/toplevel.git/git-receive-pack", "application/x-git-receive-pack-request", false}, + } + + for _, tc := range testCases { + t.Run(tc.path, func(t *testing.T) { + req, err := http.NewRequest( + tc.method, + ws.URL+tc.path, + bytes.NewReader(testhelper.GitalyReceivePackResponseMock), + ) + require.NoError(t, err) + + req.Header.Set("Content-Type", tc.contentType) + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + body := string(testhelper.ReadAll(t, resp.Body)) + + if tc.match { + require.Equal(t, 200, resp.StatusCode) + require.Contains(t, body, "\x00", "expect response generated by test gitaly server") + } else { + require.Equal(t, 204, resp.StatusCode) + require.Empty(t, body, "normal request has empty response body") + } + }) + } +} + // ReaderFunc is an adapter to turn a conforming function into an io.Reader. type ReaderFunc func(b []byte) (int, error) @@ -376,6 +486,65 @@ func TestPostUploadPackProxiedToGitalyInterrupted(t *testing.T) { waitDone(t, done) } +func TestPostUploadPackRouting(t *testing.T) { + gitalyServer, socketPath := startGitalyServer(t, codes.OK) + defer gitalyServer.GracefulStop() + + apiResponse := gitOkBody(t) + apiResponse.GitalyServer.Address = "unix:" + socketPath + ts := testAuthServer(t, nil, nil, 200, apiResponse) + defer ts.Close() + + ws := startWorkhorseServer(ts.URL) + defer ws.Close() + + testCases := []struct { + method string + path string + contentType string + match bool + }{ + {"POST", "/toplevel.git/git-upload-pack", "application/x-git-upload-pack-request", true}, + {"POST", "/toplevel.wiki.git/git-upload-pack", "application/x-git-upload-pack-request", true}, + {"POST", "/toplevel/child/project.git/git-upload-pack", "application/x-git-upload-pack-request", true}, + {"POST", "/toplevel/child/project.wiki.git/git-upload-pack", "application/x-git-upload-pack-request", true}, + {"POST", "/toplevel/child/project/snippets/123.git/git-upload-pack", "application/x-git-upload-pack-request", true}, + {"POST", "/snippets/123.git/git-upload-pack", "application/x-git-upload-pack-request", true}, + {"POST", "/foo/bar/git-upload-pack", "application/x-git-upload-pack-request", false}, + {"POST", "/foo/bar.git/git-zzz-pack", "application/x-git-upload-pack-request", false}, + {"POST", "/.git/git-upload-pack", "application/x-git-upload-pack-request", false}, + {"POST", "/toplevel.git/git-upload-pack", "application/x-git-receive-pack-request", false}, + {"GET", "/toplevel.git/git-upload-pack", "application/x-git-upload-pack-request", false}, + } + + for _, tc := range testCases { + t.Run(tc.path, func(t *testing.T) { + req, err := http.NewRequest( + tc.method, + ws.URL+tc.path, + bytes.NewReader(testhelper.GitalyReceivePackResponseMock), + ) + require.NoError(t, err) + + req.Header.Set("Content-Type", tc.contentType) + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + body := string(testhelper.ReadAll(t, resp.Body)) + + if tc.match { + require.Equal(t, 200, resp.StatusCode) + require.Contains(t, body, "\x00", "expect response generated by test gitaly server") + } else { + require.Equal(t, 204, resp.StatusCode) + require.Empty(t, body, "normal request has empty response body") + } + }) + } +} + func TestGetDiffProxiedToGitalySuccessfully(t *testing.T) { gitalyServer, socketPath := startGitalyServer(t, codes.OK) defer gitalyServer.GracefulStop() diff --git a/workhorse/go.mod b/workhorse/go.mod index 5fed8de4796..6396e419487 100644 --- a/workhorse/go.mod +++ b/workhorse/go.mod @@ -3,11 +3,11 @@ module gitlab.com/gitlab-org/gitlab-workhorse go 1.13 require ( - github.com/Azure/azure-storage-blob-go v0.10.0 + github.com/Azure/azure-storage-blob-go v0.11.1-0.20201209121048-6df5d9af221d github.com/BurntSushi/toml v0.3.1 github.com/FZambia/sentinel v1.0.0 github.com/alecthomas/chroma v0.7.3 - github.com/aws/aws-sdk-go v1.31.13 + github.com/aws/aws-sdk-go v1.36.1 github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/disintegration/imaging v1.6.2 @@ -15,7 +15,7 @@ require ( github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721 github.com/golang/protobuf v1.4.3 github.com/gomodule/redigo v2.0.0+incompatible - github.com/gorilla/websocket v1.4.0 + github.com/gorilla/websocket v1.4.1 github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/johannesboyne/gofakes3 v0.0.0-20200510090907-02d71f533bec @@ -30,11 +30,15 @@ require ( github.com/stretchr/testify v1.6.1 gitlab.com/gitlab-org/gitaly v1.74.0 gitlab.com/gitlab-org/labkit v1.0.0 - gocloud.dev v0.20.0 + gocloud.dev v0.21.1-0.20201223184910-5094f54ed8bb golang.org/x/lint v0.0.0-20200302205851-738671d3881b - golang.org/x/net v0.0.0-20200625001655-4c5254603344 - golang.org/x/tools v0.0.0-20200608174601-1b747fd94509 - google.golang.org/grpc v1.29.1 + golang.org/x/net v0.0.0-20201224014010-6772e930b67b + golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061 // indirect + golang.org/x/text v0.3.5 // indirect + golang.org/x/tools v0.0.0-20201203202102-a1a1cbeaa516 + google.golang.org/genproto v0.0.0-20210111234610-22ae2b108f89 // indirect + google.golang.org/grpc v1.34.1 + google.golang.org/grpc/examples v0.0.0-20201226181154-53788aa5dcb4 // indirect honnef.co/go/tools v0.0.1-2020.1.5 ) diff --git a/workhorse/go.sum b/workhorse/go.sum index 0b92dda088c..4796d40638b 100644 --- a/workhorse/go.sum +++ b/workhorse/go.sum @@ -6,18 +6,19 @@ cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSR cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.55.0/go.mod h1:ZHmoY+/lIMNkN2+fBmuTiqZ4inFhvQad8ft7MT8IV5Y= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.58.0 h1:vtAfVc723K3xKq1BQydk/FyCldnaNFhGhpJxaJzgRMQ= -cloud.google.com/go v0.58.0/go.mod h1:W+9FnSUw6nhVwXlFcp1eL+krq5+HQUJeUogSeJZZiWg= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko= +cloud.google.com/go v0.72.0 h1:eWRCuwubtDrCJG0oSUMgnsbD4CmPFQF2ei4OFbXvwww= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -28,60 +29,64 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.2.0/go.mod h1:iISCjWnTpnoJT1R287xRdjvQHJrxQOpeah4phb5D3h0= +cloud.google.com/go/firestore v1.4.0/go.mod h1:NjjGEnxCS3CAKYp+vmALu20QzcqasGodQp48WxJGAYc= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.9.0/go.mod h1:G3o6/kJvEMIEAN5urdkaP4be49WQsjNiykBIto9LFtY= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.9.0 h1:oXnZyBjHB6hC8TnSle0AWW6pGJ29EuSo5ww+SFmdNBg= -cloud.google.com/go/storage v1.9.0/go.mod h1:m+/etGaqZbylxaNT876QGXqEHp4PR2Rq5GMqICWb9bU= -contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= -contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw= -contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= -contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.12.0 h1:4y3gHptW1EHVtcPAVE0eBBlFuGqEejTTG3KdIE0lUX4= +cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7nillojlL5Ho= +contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= +contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= +contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/Azure/azure-amqp-common-go/v3 v3.0.0/go.mod h1:SY08giD/XbhTz07tJdpw1SoxQXHPN30+DI3Z04SYqyg= -github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= -github.com/Azure/azure-pipeline-go v0.2.2 h1:6oiIS9yaG6XCCzhgAgKFfIWyo4LLCiDhZot6ltoThhY= -github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= +github.com/Azure/azure-amqp-common-go/v3 v3.0.1/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0= +github.com/Azure/azure-amqp-common-go/v3 v3.1.0/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0= +github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= +github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-sdk-for-go v37.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-service-bus-go v0.10.1/go.mod h1:E/FOceuKAFUfpbIJDKWz/May6guE+eGibfGT6q+n1to= -github.com/Azure/azure-storage-blob-go v0.9.0/go.mod h1:8UBPbiOhrMQ4pLPi3gA1tXnpjrS76UYE/fo5A40vf4g= -github.com/Azure/azure-storage-blob-go v0.10.0 h1:evCwGreYo3XLeBV4vSxLbLiYb6e0SzsJiXQVRGsRXxs= -github.com/Azure/azure-storage-blob-go v0.10.0/go.mod h1:ep1edmW+kNQx4UfWM9heESNmQdijykocJ0YOxmMX8SE= -github.com/Azure/go-amqp v0.12.6/go.mod h1:qApuH6OFTSKZFmCOxccvAv5rLizBQf4v8pRmG138DPo= -github.com/Azure/go-amqp v0.12.7/go.mod h1:qApuH6OFTSKZFmCOxccvAv5rLizBQf4v8pRmG138DPo= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest v0.9.3 h1:OZEIaBbMdUE/Js+BQKlpO81XlISgipr6yDJ+PSwsgi4= -github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= -github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= -github.com/Azure/go-autorest/autorest/adal v0.8.3 h1:O1AGG9Xig71FxdX9HO5pGNyZ7TbSyHaVg+5eJO/jSGw= -github.com/Azure/go-autorest/autorest/adal v0.8.3/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= -github.com/Azure/go-autorest/autorest/azure/auth v0.4.2 h1:iM6UAvjR97ZIeR93qTcwpKNMpV+/FTWjwEbuPD495Tk= -github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= -github.com/Azure/go-autorest/autorest/azure/cli v0.3.1 h1:LXl088ZQlP0SBppGFsRZonW6hSvwgL5gRByMbvUbx8U= -github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM= -github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc= -github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= -github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= -github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= -github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/azure-sdk-for-go v49.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-service-bus-go v0.10.7/go.mod h1:o5z/3lDG1iT/T/G7vgIwIqVDTx9Qa2wndf5OdzSzpF8= +github.com/Azure/azure-storage-blob-go v0.11.1-0.20201209121048-6df5d9af221d h1:YEjZNZ0HS7ITX+BJ7wUXtTk6GXM3g8xftaqQ94XU/cs= +github.com/Azure/azure-storage-blob-go v0.11.1-0.20201209121048-6df5d9af221d/go.mod h1:A0u4VjtpgZJ7Y7um/+ix2DHBuEKFC6sEIlj0xc13a4Q= +github.com/Azure/go-amqp v0.13.0/go.mod h1:qj+o8xPCz9tMSbQ83Vp8boHahuRDl5mkNHyt1xlxUTs= +github.com/Azure/go-amqp v0.13.1/go.mod h1:qj+o8xPCz9tMSbQ83Vp8boHahuRDl5mkNHyt1xlxUTs= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.3/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest v0.11.7/go.mod h1:V6p3pKZx1KKkJubbxnDWrzNhEIfOy/pTGasLqzHIPHs= +github.com/Azure/go-autorest/autorest v0.11.9/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest v0.11.12 h1:gI8ytXbxMfI+IVbI9mP2JGCTXIuhHLgRlvQ9X4PsnHE= +github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= +github.com/Azure/go-autorest/autorest/adal v0.9.4/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/adal v0.9.6 h1:d3pSDwvBWBLqdA91u+keH1zs1cCEzrQdHKY6iqbQNkE= +github.com/Azure/go-autorest/autorest/adal v0.9.6/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.3 h1:lZifaPRAk1bqg5vGqreL6F8uLC5V0fDpY8nFvc3boFc= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.3/go.mod h1:4bJZhUhcq8LB20TruwHbAQsmUs2Xh+QR7utuJpLXX3A= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 h1:dMOmEJfkLKW/7JsokJqkyoYSgmR08hi9KrhjZb+JALY= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= +github.com/Azure/go-autorest/autorest/validation v0.3.0/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= +github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8Pcx+3oqrE= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -89,7 +94,7 @@ github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EF github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= github.com/FZambia/sentinel v1.0.0 h1:KJ0ryjKTZk5WMp0dXvSdNqp3lFaW1fNFuEYfrkLOYIc= github.com/FZambia/sentinel v1.0.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI= -github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo= +github.com/GoogleCloudPlatform/cloudsql-proxy v1.19.1/go.mod h1:+yYmuKqcBVkgRePGpUhTA9OEg0XsnFE96eZ6nJ2yCQM= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= @@ -123,10 +128,10 @@ github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6l github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.17.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.31.13 h1:UeWMTRTL0XAKLR7vxDL4/u7KOtz/LtfJr+lXtxN4YEQ= -github.com/aws/aws-sdk-go v1.31.13/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.36.1 h1:rDgSL20giXXu48Ycx6Qa4vWaNTVTltUl6vA73ObCSVk= +github.com/aws/aws-sdk-go v1.36.1/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -137,8 +142,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 h1:uH66TXeswKn5PW5zdZ39xEwfS9an067BirqA+P4QaLI= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= @@ -153,6 +158,7 @@ github.com/client9/reopen v1.0.0 h1:8tpLVR74DLpLObrn2KvsyxJY++2iORGR17WLUdSzUws= github.com/client9/reopen v1.0.0/go.mod h1:caXVCEr+lUtoN1FlsRiOWdfQtdRHIYfcb0ai8qKWtkQ= github.com/cloudflare/tableflip v1.2.1-0.20200514155827-4baec9811f2b/go.mod h1:vhhSlJqV8uUnxGkRSgyvGthfGlkAwJ4UuSV51fSrCQY= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= @@ -171,6 +177,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= @@ -178,6 +185,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= @@ -193,17 +202,22 @@ github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4s github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/getsentry/raven-go v0.1.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= @@ -213,7 +227,9 @@ github.com/getsentry/sentry-go v0.7.0 h1:MR2yfR4vFfv/2+iBuSnkdQwVg7N9cJzihZ6KJu7 github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= @@ -228,6 +244,10 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= @@ -261,6 +281,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -288,28 +310,40 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/go-replayers/grpcreplay v0.1.0 h1:eNb1y9rZFmY4ax45uEEECSa8fsxGRU+8Bil52ASAwic= -github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE= -github.com/google/go-replayers/httpreplay v0.1.0 h1:AX7FUb4BjrrzNvblr/OlgwrmFiep6soj5K2QSDW7BGk= -github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no= +github.com/google/go-replayers/grpcreplay v1.0.0 h1:B5kVOzJ1hBgnevTgIWhSTatQ3608yu/2NnU0Ta1d0kY= +github.com/google/go-replayers/grpcreplay v1.0.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE= +github.com/google/go-replayers/httpreplay v0.1.2 h1:HCfx+dQzwN9XbGTHF8qJ+67WN8glL9FTWV5rraCJ/jU= +github.com/google/go-replayers/httpreplay v0.1.2/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible h1:xmapqc1AyLoB+ddYT6r04bD9lIjlOqGaREovi0SzFaE= github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c h1:lIC98ZUNah83ky7d9EXktLFe4H7Nwus59dTOLXr8xAI= -github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c h1:Jx2lEv4nMccTJE+IIZOVIvk+DjNKlRsW0sm1uBr896U= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.4.0 h1:kXcsA/rIGzJImVqPdhfnr6q0xsS9gU0515q1EPpJ9fE= github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww= @@ -325,6 +359,8 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 h1:FlFbCRLd5Jr4iYXZufAvgWN6Ao0JrI5chLINnUXDDr0= @@ -357,6 +393,7 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= @@ -366,8 +403,10 @@ github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62 github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/johannesboyne/gofakes3 v0.0.0-20200510090907-02d71f533bec h1:jEZFmuFe51KdrceqM4NL3dJiuog0zojzcN/VculG26o= github.com/johannesboyne/gofakes3 v0.0.0-20200510090907-02d71f533bec/go.mod h1:fNiSoOiEI5KlkWXn26OwKnNe58ilTIkpBlgOrt7Olu8= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= @@ -377,6 +416,7 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= @@ -399,6 +439,8 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -411,8 +453,9 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libgit2/git2go/v30 v30.0.5/go.mod h1:YReiQ7xhMoyAL4ISYFLZt+OGqn6xtLqvTC1xJ9oAH7Y= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743 h1:143Bb8f8DuGWck/xpNUOckBVYfFbBTnLevfRZ1aVVqo= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= @@ -424,8 +467,6 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= -github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -457,6 +498,8 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -475,6 +518,8 @@ github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7 github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc= @@ -642,6 +687,8 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDf github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= gitlab.com/gitlab-org/gitaly v1.87.1-0.20201001041716-3f5e218def93 h1:5qkRBchgs4IvlbRdJTMISuktLF1ZtLMowyhzQteEeKI= gitlab.com/gitlab-org/gitaly v1.87.1-0.20201001041716-3f5e218def93/go.mod h1:NEpGSBkjMt7yV5SB1MFySVQqTKFEUdfTDxS76Rt7GC8= @@ -660,6 +707,9 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= @@ -669,8 +719,8 @@ go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -gocloud.dev v0.20.0 h1:mbEKMfnyPV7W1Rj35R1xXfjszs9dXkwSOq2KoFr25g8= -gocloud.dev v0.20.0/go.mod h1:+Y/RpSXrJthIOM8uFNzWp6MRu9pFPNFEEZrQMxpkfIc= +gocloud.dev v0.21.1-0.20201223184910-5094f54ed8bb h1:3EJw/ZRo3jKHY2WP8HiAxCpGlfsrKpaIeCl6VfMprKw= +gocloud.dev v0.21.1-0.20201223184910-5094f54ed8bb/go.mod h1:iI47kpBb27cms1+KJCbcO2NbZBOg4V6SJWFeO/Kpp1c= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -681,9 +731,12 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c h1:9HhBz5L/UjnK9XLtiZhYAdue5BVKep3PMmS2LuPDt8k= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -719,6 +772,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -737,11 +792,12 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -755,16 +811,28 @@ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201203001011-0b49973bad19 h1:ZD+2Sd/BnevwJp8PSli8WgGAGzb9IZtxBsv1iZMYeEA= +golang.org/x/oauth2 v0.0.0-20201203001011-0b49973bad19/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -773,6 +841,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -798,6 +868,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -811,7 +882,6 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -820,21 +890,37 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201202213521-69691e467435 h1:25AvDqqB9PrNqj1FLf2/70I4W0L19qqoaFq3gjNwbKk= +golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061 h1:DQmQoKxQWtyybCtX/3dIuDBcAhFszqq8YiNeS6sNu1c= +golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -858,6 +944,7 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190829051458-42f498d34c4d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -879,26 +966,36 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200317043434-63da46f3035e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200601175630-2caf76543d99/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200606014950-c42cb6316fb6/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200608174601-1b747fd94509 h1:MI14dOfl3OG6Zd32w3ugsrvcUO810fDZdWakTq39dH4= -golang.org/x/tools v0.0.0-20200608174601-1b747fd94509/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201202200335-bef1c476418a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201203202102-a1a1cbeaa516 h1:E8xavSjXY8LFvcMSu/8Fjztt+SerwKnuAUOdS+aCXUM= +golang.org/x/tools v0.0.0-20201203202102-a1a1cbeaa516/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= @@ -908,16 +1005,25 @@ google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.26.0 h1:VJZ8h6E8ip82FRpQl848c5vAadxlTXrUh8RzQzSRm08= -google.golang.org/api v0.26.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.31.0/go.mod h1:CL+9IBCa2WWU6gRuBWaKqGWLFFwbEUXkfeMkHLQWYWo= +google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0 h1:l2Nfbl2GPXdWorv+dT2XfinX2jOOw4zv1VhLstx+6rE= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -940,17 +1046,27 @@ google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200317114155-1f3552e48f24/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200325114520-5b2d0af7952b/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200603110839-e855014d5736/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200608115520-7c474a2e3482 h1:i+Aiej6cta/Frzp13/swvwz5O00kYcSe0A/C5Wd7zX8= -google.golang.org/genproto v0.0.0-20200608115520-7c474a2e3482/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200914193844-75d14daec038/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200921151605-7abf4a1a14d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201203001206-6486ece9c497 h1:jDYzwXmX9tLnuG4sL85HPmE1ruErXOopALp2i/0AHnI= +google.golang.org/genproto v0.0.0-20201203001206-6486ece9c497/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210111234610-22ae2b108f89 h1:R2owLnwrU3BdTJ5R9cnHDNsnEmBQ7n5lZjKShnbISe4= +google.golang.org/genproto v0.0.0-20210111234610-22ae2b108f89/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -968,6 +1084,16 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.34.1 h1:ugq+9++ZQPFzM2pKUMCIK8gj9M0pFyuUWO9Q8kwEDQw= +google.golang.org/grpc v1.34.1/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc/examples v0.0.0-20201226181154-53788aa5dcb4 h1:tfxAh8kBsG9GdCdaDiSCA1qqpd8lMOqgEebUyqTtnH8= +google.golang.org/grpc/examples v0.0.0-20201226181154-53788aa5dcb4/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -978,6 +1104,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/DataDog/dd-trace-go.v1 v1.7.0 h1:7wbMayb6JXcbAS95RN7MI42W3o1BCxCcdIzZfVWBAiE= gopkg.in/DataDog/dd-trace-go.v1 v1.7.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -985,6 +1113,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= @@ -1018,6 +1148,7 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.5 h1:nI5egYTGJakVyOryqLs1cQO5dO0ksin5XXs2pspk75k= honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/workhorse/internal/filestore/save_file_opts.go b/workhorse/internal/filestore/save_file_opts.go index 1eb708c3f55..d0b2c6ec809 100644 --- a/workhorse/internal/filestore/save_file_opts.go +++ b/workhorse/internal/filestore/save_file_opts.go @@ -146,7 +146,7 @@ func (c *ObjectStorageConfig) IsGoCloud() bool { func (c *ObjectStorageConfig) IsValid() bool { if c.IsAWS() { - return c.S3Config.Bucket != "" && c.S3Config.Region != "" && c.s3CredentialsValid() + return c.S3Config.Bucket != "" && c.s3CredentialsValid() } else if c.IsGoCloud() { // We could parse and validate the URL, but GoCloud providers // such as AzureRM don't have a fallback to normal HTTP, so we diff --git a/workhorse/internal/filestore/save_file_opts_test.go b/workhorse/internal/filestore/save_file_opts_test.go index 2d6cd683b51..facfb1cdc85 100644 --- a/workhorse/internal/filestore/save_file_opts_test.go +++ b/workhorse/internal/filestore/save_file_opts_test.go @@ -187,6 +187,9 @@ func TestUseWorkhorseClientEnabled(t *testing.T) { iamConfig := missingCfg iamConfig.S3Config.UseIamProfile = true + missingRegion := cfg + missingRegion.S3Config.Region = "" + tests := []struct { name string UseWorkhorseClient bool @@ -245,6 +248,13 @@ func TestUseWorkhorseClientEnabled(t *testing.T) { }, expected: false, }, + { + name: "missing S3 region", + UseWorkhorseClient: true, + remoteTempObjectID: "test-object", + objectStorageConfig: missingRegion, + expected: true, + }, } for _, test := range tests { diff --git a/workhorse/internal/git/info-refs.go b/workhorse/internal/git/info-refs.go index e5491a7b733..6d0826a22f9 100644 --- a/workhorse/internal/git/info-refs.go +++ b/workhorse/internal/git/info-refs.go @@ -20,6 +20,11 @@ func GetInfoRefsHandler(a *api.API) http.Handler { return repoPreAuthorizeHandler(a, handleGetInfoRefs) } +func IsSmartInfoRefs(r *http.Request) bool { + service := r.URL.Query().Get("service") + return r.Method == "GET" && (service == "git-upload-pack" || service == "git-receive-pack") +} + func handleGetInfoRefs(rw http.ResponseWriter, r *http.Request, a *api.Response) { responseWriter := NewHttpResponseWriter(rw) // Log 0 bytes in because we ignore the request body (and there usually is none anyway). diff --git a/workhorse/internal/git/info-refs_test.go b/workhorse/internal/git/info-refs_test.go new file mode 100644 index 00000000000..ed66518b8d3 --- /dev/null +++ b/workhorse/internal/git/info-refs_test.go @@ -0,0 +1,33 @@ +package git + +import ( + "net/http" + "net/url" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestIsSmartInfoRefs(t *testing.T) { + testCases := []struct { + method string + url string + match bool + }{ + {"GET", "?service=git-upload-pack", true}, + {"GET", "?service=git-receive-pack", true}, + {"GET", "", false}, + {"GET", "?service=", false}, + {"GET", "?service=foo", false}, + {"POST", "?service=git-upload-pack", false}, + {"POST", "?service=git-receive-pack", false}, + } + + for _, tc := range testCases { + url, err := url.Parse(tc.url) + require.NoError(t, err) + + r := http.Request{Method: tc.method, URL: url} + require.Equal(t, tc.match, IsSmartInfoRefs(&r)) + } +} diff --git a/workhorse/internal/upstream/routes.go b/workhorse/internal/upstream/routes.go index 5bbd245719b..b8a97a1b5f6 100644 --- a/workhorse/internal/upstream/routes.go +++ b/workhorse/internal/upstream/routes.go @@ -53,13 +53,13 @@ type uploadPreparers struct { } const ( - apiPattern = `^/api/` - ciAPIPattern = `^/ci/api/` - gitProjectPattern = `^/([^/]+/){1,}[^/]+\.git/` - projectPattern = `^/([^/]+/){1,}[^/]+/` - snippetUploadPattern = `^/uploads/personal_snippet` - userUploadPattern = `^/uploads/user` - importPattern = `^/import/` + apiPattern = `\A/api/` + ciAPIPattern = `\A/ci/api/` + gitRepositoryPattern = `\A/.+\.git/` + projectPattern = `\A/([^/]+/){1,}[^/]+/` + snippetUploadPattern = `\A/uploads/personal_snippet` + userUploadPattern = `\A/uploads/user` + importPattern = `\A/import/` ) func compileRegexp(regexpStr string) *regexp.Regexp { @@ -222,10 +222,10 @@ func (u *upstream) configureRoutes() { u.Routes = []routeEntry{ // Git Clone - u.route("GET", gitProjectPattern+`info/refs\z`, git.GetInfoRefsHandler(api)), - u.route("POST", gitProjectPattern+`git-upload-pack\z`, contentEncodingHandler(git.UploadPack(api)), withMatcher(isContentType("application/x-git-upload-pack-request"))), - u.route("POST", gitProjectPattern+`git-receive-pack\z`, contentEncodingHandler(git.ReceivePack(api)), withMatcher(isContentType("application/x-git-receive-pack-request"))), - u.route("PUT", gitProjectPattern+`gitlab-lfs/objects/([0-9a-f]{64})/([0-9]+)\z`, lfs.PutStore(api, signingProxy, preparers.lfs), withMatcher(isContentType("application/octet-stream"))), + u.route("GET", gitRepositoryPattern+`info/refs\z`, git.GetInfoRefsHandler(api), withMatcher(git.IsSmartInfoRefs)), + u.route("POST", gitRepositoryPattern+`git-upload-pack\z`, contentEncodingHandler(git.UploadPack(api)), withMatcher(isContentType("application/x-git-upload-pack-request"))), + u.route("POST", gitRepositoryPattern+`git-receive-pack\z`, contentEncodingHandler(git.ReceivePack(api)), withMatcher(isContentType("application/x-git-receive-pack-request"))), + u.route("PUT", gitRepositoryPattern+`gitlab-lfs/objects/([0-9a-f]{64})/([0-9]+)\z`, lfs.PutStore(api, signingProxy, preparers.lfs), withMatcher(isContentType("application/octet-stream"))), // CI Artifacts u.route("POST", apiPattern+`v4/jobs/[0-9]+/artifacts\z`, contentEncodingHandler(artifacts.UploadArtifacts(api, signingProxy, preparers.artifacts))), diff --git a/workhorse/main_test.go b/workhorse/main_test.go index d15af1d3e4c..b725f36a68a 100644 --- a/workhorse/main_test.go +++ b/workhorse/main_test.go @@ -694,6 +694,12 @@ func testAuthServer(t *testing.T, url *regexp.Regexp, params url.Values, code in return testhelper.TestServerWithHandler(url, func(w http.ResponseWriter, r *http.Request) { require.NotEmpty(t, r.Header.Get("X-Request-Id")) + // return a 204 No Content response if we don't receive the JWT header + if r.Header.Get(secret.RequestHeader) == "" { + w.WriteHeader(204) + return + } + w.Header().Set("Content-Type", api.ResponseContentType) logEntry := log.WithFields(log.Fields{ diff --git a/workhorse/upload_test.go b/workhorse/upload_test.go index 1e5d9bd00e9..4ec65b6607f 100644 --- a/workhorse/upload_test.go +++ b/workhorse/upload_test.go @@ -234,7 +234,7 @@ func TestLfsUpload(t *testing.T) { reqBody := "test data" rspBody := "test success" oid := "916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9" - resource := fmt.Sprintf("/%s/gitlab-lfs/objects/%s/%d", testRepo, oid, len(reqBody)) + resource := fmt.Sprintf("/gitlab-org/gitlab-test.git/gitlab-lfs/objects/%s/%d", oid, len(reqBody)) lfsApiResponse := fmt.Sprintf( `{"TempPath":%q, "LfsOid":%q, "LfsSize": %d}`, @@ -292,6 +292,74 @@ func TestLfsUpload(t *testing.T) { require.Equal(t, rspBody, string(rspData)) } +func TestLfsUploadRouting(t *testing.T) { + reqBody := "test data" + rspBody := "test success" + oid := "916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9" + + ts := testhelper.TestServerWithHandler(regexp.MustCompile(`.`), func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get(secret.RequestHeader) == "" { + w.WriteHeader(204) + } else { + fmt.Fprint(w, rspBody) + } + }) + defer ts.Close() + + ws := startWorkhorseServer(ts.URL) + defer ws.Close() + + testCases := []struct { + method string + path string + contentType string + match bool + }{ + {"PUT", "/toplevel.git/gitlab-lfs/objects", "application/octet-stream", true}, + {"PUT", "/toplevel.wiki.git/gitlab-lfs/objects", "application/octet-stream", true}, + {"PUT", "/toplevel/child/project.git/gitlab-lfs/objects", "application/octet-stream", true}, + {"PUT", "/toplevel/child/project.wiki.git/gitlab-lfs/objects", "application/octet-stream", true}, + {"PUT", "/toplevel/child/project/snippets/123.git/gitlab-lfs/objects", "application/octet-stream", true}, + {"PUT", "/snippets/123.git/gitlab-lfs/objects", "application/octet-stream", true}, + {"PUT", "/foo/bar/gitlab-lfs/objects", "application/octet-stream", false}, + {"PUT", "/foo/bar.git/gitlab-lfs/objects/zzz", "application/octet-stream", false}, + {"PUT", "/.git/gitlab-lfs/objects", "application/octet-stream", false}, + {"PUT", "/toplevel.git/gitlab-lfs/objects", "application/zzz", false}, + {"POST", "/toplevel.git/gitlab-lfs/objects", "application/octet-stream", false}, + } + + for _, tc := range testCases { + t.Run(tc.path, func(t *testing.T) { + resource := fmt.Sprintf(tc.path+"/%s/%d", oid, len(reqBody)) + + req, err := http.NewRequest( + tc.method, + ws.URL+resource, + strings.NewReader(reqBody), + ) + require.NoError(t, err) + + req.Header.Set("Content-Type", tc.contentType) + req.ContentLength = int64(len(reqBody)) + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + rspData, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + + if tc.match { + require.Equal(t, 200, resp.StatusCode) + require.Equal(t, rspBody, string(rspData), "expect response generated by test upstream server") + } else { + require.Equal(t, 204, resp.StatusCode) + require.Empty(t, rspData, "normal request has empty response body") + } + }) + } +} + func packageUploadTestServer(t *testing.T, resource string, reqBody string, rspBody string) *httptest.Server { return testhelper.TestServerWithHandler(regexp.MustCompile(`.`), func(w http.ResponseWriter, r *http.Request) { require.Equal(t, r.Method, "PUT") |