summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-14 00:07:42 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-14 00:07:42 +0000
commit5225ffb5ccfe2fe0e55a3327d43f28f4ed08ae63 (patch)
treeceb6a117475daa1aa17632e1c297863b53195897
parentc0b9c14ebd1524a1e2334e656f997ec680a18966 (diff)
downloadgitlab-ce-5225ffb5ccfe2fe0e55a3327d43f28f4ed08ae63.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/error_tracking/components/error_details.vue8
-rw-r--r--app/assets/javascripts/error_tracking/components/error_details_info.vue2
-rw-r--r--app/assets/javascripts/error_tracking/components/error_tracking_list.vue16
-rw-r--r--app/assets/javascripts/error_tracking/events_tracking.js (renamed from app/assets/javascripts/error_tracking/utils.js)24
-rw-r--r--app/assets/javascripts/import/details/components/import_details_table.vue8
-rw-r--r--app/models/ci/build_trace.rb6
-rw-r--r--config/feature_flags/development/import_details_page.yml2
-rw-r--r--config/feature_flags/development/sign_and_verify_ansi2json_state.yml8
-rw-r--r--db/docs/batched_background_migrations/cleanup_personal_access_tokens_with_nil_expires_at.yml6
-rw-r--r--db/post_migrate/20221021082256_finish_reset_duplicate_ci_runners_token_values.rb21
-rw-r--r--db/post_migrate/20221021082257_add_unique_index_on_ci_runners_token.rb (renamed from db/post_migrate/20221021082255_add_unique_index_on_ci_runners_token.rb)8
-rw-r--r--db/post_migrate/20221021082313_finish_reset_duplicate_ci_runners_token_encrypted_values.rb21
-rw-r--r--db/post_migrate/20221021082314_add_unique_index_on_ci_runners_token_encrypted.rb (renamed from db/post_migrate/20221021082312_add_unique_index_on_ci_runners_token_encrypted.rb)8
-rw-r--r--db/post_migrate/20230510062503_queue_cleanup_personal_access_tokens_with_nil_expires_at.rb23
-rw-r--r--db/schema_migrations/202210210822551
-rw-r--r--db/schema_migrations/202210210822561
-rw-r--r--db/schema_migrations/202210210822571
-rw-r--r--db/schema_migrations/202210210823121
-rw-r--r--db/schema_migrations/202210210823131
-rw-r--r--db/schema_migrations/202210210823141
-rw-r--r--db/schema_migrations/202305100625031
-rw-r--r--lib/gitlab/background_migration/cleanup_personal_access_tokens_with_nil_expires_at.rb22
-rw-r--r--lib/gitlab/ci/ansi2json.rb4
-rw-r--r--lib/gitlab/ci/ansi2json/converter.rb8
-rw-r--r--lib/gitlab/ci/ansi2json/signed_state.rb74
-rw-r--r--lib/gitlab/ci/ansi2json/state.rb71
-rw-r--r--lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb5
-rw-r--r--spec/frontend/error_tracking/components/error_details_info_spec.js2
-rw-r--r--spec/frontend/error_tracking/components/error_details_spec.js11
-rw-r--r--spec/frontend/error_tracking/components/error_tracking_list_spec.js24
-rw-r--r--spec/frontend/error_tracking/events_tracking_spec.js (renamed from spec/frontend/error_tracking/utils_spec.js)2
-rw-r--r--spec/frontend/import/details/mock_data.js6
-rw-r--r--spec/lib/gitlab/background_migration/cleanup_personal_access_tokens_with_nil_expires_at_spec.rb38
-rw-r--r--spec/lib/gitlab/ci/ansi2json/signed_state_spec.rb67
-rw-r--r--spec/lib/gitlab/ci/ansi2json/state_spec.rb51
-rw-r--r--spec/lib/gitlab/ci/ansi2json_spec.rb16
-rw-r--r--spec/migrations/20230510062502_queue_cleanup_personal_access_tokens_with_nil_expires_at_spec.rb26
-rw-r--r--spec/models/ci/build_trace_spec.rb20
38 files changed, 335 insertions, 280 deletions
diff --git a/app/assets/javascripts/error_tracking/components/error_details.vue b/app/assets/javascripts/error_tracking/components/error_details.vue
index 0a661d51576..ccadf940fe3 100644
--- a/app/assets/javascripts/error_tracking/components/error_details.vue
+++ b/app/assets/javascripts/error_tracking/components/error_details.vue
@@ -17,7 +17,11 @@ import Tracking from '~/tracking';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import query from '../queries/details.query.graphql';
-import { trackErrorDetailsViewsOptions, trackErrorStatusUpdateOptions } from '../utils';
+import {
+ trackErrorDetailsViewsOptions,
+ trackErrorStatusUpdateOptions,
+ trackCreateIssueFromError,
+} from '../events_tracking';
import { severityLevel, severityLevelVariant, errorStatus } from '../constants';
import Stacktrace from './stacktrace.vue';
import ErrorDetailsInfo from './error_details_info.vue';
@@ -184,6 +188,8 @@ export default {
]),
createIssue() {
this.issueCreationInProgress = true;
+ const { category, action } = trackCreateIssueFromError;
+ Tracking.event(category, action);
this.$refs.sentryIssueForm.submit();
},
onIgnoreStatusUpdate() {
diff --git a/app/assets/javascripts/error_tracking/components/error_details_info.vue b/app/assets/javascripts/error_tracking/components/error_details_info.vue
index bbc7b0de7cf..f6f39f178fb 100644
--- a/app/assets/javascripts/error_tracking/components/error_details_info.vue
+++ b/app/assets/javascripts/error_tracking/components/error_details_info.vue
@@ -2,7 +2,7 @@
import { GlLink, GlIcon, GlCard, GlTooltipDirective } from '@gitlab/ui';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event';
-import { trackClickErrorLinkToSentryOptions } from '../utils';
+import { trackClickErrorLinkToSentryOptions } from '../events_tracking';
const CARD_CLASS = 'gl-mr-7 gl-w-15p gl-min-w-fit-content';
const HEADER_CLASS =
diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
index 2a4bb88b6c2..6750f0f5411 100644
--- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
+++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
@@ -23,7 +23,12 @@ import { __ } from '~/locale';
import Tracking from '~/tracking';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { sanitizeUrl } from '~/lib/utils/url_utility';
-import { trackErrorListViewsOptions, trackErrorStatusUpdateOptions } from '../utils';
+import {
+ trackErrorListViewsOptions,
+ trackErrorStatusUpdateOptions,
+ trackErrorStatusFilterOptions,
+ trackErrorSortedByField,
+} from '../events_tracking';
import { I18N_ERROR_TRACKING_LIST } from '../constants';
import ErrorTrackingActions from './error_tracking_actions.vue';
@@ -237,8 +242,15 @@ export default {
},
filterErrors(status, label) {
this.filterValue = label;
+ const { category, action } = trackErrorStatusFilterOptions(status);
+ Tracking.event(category, action);
return this.filterByStatus(status);
},
+ sortErrorsByField(field) {
+ const { category, action } = trackErrorSortedByField(field);
+ Tracking.event(category, action);
+ return this.sortByField(field);
+ },
updateErrosStatus({ errorId, status }) {
// eslint-disable-next-line promise/catch-or-return
this.updateStatus({
@@ -371,7 +383,7 @@ export default {
<gl-dropdown-item
v-for="(label, field) in $options.sortFields"
:key="field"
- @click="sortByField(field)"
+ @click="sortErrorsByField(field)"
>
<span class="d-flex">
<gl-icon
diff --git a/app/assets/javascripts/error_tracking/utils.js b/app/assets/javascripts/error_tracking/events_tracking.js
index afb91d3db51..aaef274d0cd 100644
--- a/app/assets/javascripts/error_tracking/utils.js
+++ b/app/assets/javascripts/error_tracking/events_tracking.js
@@ -34,3 +34,27 @@ export const trackErrorStatusUpdateOptions = (status) => ({
category,
action: `update_${status}_status`,
});
+
+/**
+ * Tracks snowplow event when error list is filter by status
+ */
+export const trackErrorStatusFilterOptions = (status) => ({
+ category,
+ action: `filter_${status}_status`,
+});
+
+/**
+ * Tracks snowplow event when error list is sorted by field
+ */
+export const trackErrorSortedByField = (field) => ({
+ category,
+ action: `sort_by_${field}`,
+});
+
+/**
+ * Tracks snowplow event when the Create Issue button is clicked
+ */
+export const trackCreateIssueFromError = {
+ category,
+ action: 'click_create_issue_from_error',
+};
diff --git a/app/assets/javascripts/import/details/components/import_details_table.vue b/app/assets/javascripts/import/details/components/import_details_table.vue
index b32b5778265..813dc1f2645 100644
--- a/app/assets/javascripts/import/details/components/import_details_table.vue
+++ b/app/assets/javascripts/import/details/components/import_details_table.vue
@@ -34,7 +34,7 @@ export default {
tdClass: 'gl-md-w-30 gl-word-break-word',
},
{
- key: 'url',
+ key: 'provider_url',
label: __('URL'),
tdClass: 'gl-white-space-nowrap',
},
@@ -141,9 +141,9 @@ export default {
<template #cell(type)="{ item: { type } }">
{{ $options.STATISTIC_ITEMS[type] }}
</template>
- <template #cell(url)="{ item: { url } }">
- <gl-link v-if="url" :href="url" target="_blank">
- {{ url }}
+ <template #cell(provider_url)="{ item: { provider_url } }">
+ <gl-link v-if="provider_url" :href="provider_url" target="_blank">
+ {{ provider_url }}
<gl-icon name="external-link" />
</gl-link>
</template>
diff --git a/app/models/ci/build_trace.rb b/app/models/ci/build_trace.rb
index b9a74102641..f70e1ed69ea 100644
--- a/app/models/ci/build_trace.rb
+++ b/app/models/ci/build_trace.rb
@@ -12,11 +12,7 @@ module Ci
if stream.valid?
stream.limit
- @trace = Gitlab::Ci::Ansi2json.convert(
- stream.stream,
- state,
- verify_state: Feature.enabled?(:sign_and_verify_ansi2json_state, build.project)
- )
+ @trace = Gitlab::Ci::Ansi2json.convert(stream.stream, state)
end
end
diff --git a/config/feature_flags/development/import_details_page.yml b/config/feature_flags/development/import_details_page.yml
index 412fe90f40a..d59e2945ae9 100644
--- a/config/feature_flags/development/import_details_page.yml
+++ b/config/feature_flags/development/import_details_page.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/397650
milestone: '15.11'
type: development
group: group::import
-default_enabled: false
+default_enabled: true
diff --git a/config/feature_flags/development/sign_and_verify_ansi2json_state.yml b/config/feature_flags/development/sign_and_verify_ansi2json_state.yml
deleted file mode 100644
index af9286dc3cd..00000000000
--- a/config/feature_flags/development/sign_and_verify_ansi2json_state.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: sign_and_verify_ansi2json_state
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/116625
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/404718
-milestone: '15.11'
-type: development
-group: group::pipeline execution
-default_enabled: true
diff --git a/db/docs/batched_background_migrations/cleanup_personal_access_tokens_with_nil_expires_at.yml b/db/docs/batched_background_migrations/cleanup_personal_access_tokens_with_nil_expires_at.yml
new file mode 100644
index 00000000000..630aeccd6e1
--- /dev/null
+++ b/db/docs/batched_background_migrations/cleanup_personal_access_tokens_with_nil_expires_at.yml
@@ -0,0 +1,6 @@
+---
+migration_job_name: CleanupPersonalAccessTokensWithInvalidExpiresAt
+description: Updates value of expires_at column to 365 days from now when it's nil for PersonalAccessTokens
+feature_category: system_access
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120239
+milestone: 16.0
diff --git a/db/post_migrate/20221021082256_finish_reset_duplicate_ci_runners_token_values.rb b/db/post_migrate/20221021082256_finish_reset_duplicate_ci_runners_token_values.rb
new file mode 100644
index 00000000000..4e6195bb3c8
--- /dev/null
+++ b/db/post_migrate/20221021082256_finish_reset_duplicate_ci_runners_token_values.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class FinishResetDuplicateCiRunnersTokenValues < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_ci
+
+ def up
+ ensure_batched_background_migration_is_finished(
+ job_class_name: 'ResetDuplicateCiRunnersTokenValues',
+ table_name: :ci_runners,
+ column_name: :id,
+ job_arguments: [],
+ finalize: true
+ )
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20221021082255_add_unique_index_on_ci_runners_token.rb b/db/post_migrate/20221021082257_add_unique_index_on_ci_runners_token.rb
index 3dfa44f9615..3858c0efe06 100644
--- a/db/post_migrate/20221021082255_add_unique_index_on_ci_runners_token.rb
+++ b/db/post_migrate/20221021082257_add_unique_index_on_ci_runners_token.rb
@@ -6,12 +6,10 @@ class AddUniqueIndexOnCiRunnersToken < Gitlab::Database::Migration[2.0]
INDEX_NAME = 'index_uniq_ci_runners_on_token'
def up
- finalize_background_migration 'ResetDuplicateCiRunnersTokenValues'
-
add_concurrent_index :ci_runners,
- :token,
- name: INDEX_NAME,
- unique: true
+ :token,
+ name: INDEX_NAME,
+ unique: true
end
def down
diff --git a/db/post_migrate/20221021082313_finish_reset_duplicate_ci_runners_token_encrypted_values.rb b/db/post_migrate/20221021082313_finish_reset_duplicate_ci_runners_token_encrypted_values.rb
new file mode 100644
index 00000000000..ba08322b1ff
--- /dev/null
+++ b/db/post_migrate/20221021082313_finish_reset_duplicate_ci_runners_token_encrypted_values.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class FinishResetDuplicateCiRunnersTokenEncryptedValues < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_ci
+
+ def up
+ ensure_batched_background_migration_is_finished(
+ job_class_name: 'ResetDuplicateCiRunnersTokenEncryptedValues',
+ table_name: :ci_runners,
+ column_name: :id,
+ job_arguments: [],
+ finalize: true
+ )
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20221021082312_add_unique_index_on_ci_runners_token_encrypted.rb b/db/post_migrate/20221021082314_add_unique_index_on_ci_runners_token_encrypted.rb
index 8728c0ff20e..12fc6a72e84 100644
--- a/db/post_migrate/20221021082312_add_unique_index_on_ci_runners_token_encrypted.rb
+++ b/db/post_migrate/20221021082314_add_unique_index_on_ci_runners_token_encrypted.rb
@@ -6,12 +6,10 @@ class AddUniqueIndexOnCiRunnersTokenEncrypted < Gitlab::Database::Migration[2.0]
INDEX_NAME = 'index_uniq_ci_runners_on_token_encrypted'
def up
- finalize_background_migration 'ResetDuplicateCiRunnersTokenEncryptedValues'
-
add_concurrent_index :ci_runners,
- :token_encrypted,
- name: INDEX_NAME,
- unique: true
+ :token_encrypted,
+ name: INDEX_NAME,
+ unique: true
end
def down
diff --git a/db/post_migrate/20230510062503_queue_cleanup_personal_access_tokens_with_nil_expires_at.rb b/db/post_migrate/20230510062503_queue_cleanup_personal_access_tokens_with_nil_expires_at.rb
new file mode 100644
index 00000000000..a7dd1bda9db
--- /dev/null
+++ b/db/post_migrate/20230510062503_queue_cleanup_personal_access_tokens_with_nil_expires_at.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class QueueCleanupPersonalAccessTokensWithNilExpiresAt < Gitlab::Database::Migration[2.1]
+ MIGRATION = "CleanupPersonalAccessTokensWithNilExpiresAt"
+ DELAY_INTERVAL = 2.minutes
+ BATCH_SIZE = 50_000
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ def up
+ queue_batched_background_migration(
+ MIGRATION,
+ :personal_access_tokens,
+ :id,
+ job_interval: DELAY_INTERVAL,
+ batch_size: BATCH_SIZE
+ )
+ end
+
+ def down
+ delete_batched_background_migration(MIGRATION, :personal_access_tokens, :id, [])
+ end
+end
diff --git a/db/schema_migrations/20221021082255 b/db/schema_migrations/20221021082255
deleted file mode 100644
index afb266271d4..00000000000
--- a/db/schema_migrations/20221021082255
+++ /dev/null
@@ -1 +0,0 @@
-10caa548bccc134775ed14f85eae2b2063e83afe4a932982c353ecf1549a557d \ No newline at end of file
diff --git a/db/schema_migrations/20221021082256 b/db/schema_migrations/20221021082256
new file mode 100644
index 00000000000..0074889957f
--- /dev/null
+++ b/db/schema_migrations/20221021082256
@@ -0,0 +1 @@
+48c2eca5f5feea194eadc9d259f83e54fecbc7be1d219647d0b09ce4e5410eb4 \ No newline at end of file
diff --git a/db/schema_migrations/20221021082257 b/db/schema_migrations/20221021082257
new file mode 100644
index 00000000000..43ca84c0427
--- /dev/null
+++ b/db/schema_migrations/20221021082257
@@ -0,0 +1 @@
+1f3bf844501eee018b9594b447e55fac6c4628a22a9070cd95f37398067b03d6 \ No newline at end of file
diff --git a/db/schema_migrations/20221021082312 b/db/schema_migrations/20221021082312
deleted file mode 100644
index 26007002f54..00000000000
--- a/db/schema_migrations/20221021082312
+++ /dev/null
@@ -1 +0,0 @@
-86d979a179c504508fd2e9c1a62e935884297054b13b78a4c1460679d75f5b96 \ No newline at end of file
diff --git a/db/schema_migrations/20221021082313 b/db/schema_migrations/20221021082313
new file mode 100644
index 00000000000..5c70993f0b4
--- /dev/null
+++ b/db/schema_migrations/20221021082313
@@ -0,0 +1 @@
+9383e4f5ec51cf2971c98b4575546099c551b2a9f328f081c57866dc91838896 \ No newline at end of file
diff --git a/db/schema_migrations/20221021082314 b/db/schema_migrations/20221021082314
new file mode 100644
index 00000000000..c35ab7664ef
--- /dev/null
+++ b/db/schema_migrations/20221021082314
@@ -0,0 +1 @@
+2711e477b81213c7221001a9c75dde169a5b8f2cc2a05534dcdae16ace9231a9 \ No newline at end of file
diff --git a/db/schema_migrations/20230510062503 b/db/schema_migrations/20230510062503
new file mode 100644
index 00000000000..f6be2a73392
--- /dev/null
+++ b/db/schema_migrations/20230510062503
@@ -0,0 +1 @@
+2bd476bf0389b70aa5736ff69023993d37d54c4d333e3a91de9e57370935d6ec \ No newline at end of file
diff --git a/lib/gitlab/background_migration/cleanup_personal_access_tokens_with_nil_expires_at.rb b/lib/gitlab/background_migration/cleanup_personal_access_tokens_with_nil_expires_at.rb
new file mode 100644
index 00000000000..e8ee2a4c251
--- /dev/null
+++ b/lib/gitlab/background_migration/cleanup_personal_access_tokens_with_nil_expires_at.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Clean up personal access tokens with expires_at value is nil
+ # and set the value to new default 365 days
+ class CleanupPersonalAccessTokensWithNilExpiresAt < BatchedMigrationJob
+ feature_category :system_access
+
+ EXPIRES_AT_DEFAULT = 365.days.from_now
+
+ scope_to ->(relation) { relation.where(expires_at: nil) }
+ operation_name :update_all
+
+ def perform
+ each_sub_batch do |sub_batch|
+ sub_batch.update_all(expires_at: EXPIRES_AT_DEFAULT)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/ansi2json.rb b/lib/gitlab/ci/ansi2json.rb
index 70b68c7b821..79114d35916 100644
--- a/lib/gitlab/ci/ansi2json.rb
+++ b/lib/gitlab/ci/ansi2json.rb
@@ -4,8 +4,8 @@
module Gitlab
module Ci
module Ansi2json
- def self.convert(ansi, state = nil, verify_state: false)
- Converter.new.convert(ansi, state, verify_state: verify_state)
+ def self.convert(ansi, state = nil)
+ Converter.new.convert(ansi, state)
end
end
end
diff --git a/lib/gitlab/ci/ansi2json/converter.rb b/lib/gitlab/ci/ansi2json/converter.rb
index 84541208a2f..78f6c5bf0aa 100644
--- a/lib/gitlab/ci/ansi2json/converter.rb
+++ b/lib/gitlab/ci/ansi2json/converter.rb
@@ -4,13 +4,9 @@ module Gitlab
module Ci
module Ansi2json
class Converter
- def convert(stream, new_state, verify_state: false)
+ def convert(stream, new_state)
@lines = []
- @state = if verify_state
- SignedState.new(new_state, stream.size)
- else
- State.new(new_state, stream.size)
- end
+ @state = State.new(new_state, stream.size)
append = false
truncated = false
diff --git a/lib/gitlab/ci/ansi2json/signed_state.rb b/lib/gitlab/ci/ansi2json/signed_state.rb
deleted file mode 100644
index 98e2419f0a8..00000000000
--- a/lib/gitlab/ci/ansi2json/signed_state.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-# frozen_string_literal: true
-
-require 'openssl'
-
-module Gitlab
- module Ci
- module Ansi2json
- class SignedState < ::Gitlab::Ci::Ansi2json::State
- include Gitlab::Utils::StrongMemoize
-
- SIGNATURE_KEY_SALT = 'gitlab-ci-ansi2json-state'
- SEPARATOR = '--'
-
- def encode
- encoded = super
-
- encoded + SEPARATOR + sign(encoded)
- end
-
- private
-
- def sign(message)
- ::OpenSSL::HMAC.hexdigest(
- signature_digest,
- signature_key,
- message
- )
- end
-
- def verify(signed_message)
- signature_length = signature_digest.digest_length * 2 # a byte is exactly two hexadecimals
- message_length = signed_message.length - SEPARATOR.length - signature_length
- return if message_length <= 0
-
- signature = signed_message.last(signature_length)
- message = signed_message.first(message_length)
- return unless valid_signature?(message, signature)
-
- message
- end
-
- def valid_signature?(message, signature)
- expected_signature = sign(message)
- expected_signature.bytesize == signature.bytesize &&
- ::OpenSSL.fixed_length_secure_compare(signature, expected_signature)
- end
-
- def decode_state(data)
- return if data.blank?
-
- encoded_state = verify(data)
- if encoded_state.blank?
- ::Gitlab::AppLogger.warn(message: "#{self.class}: signature missing or invalid", invalid_state: data)
- return
- end
-
- decoded_state = Base64.urlsafe_decode64(encoded_state)
- return unless decoded_state.present?
-
- ::Gitlab::Json.parse(decoded_state)
- end
-
- def signature_digest
- ::OpenSSL::Digest.new('SHA256')
- end
-
- def signature_key
- ::Gitlab::Application.key_generator.generate_key(SIGNATURE_KEY_SALT, signature_digest.block_length)
- end
- strong_memoize_attr :signature_key
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/ansi2json/state.rb b/lib/gitlab/ci/ansi2json/state.rb
index 279e1929b22..3aec1cde1bc 100644
--- a/lib/gitlab/ci/ansi2json/state.rb
+++ b/lib/gitlab/ci/ansi2json/state.rb
@@ -1,11 +1,18 @@
# frozen_string_literal: true
+require 'openssl'
+
# In this class we keep track of the state changes that the
# Converter makes as it scans through the log stream.
module Gitlab
module Ci
module Ansi2json
class State
+ include Gitlab::Utils::StrongMemoize
+
+ SIGNATURE_KEY_SALT = 'gitlab-ci-ansi2json-state'
+ SEPARATOR = '--'
+
attr_accessor :offset, :current_line, :inherited_style, :open_sections, :last_line_offset
def initialize(new_state, stream_size)
@@ -24,7 +31,9 @@ module Gitlab
open_sections: @open_sections
}.to_json
- Base64.urlsafe_encode64(json, padding: false)
+ encoded = Base64.urlsafe_encode64(json, padding: false)
+
+ encoded + SEPARATOR + sign(encoded)
end
def open_section(section, timestamp, options)
@@ -86,27 +95,55 @@ module Gitlab
end
end
- def decode_state(state)
- return unless state.present?
+ def decode_state(data)
+ return if data.blank?
+
+ encoded_state = verify(data)
+ if encoded_state.blank?
+ ::Gitlab::AppLogger.warn(message: "#{self.class}: signature missing or invalid", invalid_state: data)
+ return
+ end
- decoded_state = Base64.urlsafe_decode64(state)
+ decoded_state = Base64.urlsafe_decode64(encoded_state)
return unless decoded_state.present?
::Gitlab::Json.parse(decoded_state)
- rescue ArgumentError, JSON::ParserError => error
- # This rescue is so that we don't break during the rollout or rollback
- # of `sign_and_verify_ansi2json_state`, because we may receive a
- # signed state even when the flag is disabled, and this would result
- # in invalid Base64 (ArgumentError) or invalid JSON in case the signed
- # state happens to decode as valid Base64 (JSON::ParserError).
- #
- # Once the flag has been fully rolled out this should not
- # be possible (it would imply a backend bug) and we not rescue from
- # this.
- ::Gitlab::AppLogger.warn(message: "#{self.class}: decode error", invalid_state: state, error: error)
-
- nil
end
+
+ def sign(message)
+ ::OpenSSL::HMAC.hexdigest(
+ signature_digest,
+ signature_key,
+ message
+ )
+ end
+
+ def verify(signed_message)
+ signature_length = signature_digest.digest_length * 2 # a byte is exactly two hexadecimals
+ message_length = signed_message.length - SEPARATOR.length - signature_length
+ return if message_length <= 0
+
+ signature = signed_message.last(signature_length)
+ message = signed_message.first(message_length)
+ return unless valid_signature?(message, signature)
+
+ message
+ end
+
+ def valid_signature?(message, signature)
+ expected_signature = sign(message)
+ expected_signature.bytesize == signature.bytesize &&
+ ::OpenSSL.fixed_length_secure_compare(signature, expected_signature)
+ end
+
+ def signature_digest
+ ::OpenSSL::Digest.new('SHA256')
+ end
+
+ def signature_key
+ ::Gitlab::Application.key_generator.generate_key(SIGNATURE_KEY_SALT, signature_digest.block_length)
+ end
+ strong_memoize_attr :signature_key
end
end
end
diff --git a/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb b/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb
index bb87104630c..3ed9c1743ed 100644
--- a/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb
+++ b/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb
@@ -147,7 +147,10 @@ module Gitlab
end
local cookie = cmsgpack.unpack(cookie_msgpack)
cookie.deduplicated = "1"
- redis.call("set", KEYS[1], cmsgpack.pack(cookie), "ex", redis.call("ttl", KEYS[1]))
+ local ttl = redis.call("ttl", KEYS[1])
+ if ttl > 0 then
+ redis.call("set", KEYS[1], cmsgpack.pack(cookie), "ex", ttl)
+ end
LUA
def should_reschedule?
diff --git a/spec/frontend/error_tracking/components/error_details_info_spec.js b/spec/frontend/error_tracking/components/error_details_info_spec.js
index 84b926e49fa..4a741a4c31e 100644
--- a/spec/frontend/error_tracking/components/error_details_info_spec.js
+++ b/spec/frontend/error_tracking/components/error_details_info_spec.js
@@ -3,7 +3,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import ErrorDetailsInfo from '~/error_tracking/components/error_details_info.vue';
-import { trackClickErrorLinkToSentryOptions } from '~/error_tracking/utils';
+import { trackClickErrorLinkToSentryOptions } from '~/error_tracking/events_tracking';
import Tracking from '~/tracking';
jest.mock('~/tracking');
diff --git a/spec/frontend/error_tracking/components/error_details_spec.js b/spec/frontend/error_tracking/components/error_details_spec.js
index 4e73db13a17..8700301ef73 100644
--- a/spec/frontend/error_tracking/components/error_details_spec.js
+++ b/spec/frontend/error_tracking/components/error_details_spec.js
@@ -17,7 +17,8 @@ import ErrorDetailsInfo from '~/error_tracking/components/error_details_info.vue
import {
trackErrorDetailsViewsOptions,
trackErrorStatusUpdateOptions,
-} from '~/error_tracking/utils';
+ trackCreateIssueFromError,
+} from '~/error_tracking/events_tracking';
import { createAlert, VARIANT_WARNING } from '~/alert';
import { __ } from '~/locale';
import Tracking from '~/tracking';
@@ -492,17 +493,21 @@ describe('ErrorDetails', () => {
});
it('should track IGNORE status update', async () => {
- Tracking.event.mockClear();
await findUpdateIgnoreStatusButton().trigger('click');
const { category, action } = trackErrorStatusUpdateOptions('ignored');
expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
it('should track RESOLVE status update', async () => {
- Tracking.event.mockClear();
await findUpdateResolveStatusButton().trigger('click');
const { category, action } = trackErrorStatusUpdateOptions('resolved');
expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
+
+ it('should track create issue button click', async () => {
+ await wrapper.find('[data-qa-selector="create_issue_button"]').vm.$emit('click');
+ const { category, action } = trackCreateIssueFromError;
+ expect(Tracking.event).toHaveBeenCalledWith(category, action);
+ });
});
});
diff --git a/spec/frontend/error_tracking/components/error_tracking_list_spec.js b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
index c9a2551d11c..6d4e92cf91f 100644
--- a/spec/frontend/error_tracking/components/error_tracking_list_spec.js
+++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
@@ -5,7 +5,12 @@ import Vuex from 'vuex';
import stubChildren from 'helpers/stub_children';
import ErrorTrackingActions from '~/error_tracking/components/error_tracking_actions.vue';
import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue';
-import { trackErrorListViewsOptions, trackErrorStatusUpdateOptions } from '~/error_tracking/utils';
+import {
+ trackErrorListViewsOptions,
+ trackErrorStatusUpdateOptions,
+ trackErrorStatusFilterOptions,
+ trackErrorSortedByField,
+} from '~/error_tracking/events_tracking';
import Tracking from '~/tracking';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import errorsList from './list_mock.json';
@@ -524,6 +529,8 @@ describe('ErrorTrackingList', () => {
stubs: {
GlTable: false,
GlLink: false,
+ GlDropdown: false,
+ GlDropdownItem: false,
},
});
});
@@ -534,7 +541,6 @@ describe('ErrorTrackingList', () => {
});
it('should track status updates', async () => {
- Tracking.event.mockClear();
const status = 'ignored';
findErrorActions().vm.$emit('update-issue-status', {
errorId: 1,
@@ -546,5 +552,19 @@ describe('ErrorTrackingList', () => {
const { category, action } = trackErrorStatusUpdateOptions(status);
expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
+
+ it('should track error filter', () => {
+ const findStatusFilter = () => findStatusFilterDropdown().find('.dropdown-item');
+ findStatusFilter().trigger('click');
+ const { category, action } = trackErrorStatusFilterOptions('unresolved');
+ expect(Tracking.event).toHaveBeenCalledWith(category, action);
+ });
+
+ it('should track error sorting', () => {
+ const findSortItem = () => findSortDropdown().find('.dropdown-item');
+ findSortItem().trigger('click');
+ const { category, action } = trackErrorSortedByField('last_seen');
+ expect(Tracking.event).toHaveBeenCalledWith(category, action);
+ });
});
});
diff --git a/spec/frontend/error_tracking/utils_spec.js b/spec/frontend/error_tracking/events_tracking_spec.js
index a0d6f7f009d..10479d863cf 100644
--- a/spec/frontend/error_tracking/utils_spec.js
+++ b/spec/frontend/error_tracking/events_tracking_spec.js
@@ -1,4 +1,4 @@
-import * as errorTrackingUtils from '~/error_tracking/utils';
+import * as errorTrackingUtils from '~/error_tracking/events_tracking';
const externalUrl = 'https://sentry.io/organizations/test-sentry-nk/issues/1/?project=1';
diff --git a/spec/frontend/import/details/mock_data.js b/spec/frontend/import/details/mock_data.js
index a8e0e53ed2b..67148173404 100644
--- a/spec/frontend/import/details/mock_data.js
+++ b/spec/frontend/import/details/mock_data.js
@@ -2,7 +2,7 @@ export const mockImportFailures = [
{
type: 'pull_request',
title: 'Add one cool feature',
- url: 'https://github.com/USER/REPO/pull/2',
+ provider_url: 'https://github.com/USER/REPO/pull/2',
details: {
exception_class: 'ActiveRecord::RecordInvalid',
exception_message: 'Record invalid',
@@ -17,7 +17,7 @@ export const mockImportFailures = [
{
type: 'pull_request',
title: 'Add another awesome feature',
- url: 'https://github.com/USER/REPO/pull/3',
+ provider_url: 'https://github.com/USER/REPO/pull/3',
details: {
exception_class: 'ActiveRecord::RecordInvalid',
exception_message: 'Record invalid',
@@ -32,7 +32,7 @@ export const mockImportFailures = [
{
type: 'lfs_object',
title: '3a9257fae9e86faee27d7208cb55e086f18e6f29f48c430bfbc26d42eb',
- url: null,
+ provider_url: null,
details: {
exception_class: 'NameError',
exception_message: 'some message',
diff --git a/spec/lib/gitlab/background_migration/cleanup_personal_access_tokens_with_nil_expires_at_spec.rb b/spec/lib/gitlab/background_migration/cleanup_personal_access_tokens_with_nil_expires_at_spec.rb
new file mode 100644
index 00000000000..ade16c0a780
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/cleanup_personal_access_tokens_with_nil_expires_at_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::CleanupPersonalAccessTokensWithNilExpiresAt, schema: 20230510062503, feature_category: :system_access do # rubocop:disable Layout/LineLength
+ let(:personal_access_tokens_table) { table(:personal_access_tokens) }
+ let(:users_table) { table(:users) }
+ let(:expires_at_default) { described_class::EXPIRES_AT_DEFAULT }
+
+ subject(:perform_migration) do
+ described_class.new(
+ start_id: 1,
+ end_id: 30,
+ batch_table: :personal_access_tokens,
+ batch_column: :id,
+ sub_batch_size: 3,
+ pause_ms: 0,
+ connection: ActiveRecord::Base.connection
+ ).perform
+ end
+
+ before do
+ user = users_table.create!(name: 'PAT_USER', email: 'pat_user@gmail.com', username: "pat_user1", projects_limit: 0)
+ personal_access_tokens_table.create!(user_id: user.id, name: "PAT#1", expires_at: expires_at_default + 1.day)
+ personal_access_tokens_table.create!(user_id: user.id, name: "PAT#2", expires_at: nil)
+ personal_access_tokens_table.create!(user_id: user.id, name: "PAT#3", expires_at: Time.zone.now + 2.days)
+ end
+
+ it 'adds expiry to personal access tokens', :aggregate_failures do
+ freeze_time do
+ expect(ActiveRecord::QueryRecorder.new { perform_migration }.count).to eq(3)
+
+ expect(personal_access_tokens_table.find_by_name("PAT#1").expires_at).to eq(expires_at_default.to_date + 1.day)
+ expect(personal_access_tokens_table.find_by_name("PAT#2").expires_at).to eq(expires_at_default.to_date)
+ expect(personal_access_tokens_table.find_by_name("PAT#3").expires_at).to eq(Time.zone.now.to_date + 2.days)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/ansi2json/signed_state_spec.rb b/spec/lib/gitlab/ci/ansi2json/signed_state_spec.rb
deleted file mode 100644
index 671efdf5256..00000000000
--- a/spec/lib/gitlab/ci/ansi2json/signed_state_spec.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Ci::Ansi2json::SignedState, feature_category: :continuous_integration do
- def build_state(state_class)
- state_class.new('', 1000).tap do |state|
- state.offset = 1
- state.new_line!(style: { fg: 'some-fg', bg: 'some-bg', mask: 1234 })
- state.set_last_line_offset
- state.open_section('hello', 111, {})
- end
- end
-
- let(:state) { build_state(described_class) }
-
- describe '#initialize' do
- it 'restores valid prior state', :aggregate_failures do
- new_state = described_class.new(state.encode, 1000)
-
- expect(new_state.offset).to eq(1)
- expect(new_state.inherited_style).to eq({
- bg: 'some-bg',
- fg: 'some-fg',
- mask: 1234
- })
- expect(new_state.open_sections).to eq({ 'hello' => 111 })
- end
-
- it 'ignores unsigned prior state', :aggregate_failures do
- unsigned = build_state(Gitlab::Ci::Ansi2json::State).encode
- expect(::Gitlab::AppLogger).to(
- receive(:warn).with(
- message: a_string_matching(/signature missing or invalid/),
- invalid_state: unsigned
- )
- )
-
- new_state = described_class.new(unsigned, 0)
-
- expect(new_state.offset).to eq(0)
- expect(new_state.inherited_style).to eq({})
- expect(new_state.open_sections).to eq({})
- end
-
- it 'ignores bad input', :aggregate_failures do
- expect(::Gitlab::AppLogger).to(
- receive(:warn).with(
- message: a_string_matching(/signature missing or invalid/),
- invalid_state: 'abcd'
- )
- )
-
- new_state = described_class.new('abcd', 0)
-
- expect(new_state.offset).to eq(0)
- expect(new_state.inherited_style).to eq({})
- expect(new_state.open_sections).to eq({})
- end
- end
-
- describe '#encode' do
- it 'deterministically signs the state' do
- expect(state.encode).to eq state.encode
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/ansi2json/state_spec.rb b/spec/lib/gitlab/ci/ansi2json/state_spec.rb
index 9b14231f1be..8dd4092f3d8 100644
--- a/spec/lib/gitlab/ci/ansi2json/state_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json/state_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Ansi2json::State, feature_category: :continuous_integration do
- def build_state(state_class)
- state_class.new('', 1000).tap do |state|
+ def build_state
+ described_class.new('', 1000).tap do |state|
state.offset = 1
state.new_line!(style: { fg: 'some-fg', bg: 'some-bg', mask: 1234 })
state.set_last_line_offset
@@ -12,7 +12,7 @@ RSpec.describe Gitlab::Ci::Ansi2json::State, feature_category: :continuous_integ
end
end
- let(:state) { build_state(described_class) }
+ let(:state) { build_state }
describe '#initialize' do
it 'restores valid prior state', :aggregate_failures do
@@ -27,57 +27,42 @@ RSpec.describe Gitlab::Ci::Ansi2json::State, feature_category: :continuous_integ
expect(new_state.open_sections).to eq({ 'hello' => 111 })
end
- it 'ignores signed state' do
- signed_state = Gitlab::Ci::Ansi2json::SignedState.new('', 1000)
- signed_state.offset = 1
- signed_state.new_line!(style: { fg: 'some-fg', bg: 'some-bg', mask: 1234 })
- signed_state.set_last_line_offset
- signed_state.open_section('hello', 111, {})
+ it 'ignores unsigned prior state', :aggregate_failures do
+ unsigned, _ = build_state.encode.split('--')
- encoded = signed_state.encode
expect(::Gitlab::AppLogger).to(
receive(:warn).with(
- message: a_string_matching(/decode error/),
- invalid_state: encoded,
- error: an_instance_of(JSON::ParserError)
+ message: a_string_matching(/signature missing or invalid/),
+ invalid_state: unsigned
)
)
- new_state = described_class.new(encoded, 1000)
+
+ new_state = described_class.new(unsigned, 0)
+
expect(new_state.offset).to eq(0)
expect(new_state.inherited_style).to eq({})
expect(new_state.open_sections).to eq({})
end
- it 'ignores invalid Base64 and logs a warning', :aggregate_failures do
+ it 'ignores bad input', :aggregate_failures do
expect(::Gitlab::AppLogger).to(
receive(:warn).with(
- message: a_string_matching(/decode error/),
- invalid_state: '.',
- error: an_instance_of(ArgumentError)
+ message: a_string_matching(/signature missing or invalid/),
+ invalid_state: 'abcd'
)
)
- new_state = described_class.new('.', 0)
+ new_state = described_class.new('abcd', 0)
expect(new_state.offset).to eq(0)
expect(new_state.inherited_style).to eq({})
expect(new_state.open_sections).to eq({})
end
+ end
- it 'ignores invalid JSON and logs a warning', :aggregate_failures do
- encoded = Base64.urlsafe_encode64('.')
- expect(::Gitlab::AppLogger).to(
- receive(:warn).with(
- message: a_string_matching(/decode error/),
- invalid_state: encoded,
- error: an_instance_of(JSON::ParserError)
- )
- )
-
- new_state = described_class.new(encoded, 0)
- expect(new_state.offset).to eq(0)
- expect(new_state.inherited_style).to eq({})
- expect(new_state.open_sections).to eq({})
+ describe '#encode' do
+ it 'deterministically signs the state' do
+ expect(state.encode).to eq state.encode
end
end
end
diff --git a/spec/lib/gitlab/ci/ansi2json_spec.rb b/spec/lib/gitlab/ci/ansi2json_spec.rb
index 12eeb8f6cac..98fca40e8ea 100644
--- a/spec/lib/gitlab/ci/ansi2json_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json_spec.rb
@@ -6,22 +6,6 @@ RSpec.describe Gitlab::Ci::Ansi2json, feature_category: :continuous_integration
subject { described_class }
describe 'lines' do
- describe 'verify_state' do
- it 'uses SignedState when true' do
- expect(Gitlab::Ci::Ansi2json::State).not_to receive(:new)
- expect(Gitlab::Ci::Ansi2json::SignedState).to receive(:new).and_call_original
-
- described_class.convert(StringIO.new('data'), verify_state: true)
- end
-
- it 'uses State when false' do
- expect(Gitlab::Ci::Ansi2json::State).to receive(:new).and_call_original
- expect(Gitlab::Ci::Ansi2json::SignedState).not_to receive(:new)
-
- described_class.convert(StringIO.new('data'), verify_state: false)
- end
- end
-
it 'prints non-ansi as-is' do
expect(convert_json('Hello')).to eq([{ offset: 0, content: [{ text: 'Hello' }] }])
end
diff --git a/spec/migrations/20230510062502_queue_cleanup_personal_access_tokens_with_nil_expires_at_spec.rb b/spec/migrations/20230510062502_queue_cleanup_personal_access_tokens_with_nil_expires_at_spec.rb
new file mode 100644
index 00000000000..45ef85a49cf
--- /dev/null
+++ b/spec/migrations/20230510062502_queue_cleanup_personal_access_tokens_with_nil_expires_at_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe QueueCleanupPersonalAccessTokensWithNilExpiresAt, feature_category: :system_access do
+ let!(:batched_migration) { described_class::MIGRATION }
+
+ it 'schedules a new batched migration' do
+ reversible_migration do |migration|
+ migration.before -> {
+ expect(batched_migration).not_to have_scheduled_batched_migration
+ }
+
+ migration.after -> {
+ expect(batched_migration).to have_scheduled_batched_migration(
+ table_name: :personal_access_tokens,
+ column_name: :id,
+ interval: described_class::DELAY_INTERVAL,
+ batch_size: described_class::BATCH_SIZE,
+ sub_batch_size: described_class::SUB_BATCH_SIZE
+ )
+ }
+ end
+ end
+end
diff --git a/spec/models/ci/build_trace_spec.rb b/spec/models/ci/build_trace_spec.rb
index 54b4f02fb91..30e4ef760d7 100644
--- a/spec/models/ci/build_trace_spec.rb
+++ b/spec/models/ci/build_trace_spec.rb
@@ -25,26 +25,6 @@ RSpec.describe Ci::BuildTrace, feature_category: :continuous_integration do
it { is_expected.to delegate_method(:complete?).to(:build).with_prefix }
end
- describe 'FF sign_and_verify_ansi2json_state' do
- before do
- stub_feature_flags(sign_and_verify_ansi2json_state: false)
- end
-
- it 'calls convert with verify_state: true when enabled for project' do
- build.project = create(:project)
- stub_feature_flags(sign_and_verify_ansi2json_state: build.project)
-
- expect(Gitlab::Ci::Ansi2json).to receive(:convert).with(stream.stream, state, verify_state: true)
-
- described_class.new(build: build, stream: stream, state: state)
- end
-
- it 'calls convert with verify_state: false when disabled' do
- expect(Gitlab::Ci::Ansi2json).to receive(:convert).with(stream.stream, state, verify_state: false)
- described_class.new(build: build, stream: stream, state: state)
- end
- end
-
it 'returns formatted trace' do
expect(subject.lines).to eq(
[