gitlab-cloud-connector 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -3
- data/CONTRIBUTING.md +50 -0
- data/LICENSE +28 -0
- data/Makefile +33 -0
- data/README.md +11 -221
- data/Rakefile +1 -5
- data/config/add_ons/duo_enterprise.yml +2 -0
- data/config/add_ons/duo_pro.yml +2 -0
- data/config/backend_services/ai_gateway.yml +5 -0
- data/config/backend_services/duo_workflow_service.yml +5 -0
- data/config/backend_services/observability.yml +5 -0
- data/config/backend_services/security_gateway.yml +5 -0
- data/config/license_types/premium.yml +2 -0
- data/config/license_types/ultimate.yml +2 -0
- data/config/schemas/add_on_schema.json +15 -0
- data/config/schemas/backend_service_schema.json +26 -0
- data/config/schemas/license_type_schema.json +15 -0
- data/config/schemas/unit_primitive_schema.json +97 -0
- data/config/unit_primitives/ask_build.yml +15 -0
- data/config/unit_primitives/ask_commit.yml +15 -0
- data/config/unit_primitives/ask_epic.yml +15 -0
- data/config/unit_primitives/ask_issue.yml +15 -0
- data/config/unit_primitives/code_suggestions.yml +19 -0
- data/config/unit_primitives/complete_code.yml +19 -0
- data/config/unit_primitives/documentation_search.yml +19 -0
- data/config/unit_primitives/duo_chat.yml +19 -0
- data/config/unit_primitives/duo_workflow_execute_workflow.yml +13 -0
- data/config/unit_primitives/explain_code.yml +17 -0
- data/config/unit_primitives/explain_vulnerability.yml +17 -0
- data/config/unit_primitives/fix_code.yml +17 -0
- data/config/unit_primitives/generate_code.yml +19 -0
- data/config/unit_primitives/generate_commit_message.yml +17 -0
- data/config/unit_primitives/generate_issue_description.yml +15 -0
- data/config/unit_primitives/glab_ask_git_command.yml +17 -0
- data/config/unit_primitives/include_dependency_context.yml +15 -0
- data/config/unit_primitives/include_file_context.yml +17 -0
- data/config/unit_primitives/include_issue_context.yml +15 -0
- data/config/unit_primitives/include_merge_request_context.yml +15 -0
- data/config/unit_primitives/include_snippet_context.yml +17 -0
- data/config/unit_primitives/observability_all.yml +14 -0
- data/config/unit_primitives/refactor_code.yml +17 -0
- data/config/unit_primitives/resolve_vulnerability.yml +17 -0
- data/config/unit_primitives/security_scans.yml +13 -0
- data/config/unit_primitives/summarize_comments.yml +17 -0
- data/config/unit_primitives/summarize_review.yml +15 -0
- data/config/unit_primitives/troubleshoot_job.yml +17 -0
- data/config/unit_primitives/write_tests.yml +17 -0
- data/lib/cloud_connector.rb +10 -0
- data/lib/gitlab/cloud_connector/json_web_token.rb +59 -0
- data/lib/gitlab/cloud_connector/{backend_service.rb → version.rb} +1 -3
- metadata +85 -48
- data/.idea/.gitignore +0 -8
- data/.idea/gitlab-cloud-connector.iml +0 -127
- data/.idea/inspectionProfiles/Project_Default.xml +0 -9
- data/.idea/misc.xml +0 -4
- data/.idea/modules.xml +0 -8
- data/.idea/vcs.xml +0 -7
- data/CHANGELOG.md +0 -5
- data/Dangerfile +0 -5
- data/config/cloud_connector/backend_services/ai_gateway/unit_primitives/code_suggestions.yml +0 -19
- data/config/cloud_connector/backend_services/ai_gateway/unit_primitives/documentation_search.yml +0 -19
- data/config/cloud_connector/backend_services/ai_gateway/unit_primitives/duo_chat.yml +0 -19
- data/lib/gitlab/cloud_connector/auth/json_web_token.rb +0 -82
- data/lib/gitlab/cloud_connector/base_group.rb +0 -39
- data/lib/gitlab/cloud_connector/configuration.rb +0 -24
- data/lib/gitlab/cloud_connector/deliverable.rb +0 -31
- data/lib/gitlab/cloud_connector/delivered_by.rb +0 -10
- data/lib/gitlab/cloud_connector/purchasable_decorator.rb +0 -59
- data/lib/gitlab/cloud_connector/token_issuer.rb +0 -44
- data/lib/gitlab/cloud_connector/unit_primitives.rb +0 -195
- data/lib/gitlab/cloud_connector.rb +0 -68
- data/lib/gitlab/popen.rb +0 -68
- data/lib/utils/parse_helper.rb +0 -15
- data/lib/utils/strong_memoize.rb +0 -145
- data/sig/gitlab/cloud_connector.rbs +0 -0
@@ -1,59 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../../utils/strong_memoize'
|
4
|
-
require_relative '../../utils/parse_helper'
|
5
|
-
require "active_support"
|
6
|
-
require "active_support/core_ext/module/delegation"
|
7
|
-
|
8
|
-
module Gitlab
|
9
|
-
module CloudConnector
|
10
|
-
class PurchasableDecorator
|
11
|
-
include ::Utils::StrongMemoize
|
12
|
-
include ::Utils::ParseHelper
|
13
|
-
|
14
|
-
delegate_missing_to :unit_primitive_or_group
|
15
|
-
|
16
|
-
def initialize(unit_primitive_or_group)
|
17
|
-
@unit_primitive_or_group = unit_primitive_or_group
|
18
|
-
end
|
19
|
-
|
20
|
-
def available_for?(_resource)
|
21
|
-
raise 'Not Implemented'
|
22
|
-
end
|
23
|
-
|
24
|
-
def free_access?
|
25
|
-
!cut_off_date&.past?
|
26
|
-
end
|
27
|
-
|
28
|
-
def available?(resource, gitlab_version, gitlab_realm)
|
29
|
-
return false unless gitlab_realms.include?(gitlab_realm)
|
30
|
-
|
31
|
-
supported_gitlab_version?(gitlab_version) && (free_access? || available_for?(resource))
|
32
|
-
end
|
33
|
-
|
34
|
-
def supported_gitlab_version?(gitlab_version)
|
35
|
-
min_gitlab_version = calculate_min_gitlab_version
|
36
|
-
|
37
|
-
return true unless min_gitlab_version
|
38
|
-
return false unless gitlab_version
|
39
|
-
# Extract only major and minor version (ignore -ee, -pre and patch suffixes)
|
40
|
-
gitlab_version = parse_version(gitlab_version)
|
41
|
-
|
42
|
-
gitlab_version >= min_gitlab_version
|
43
|
-
end
|
44
|
-
|
45
|
-
def calculate_min_gitlab_version
|
46
|
-
if free_access?
|
47
|
-
min_gitlab_version_for_free_access
|
48
|
-
else
|
49
|
-
min_gitlab_version
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
attr_reader :unit_primitive_or_group
|
56
|
-
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'auth/json_web_token'
|
4
|
-
|
5
|
-
module Gitlab
|
6
|
-
module CloudConnector
|
7
|
-
###
|
8
|
-
# Provides access information for GitLab services offered under Cloud Connector.
|
9
|
-
#
|
10
|
-
# Self-Managed GitLab instances sync with this service to get access to Cloud Connector
|
11
|
-
# features based on billing status and other information.
|
12
|
-
module TokenIssuer
|
13
|
-
|
14
|
-
attr_reader :subject, :extra_claims
|
15
|
-
|
16
|
-
def access_token(resource, gitlab_version: nil, gitlab_realm: nil, subject: nil, extra_claims: {})
|
17
|
-
subject ||= ::Gitlab::CloudConnector.token_subject
|
18
|
-
gitlab_version ||= ::Gitlab::CloudConnector.gitlab_version
|
19
|
-
gitlab_realm ||= ::Gitlab::CloudConnector.gitlab_realm
|
20
|
-
|
21
|
-
allowed_scopes = allowed_scopes(resource, gitlab_version, gitlab_realm)
|
22
|
-
|
23
|
-
return nil if allowed_scopes.empty?
|
24
|
-
|
25
|
-
::Gitlab::CloudConnector::Auth::JsonWebToken.new(
|
26
|
-
audience: allowed_backends,
|
27
|
-
subject: subject,
|
28
|
-
scopes: allowed_scopes,
|
29
|
-
extra_claims: extra_claims
|
30
|
-
)
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
def allowed_backends
|
36
|
-
unit_primitives.values.map{ |unit_primitive| "gitlab-#{unit_primitive.backend_service.dasherize}"}.uniq
|
37
|
-
end
|
38
|
-
|
39
|
-
def allowed_scopes(resource, gitlab_version, gitlab_realm)
|
40
|
-
unit_primitives.values.select{ |unit_primitive| unit_primitive.available?(resource, gitlab_version, gitlab_realm) }.map(&:name)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,195 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../../utils/parse_helper'
|
4
|
-
|
5
|
-
module Gitlab
|
6
|
-
module CloudConnector
|
7
|
-
class UnitPrimitives
|
8
|
-
include ::Utils::ParseHelper
|
9
|
-
|
10
|
-
attr_reader :path
|
11
|
-
attr_reader :attributes
|
12
|
-
|
13
|
-
VALID_UNIT_PRIMITIVE_NAME = %r{^#{/[a-z0-9]+/}(#{'_'} #{/[a-z0-9]+/})*$}x
|
14
|
-
BACKEND_SERVICE_MATCHER = %r{\/backend_services\/(\w+)\/unit_primitives\/(?:\w+)\.yml}
|
15
|
-
|
16
|
-
PARAMS = %i[
|
17
|
-
name
|
18
|
-
description
|
19
|
-
backend_service
|
20
|
-
cut_off_date
|
21
|
-
introduced_by_url
|
22
|
-
unit_primitive_issue_url
|
23
|
-
milestone
|
24
|
-
group
|
25
|
-
feature_category
|
26
|
-
maturity
|
27
|
-
min_gitlab_version
|
28
|
-
min_gitlab_version_for_free_access
|
29
|
-
min_gitlab_version
|
30
|
-
min_backend_version
|
31
|
-
delivered_by
|
32
|
-
bundled_with
|
33
|
-
gitlab_realms
|
34
|
-
].freeze
|
35
|
-
|
36
|
-
BACKEND_SERVICES = %i[
|
37
|
-
ai_gateway
|
38
|
-
]
|
39
|
-
|
40
|
-
InvalidUnitPrimitiveError = Class.new(Exception)
|
41
|
-
|
42
|
-
PARAMS.each do |param|
|
43
|
-
define_method(param) do
|
44
|
-
## parse time or create version
|
45
|
-
attributes[param]
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
BACKEND_SERVICES.each do |backend_service, _|
|
50
|
-
define_method("#{backend_service}?") do
|
51
|
-
attributes[:backend_service].to_sym == backend_service
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
alias :add_ons :bundled_with
|
56
|
-
|
57
|
-
def initialize(path, **opts)
|
58
|
-
@path = path
|
59
|
-
@attributes = {}
|
60
|
-
|
61
|
-
PARAMS.each do |param|
|
62
|
-
if param.end_with?('version')
|
63
|
-
@attributes[param] = parse_version(opts[param]) if opts[param]
|
64
|
-
elsif param.end_with?('date')
|
65
|
-
@attributes[param] = parse_time(opts[param]).utc if opts[param]
|
66
|
-
else
|
67
|
-
@attributes[param] = opts[param]
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
# read backend_service from the path
|
72
|
-
@attributes[:backend_service] = BACKEND_SERVICE_MATCHER.match(path.to_s)[1]
|
73
|
-
end
|
74
|
-
|
75
|
-
def key
|
76
|
-
name.to_sym
|
77
|
-
end
|
78
|
-
|
79
|
-
def validate!
|
80
|
-
unless name.present?
|
81
|
-
raise InvalidUnitPrimitiveError, "Unit primitive is missing name"
|
82
|
-
end
|
83
|
-
|
84
|
-
if backend_service.empty?
|
85
|
-
raise InvalidUnitPrimitiveError, "Unit primitive '#{name}' is missing `backend_service`. Ensure to update #{path}"
|
86
|
-
end
|
87
|
-
|
88
|
-
unless BACKEND_SERVICES.include?(backend_service.to_sym)
|
89
|
-
raise InvalidUnitPrimitiveError, "Unit primitive '#{name}' backend service '#{backend_service}' is invalid."
|
90
|
-
end
|
91
|
-
|
92
|
-
if delivered_by.empty?
|
93
|
-
raise InvalidUnitPrimitiveError, "Unit primitive '#{name}' is missing 'delivered_by'. If unit primitive is delivered as a stand-alone feature, set unit_primitive name."
|
94
|
-
end
|
95
|
-
|
96
|
-
if bundled_with.empty?
|
97
|
-
raise InvalidUnitPrimitiveError, "Unit primitive '#{name}' is missing 'bundled_with'."
|
98
|
-
end
|
99
|
-
|
100
|
-
if feature_category.empty?
|
101
|
-
raise InvalidUnitPrimitiveError, "Unit primitive '#{name}' is missing 'feature_category'."
|
102
|
-
end
|
103
|
-
|
104
|
-
unless VALID_UNIT_PRIMITIVE_NAME =~ name
|
105
|
-
raise InvalidUnitPrimitiveError, "Unit primitive '#{name}' is invalid"
|
106
|
-
end
|
107
|
-
|
108
|
-
if File.basename(path, ".yml") != name
|
109
|
-
raise InvalidUnitPrimitiveError, "Unit primitive '#{name}' has an invalid path: '#{path}'. Ensure to update #{path}"
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def to_h
|
114
|
-
attributes
|
115
|
-
end
|
116
|
-
|
117
|
-
class << self
|
118
|
-
def paths
|
119
|
-
@paths ||= [File.join(CloudConnector.root, 'config', 'cloud_connector', 'backend_services', '**', 'unit_primitives', '*.yml')]
|
120
|
-
end
|
121
|
-
|
122
|
-
def definitions
|
123
|
-
# We lazily load all definitions
|
124
|
-
@definitions ||= load_all!
|
125
|
-
end
|
126
|
-
|
127
|
-
def get(key)
|
128
|
-
definitions[key.to_sym]
|
129
|
-
end
|
130
|
-
|
131
|
-
def reload!
|
132
|
-
@definitions = load_all!
|
133
|
-
end
|
134
|
-
|
135
|
-
def has_definition?(key)
|
136
|
-
definitions.has_key?(key.to_sym)
|
137
|
-
end
|
138
|
-
|
139
|
-
def register_hot_reloader!
|
140
|
-
return unless Rails.env.development? || Rails.env.test?
|
141
|
-
# Reload feature flags on change of this file or any `.yml`
|
142
|
-
file_watcher = Rails.configuration.file_watcher.new(reload_files, reload_directories) do
|
143
|
-
reload!
|
144
|
-
end
|
145
|
-
|
146
|
-
Rails.application.reloaders << file_watcher
|
147
|
-
Rails.application.reloader.to_run { file_watcher.execute_if_updated }
|
148
|
-
|
149
|
-
file_watcher
|
150
|
-
end
|
151
|
-
|
152
|
-
def load_all!
|
153
|
-
paths.each_with_object({}) do |glob_path, definitions|
|
154
|
-
load_all_from_path!(definitions, glob_path)
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
def load_from_file(path)
|
159
|
-
definition = File.read(path)
|
160
|
-
definition = YAML.safe_load(definition, aliases: true)
|
161
|
-
definition.deep_symbolize_keys!
|
162
|
-
|
163
|
-
self.new(path, **definition).tap(&:validate!)
|
164
|
-
rescue StandardError => e
|
165
|
-
raise InvalidUnitPrimitiveError, "Invalid definition for `#{path}`: #{e.message}"
|
166
|
-
end
|
167
|
-
|
168
|
-
def load_all_from_path!(definitions, glob_path)
|
169
|
-
Dir.glob(glob_path).each do |path|
|
170
|
-
definition = load_from_file(path)
|
171
|
-
|
172
|
-
if previous = definitions[definition.key]
|
173
|
-
raise InvalidUnitPrimitiveError, "Feature flag '#{definition.key}' is already defined in '#{previous.path}'"
|
174
|
-
end
|
175
|
-
|
176
|
-
definitions[definition.key] = definition
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
def reload_files
|
181
|
-
[]
|
182
|
-
end
|
183
|
-
|
184
|
-
def reload_directories
|
185
|
-
paths.each_with_object({}) do |path, result|
|
186
|
-
path = File.dirname(path)
|
187
|
-
Dir.glob(path).each do |matching_dir|
|
188
|
-
result[matching_dir] = 'yml'
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
194
|
-
end
|
195
|
-
end
|
@@ -1,68 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'cloud_connector/configuration'
|
4
|
-
require_relative 'cloud_connector/token_issuer'
|
5
|
-
require_relative 'cloud_connector/deliverable'
|
6
|
-
require_relative 'cloud_connector/delivered_by'
|
7
|
-
require_relative 'cloud_connector/backend_service'
|
8
|
-
require_relative '../utils/strong_memoize'
|
9
|
-
|
10
|
-
module Gitlab
|
11
|
-
module CloudConnector
|
12
|
-
extend Deliverable
|
13
|
-
extend TokenIssuer
|
14
|
-
extend ::Utils::StrongMemoize
|
15
|
-
Time.zone = 'UTC'
|
16
|
-
|
17
|
-
VERSION = "0.1.0"
|
18
|
-
|
19
|
-
class << self
|
20
|
-
|
21
|
-
alias :instance_access_token :access_token
|
22
|
-
delegate :token_issuer, :token_expires_in, :token_subject, :gitlab_realm, :gitlab_version, to: :configuration
|
23
|
-
|
24
|
-
def root
|
25
|
-
File.expand_path '../../..', __FILE__
|
26
|
-
end
|
27
|
-
|
28
|
-
def configure
|
29
|
-
yield configuration
|
30
|
-
end
|
31
|
-
|
32
|
-
def configuration
|
33
|
-
strong_memoize(:configuration) do
|
34
|
-
Configuration.new
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def unit_primitives
|
39
|
-
strong_memoize(:unit_primitives) do
|
40
|
-
unit_primitives = Gitlab::CloudConnector::UnitPrimitives.definitions
|
41
|
-
unit_primitives.transform_values{ |up| configuration.purchasable_decorator_class.new(up) }
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def backend_services
|
46
|
-
strong_memoize(:backend_services) do
|
47
|
-
unit_primitives.values.group_by(&:backend_service).map do |name, unit_primitives|
|
48
|
-
BackendService.new(name.to_sym, unit_primitives.index_by(&:key))
|
49
|
-
end.index_by(&:name)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def feature_categories
|
54
|
-
strong_memoize(:feature_categories) do
|
55
|
-
unit_primitives.values.map(&:feature_category).uniq
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def bundled_with
|
60
|
-
strong_memoize(:bundled_with) do
|
61
|
-
unit_primitives.values.flat_map(&:bundled_with).uniq
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
alias :add_ons :bundled_with
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
data/lib/gitlab/popen.rb
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'fileutils'
|
4
|
-
require 'open3'
|
5
|
-
|
6
|
-
module Gitlab
|
7
|
-
module Popen
|
8
|
-
extend self
|
9
|
-
|
10
|
-
Result = Struct.new(:cmd, :stdout, :stderr, :status, :duration)
|
11
|
-
|
12
|
-
# Returns [stdout + stderr, status]
|
13
|
-
# status is either the exit code or the signal that killed the process
|
14
|
-
def popen(cmd, path = nil, vars = {}, &block)
|
15
|
-
result = popen_with_detail(cmd, path, vars, &block)
|
16
|
-
|
17
|
-
# Process#waitpid returns Process::Status, which holds a 16-bit value.
|
18
|
-
# The higher-order 8 bits hold the exit() code (`exitstatus`).
|
19
|
-
# The lower-order bits holds whether the process was terminated.
|
20
|
-
# If the process didn't exit normally, `exitstatus` will be `nil`,
|
21
|
-
# but we still want a non-zero code, even if the value is
|
22
|
-
# platform-dependent.
|
23
|
-
status = result.status&.exitstatus || result.status.to_i
|
24
|
-
|
25
|
-
["#{result.stdout}#{result.stderr}", status]
|
26
|
-
end
|
27
|
-
|
28
|
-
# Returns Result
|
29
|
-
def popen_with_detail(cmd, path = nil, vars = {})
|
30
|
-
unless cmd.is_a?(Array)
|
31
|
-
raise "System commands must be given as an array of strings"
|
32
|
-
end
|
33
|
-
|
34
|
-
if cmd.one? && cmd.first.match?(/\s/)
|
35
|
-
raise "System commands must be split into an array of space-separated values"
|
36
|
-
end
|
37
|
-
|
38
|
-
path ||= Dir.pwd
|
39
|
-
vars['PWD'] = path
|
40
|
-
options = { chdir: path }
|
41
|
-
|
42
|
-
unless File.directory?(path)
|
43
|
-
FileUtils.mkdir_p(path)
|
44
|
-
end
|
45
|
-
|
46
|
-
cmd_stdout = ''
|
47
|
-
cmd_stderr = ''
|
48
|
-
cmd_status = nil
|
49
|
-
start = Time.now
|
50
|
-
|
51
|
-
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
|
52
|
-
# stderr and stdout pipes can block if stderr/stdout aren't drained: https://bugs.ruby-lang.org/issues/9082
|
53
|
-
# Mimic what Ruby does with capture3: https://github.com/ruby/ruby/blob/1ec544695fa02d714180ef9c34e755027b6a2103/lib/open3.rb#L257-L273
|
54
|
-
out_reader = Thread.new { stdout.read }
|
55
|
-
err_reader = Thread.new { stderr.read }
|
56
|
-
|
57
|
-
yield(stdin) if block_given?
|
58
|
-
stdin.close
|
59
|
-
|
60
|
-
cmd_stdout = out_reader.value
|
61
|
-
cmd_stderr = err_reader.value
|
62
|
-
cmd_status = wait_thr.value
|
63
|
-
end
|
64
|
-
|
65
|
-
Result.new(cmd, cmd_stdout, cmd_stderr, cmd_status, Time.now - start)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
data/lib/utils/parse_helper.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Utils
|
4
|
-
module ParseHelper
|
5
|
-
VERSION_REGEX = /\A(\d+\.\d+)/
|
6
|
-
def parse_time(time)
|
7
|
-
Time.zone.parse(time).utc if time
|
8
|
-
end
|
9
|
-
|
10
|
-
def parse_version(version)
|
11
|
-
version = VERSION_REGEX.match(version.to_s).to_s
|
12
|
-
Gem::Version.new(version)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
data/lib/utils/strong_memoize.rb
DELETED
@@ -1,145 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Utils
|
4
|
-
module StrongMemoize
|
5
|
-
# Instead of writing patterns like this:
|
6
|
-
#
|
7
|
-
# def trigger_from_token
|
8
|
-
# return @trigger if defined?(@trigger)
|
9
|
-
#
|
10
|
-
# @trigger = Ci::Trigger.find_by_token(params[:token].to_s)
|
11
|
-
# end
|
12
|
-
#
|
13
|
-
# We could write it like:
|
14
|
-
#
|
15
|
-
# include Utils::StrongMemoize
|
16
|
-
#
|
17
|
-
# def trigger_from_token
|
18
|
-
# Ci::Trigger.find_by_token(params[:token].to_s)
|
19
|
-
# end
|
20
|
-
# strong_memoize_attr :trigger_from_token
|
21
|
-
#
|
22
|
-
# def enabled?
|
23
|
-
# Feature.enabled?(:some_feature)
|
24
|
-
# end
|
25
|
-
# strong_memoize_attr :enabled?
|
26
|
-
#
|
27
|
-
def strong_memoize(name)
|
28
|
-
key = ivar(name)
|
29
|
-
|
30
|
-
if instance_variable_defined?(key)
|
31
|
-
instance_variable_get(key)
|
32
|
-
else
|
33
|
-
instance_variable_set(key, yield)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
# Works the same way as "strong_memoize" but takes
|
38
|
-
# a second argument - expire_in. This allows invalidate
|
39
|
-
# the data after specified number of seconds
|
40
|
-
def strong_memoize_with_expiration(name, expire_in)
|
41
|
-
key = ivar(name)
|
42
|
-
expiration_key = "#{key}_expired_at"
|
43
|
-
|
44
|
-
if instance_variable_defined?(expiration_key)
|
45
|
-
expire_at = instance_variable_get(expiration_key)
|
46
|
-
clear_memoization(name) if Time.current > expire_at
|
47
|
-
end
|
48
|
-
|
49
|
-
if instance_variable_defined?(key)
|
50
|
-
instance_variable_get(key)
|
51
|
-
else
|
52
|
-
value = instance_variable_set(key, yield)
|
53
|
-
instance_variable_set(expiration_key, Time.current + expire_in)
|
54
|
-
value
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def strong_memoize_with(name, *args)
|
59
|
-
container = strong_memoize(name) { {} }
|
60
|
-
|
61
|
-
if container.key?(args)
|
62
|
-
container[args]
|
63
|
-
else
|
64
|
-
container[args] = yield
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def strong_memoized?(name)
|
69
|
-
key = ivar(StrongMemoize.normalize_key(name))
|
70
|
-
instance_variable_defined?(key)
|
71
|
-
end
|
72
|
-
|
73
|
-
def clear_memoization(name)
|
74
|
-
key = ivar(StrongMemoize.normalize_key(name))
|
75
|
-
remove_instance_variable(key) if instance_variable_defined?(key)
|
76
|
-
end
|
77
|
-
|
78
|
-
module StrongMemoizeClassMethods
|
79
|
-
def strong_memoize_attr(method_name)
|
80
|
-
member_name = StrongMemoize.normalize_key(method_name)
|
81
|
-
|
82
|
-
StrongMemoize.send(:do_strong_memoize, self, method_name, member_name) # rubocop:disable GitlabSecurity/PublicSend
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def self.included(base)
|
87
|
-
base.singleton_class.prepend(StrongMemoizeClassMethods)
|
88
|
-
end
|
89
|
-
|
90
|
-
private
|
91
|
-
|
92
|
-
# Convert `"name"`/`:name` into `:@name`
|
93
|
-
#
|
94
|
-
# Depending on a type ensure that there's a single memory allocation
|
95
|
-
def ivar(name)
|
96
|
-
case name
|
97
|
-
when Symbol
|
98
|
-
name.to_s.prepend("@").to_sym
|
99
|
-
when String
|
100
|
-
:"@#{name}"
|
101
|
-
else
|
102
|
-
raise ArgumentError, "Invalid type of '#{name}'"
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
class << self
|
107
|
-
def normalize_key(key)
|
108
|
-
return key unless key.end_with?('!', '?')
|
109
|
-
|
110
|
-
# Replace invalid chars like `!` and `?` with allowed Unicode codeparts.
|
111
|
-
key.to_s.tr('!?', "\uFF01\uFF1F")
|
112
|
-
end
|
113
|
-
|
114
|
-
private
|
115
|
-
|
116
|
-
def do_strong_memoize(klass, method_name, member_name)
|
117
|
-
method = klass.instance_method(method_name)
|
118
|
-
|
119
|
-
unless method.arity == 0
|
120
|
-
raise <<~ERROR
|
121
|
-
Using `strong_memoize_attr` on methods with parameters is not supported.
|
122
|
-
|
123
|
-
Use `strong_memoize_with` instead.
|
124
|
-
See https://docs.gitlab.com/ee/development/utilities.html#strongmemoize
|
125
|
-
ERROR
|
126
|
-
end
|
127
|
-
|
128
|
-
# Methods defined within a class method are already public by default, so we don't need to
|
129
|
-
# explicitly make them public.
|
130
|
-
scope = %i[private protected].find do |scope|
|
131
|
-
klass.send(:"#{scope}_instance_methods") # rubocop:disable GitlabSecurity/PublicSend
|
132
|
-
.include? method_name
|
133
|
-
end
|
134
|
-
|
135
|
-
klass.define_method(method_name) do |&block|
|
136
|
-
strong_memoize(member_name) do
|
137
|
-
method.bind_call(self, &block)
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
klass.send(scope, method_name) if scope # rubocop:disable GitlabSecurity/PublicSend
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
File without changes
|