tefoji 3.2.0 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/mixins/user_functions.rb +21 -2
- data/lib/tefoji/declared_value.rb +30 -13
- data/lib/tefoji/jira_api.rb +7 -3
- data/lib/tefoji/version.rb +3 -0
- data/lib/tefoji.rb +98 -22
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 69675edd7a74569645d7b4124adf46b6b89fe61b6b249e8773ce3a90e37d4cbd
|
4
|
+
data.tar.gz: 9925aa98c156453976e2e54b55231bd22dfb2de0fe9867ffb385d3030a3684a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb28d7c19800cd8b61b8a30d44cb67f96c1dea95f31a40703d6fa74fe47f5865648dc433f6b757f853377fea76a3b7e053559753f6911f1ce7bdb553ea273cc3
|
7
|
+
data.tar.gz: 4ec1f5c99b091fd457482c9678c7d6b85fad114d3d212c126d1351214a0687bff5f94f19e40ebaefcc5a0298a5867fd67c70750e352c0d3556ee6966c771ebb1
|
@@ -14,6 +14,7 @@ module UserFunctions
|
|
14
14
|
%w[
|
15
15
|
chooser
|
16
16
|
conditional
|
17
|
+
counter
|
17
18
|
date_after
|
18
19
|
date_before
|
19
20
|
default
|
@@ -38,8 +39,8 @@ module UserFunctions
|
|
38
39
|
CLIENT_TOOLS: 'CT',
|
39
40
|
CODE_MANAGEMENT: 'CODEMGMT',
|
40
41
|
COMMUNITY_PACKAGE_REPO: 'CPR',
|
41
|
-
DOC: '
|
42
|
-
DOCS: '
|
42
|
+
DOC: 'PUPDOC',
|
43
|
+
DOCS: 'PUPDOC',
|
43
44
|
EZBAKE: 'EZ',
|
44
45
|
FACTER: 'FACT',
|
45
46
|
FORGE_INTERNAL: 'PF',
|
@@ -128,6 +129,24 @@ module UserFunctions
|
|
128
129
|
end
|
129
130
|
end
|
130
131
|
|
132
|
+
# Takes 2 optional arguments:
|
133
|
+
# initial_value (default 1), increment (default)
|
134
|
+
# Returns a Struct that starts with the initial value and post-increments every
|
135
|
+
# time it is used.
|
136
|
+
# The method name 'computed_value' is required for this form.
|
137
|
+
def counter(args)
|
138
|
+
initial_value = args[0] || 1
|
139
|
+
increment = args[1] || 1
|
140
|
+
|
141
|
+
Struct.new(:current_value, :increment) do
|
142
|
+
def computed_value
|
143
|
+
current = current_value.to_s
|
144
|
+
self.current_value += self.increment
|
145
|
+
current
|
146
|
+
end
|
147
|
+
end.new(initial_value, increment)
|
148
|
+
end
|
149
|
+
|
131
150
|
# Date `number_of_days` after `date_string`
|
132
151
|
def date_after(args)
|
133
152
|
_fail_if_unset(__method__.to_s, args)
|
@@ -1,43 +1,60 @@
|
|
1
1
|
module Tefoji
|
2
2
|
class DeclaredValue
|
3
|
-
|
3
|
+
def initialize(user_value = nil)
|
4
|
+
@value = if user_value.is_a? DeclaredValue
|
5
|
+
# prevent recursive DeclaredValues
|
6
|
+
user_value.value
|
7
|
+
else
|
8
|
+
user_value
|
9
|
+
end
|
10
|
+
end
|
4
11
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
12
|
+
# For most DeclaredValues that aren't Structs that have their one `value` method
|
13
|
+
def simple?
|
14
|
+
!@value.respond_to?(:computed_value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def value
|
18
|
+
if simple?
|
19
|
+
@value
|
20
|
+
else
|
21
|
+
@value.computed_value
|
22
|
+
end
|
9
23
|
end
|
10
24
|
|
11
25
|
def prepend(*prepended_things)
|
12
|
-
|
26
|
+
value.prepend(*prepended_things)
|
13
27
|
end
|
14
28
|
|
15
29
|
def to_i
|
16
|
-
|
30
|
+
value.to_i
|
17
31
|
end
|
18
32
|
|
19
33
|
def to_s
|
20
|
-
|
34
|
+
value.to_s
|
21
35
|
end
|
22
36
|
|
23
37
|
def to_a
|
24
|
-
|
25
|
-
|
38
|
+
current = value
|
39
|
+
if current.is_a? Array
|
40
|
+
current
|
26
41
|
else
|
27
|
-
[
|
42
|
+
[current]
|
28
43
|
end
|
29
44
|
end
|
30
45
|
|
31
46
|
def %(other)
|
32
|
-
|
47
|
+
value % other
|
33
48
|
end
|
34
49
|
|
50
|
+
# When querying @value, we don't want to invoke @value.computed_value because it may have
|
51
|
+
# unwanted side-effects.
|
35
52
|
def unset?
|
36
53
|
@value.nil?
|
37
54
|
end
|
38
55
|
|
39
56
|
def falsey?
|
40
|
-
@value.nil? || @value.empty? || @value.downcase == 'false' || @value == false
|
57
|
+
simple? && (@value.nil? || @value.empty? || @value.downcase == 'false' || @value == false)
|
41
58
|
end
|
42
59
|
|
43
60
|
def truthy?
|
data/lib/tefoji/jira_api.rb
CHANGED
@@ -4,8 +4,6 @@ require 'json'
|
|
4
4
|
require 'rest-client'
|
5
5
|
require 'uri'
|
6
6
|
|
7
|
-
RestClient.log = 'stdout'
|
8
|
-
|
9
7
|
module Tefoji
|
10
8
|
## An interface to send API request to Jira using the rest-client gem.
|
11
9
|
## Contained here are the bits of knowledge necessary to form API calls to Jira.
|
@@ -32,7 +30,7 @@ module Tefoji
|
|
32
30
|
def log_level=(level)
|
33
31
|
@log_level = level
|
34
32
|
# When debugging, turn on the RestClient @logger
|
35
|
-
RestClient.log =
|
33
|
+
RestClient.log = 'stderr' if @log_level == Logger::DEBUG
|
36
34
|
end
|
37
35
|
|
38
36
|
# Depending on how user specified their authorization we want to
|
@@ -92,6 +90,12 @@ module Tefoji
|
|
92
90
|
jira_get(search_parameters, fail_if_not_found)
|
93
91
|
end
|
94
92
|
|
93
|
+
# Get information about user in Jira
|
94
|
+
def get_components(project)
|
95
|
+
search_parameters = "project/#{project}/components"
|
96
|
+
jira_get(search_parameters)
|
97
|
+
end
|
98
|
+
|
95
99
|
# Save authentication YAML to the a file for reuse.
|
96
100
|
def save_authentication(save_file_name)
|
97
101
|
backup_file_name = "#{save_file_name}.bak"
|
data/lib/tefoji.rb
CHANGED
@@ -43,6 +43,7 @@ module Tefoji
|
|
43
43
|
@epic_security = nil
|
44
44
|
@deferral_data = {}
|
45
45
|
@transitions_table = {}
|
46
|
+
@valid_components = {}
|
46
47
|
|
47
48
|
# Logging
|
48
49
|
@log_level = Logger::INFO
|
@@ -78,6 +79,7 @@ module Tefoji
|
|
78
79
|
@logger.debug "Declarations: #{@declarations}"
|
79
80
|
|
80
81
|
check_assignees(main_template_data)
|
82
|
+
check_components(main_template_data)
|
81
83
|
|
82
84
|
# Process 'before' templates
|
83
85
|
main_template.dig(:includes, :before)&.each do |before|
|
@@ -506,7 +508,7 @@ module Tefoji
|
|
506
508
|
.gsub('%{short_name}', raw_issue_data['short_name'].value)
|
507
509
|
|
508
510
|
# Now do generic expansion of anything else
|
509
|
-
DeclaredValue.new(
|
511
|
+
DeclaredValue.new(expand_variables_in_string(formatted))
|
510
512
|
end
|
511
513
|
|
512
514
|
# These tags must not be in the issue creation because they need to be
|
@@ -629,25 +631,23 @@ module Tefoji
|
|
629
631
|
def user_function_call(function_definition)
|
630
632
|
@logger.debug "function_definition is: #{function_definition}"
|
631
633
|
function_name = function_definition['name']
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
if argument.is_a?(Array) || arguments.is_a?(Array)
|
636
|
-
arguments = argument || function_definition['arguments']
|
637
|
-
function_arguments = arguments.map do |a|
|
638
|
-
expand_right_value(a).value
|
639
|
-
end
|
640
|
-
elsif argument.is_a?(String) || arguments.is_a?(String)
|
641
|
-
argument = function_definition['argument'] || function_definition['arguments']
|
642
|
-
function_arguments = [expand_right_value(argument).value]
|
643
|
-
else
|
644
|
-
fatal("No arguments supplied to function \"#{function_name}\"")
|
645
|
-
end
|
634
|
+
# Allow singlular or plural form for any function call
|
635
|
+
user_arguments = function_definition['argument'] || function_definition['arguments']
|
646
636
|
|
647
637
|
unless user_function_allowlist.include?(function_name)
|
648
638
|
fatal "Unknown function call: \"#{function_name}\""
|
649
639
|
end
|
650
640
|
|
641
|
+
function_arguments = if user_arguments.is_a?(Array)
|
642
|
+
user_arguments.map do |a|
|
643
|
+
expand_right_value(a).value
|
644
|
+
end
|
645
|
+
elsif user_arguments.is_a?(String)
|
646
|
+
[expand_right_value(user_arguments).value]
|
647
|
+
else
|
648
|
+
[]
|
649
|
+
end
|
650
|
+
|
651
651
|
return_value = send(function_name, function_arguments)
|
652
652
|
@logger.debug "send(#{function_name}, #{function_arguments}) => #{return_value}"
|
653
653
|
DeclaredValue.new(return_value)
|
@@ -695,11 +695,11 @@ module Tefoji
|
|
695
695
|
# Some simple cases
|
696
696
|
return value if value.is_a?(DeclaredValue)
|
697
697
|
return DeclaredValue.new(value) if value.is_a?(Numeric)
|
698
|
-
return DeclaredValue.new(
|
698
|
+
return DeclaredValue.new(expand_variables_in_string(value)) if value.is_a?(String)
|
699
699
|
|
700
700
|
# Do a user-function call
|
701
701
|
if value.is_a?(Hash) && value.key?('function')
|
702
|
-
return
|
702
|
+
return user_function_call(value['function'])
|
703
703
|
end
|
704
704
|
|
705
705
|
# Return the original hash with the values variable-expanded
|
@@ -726,13 +726,39 @@ module Tefoji
|
|
726
726
|
raise "Unimplemented condition in expand_right_value: (#{value.class}) #{value}"
|
727
727
|
end
|
728
728
|
|
729
|
-
def
|
729
|
+
def expand_variables_in_string(expandable_string)
|
730
|
+
# If there are no symbols, there's nothing to expand
|
731
|
+
return expandable_string if @declarations.nil?
|
732
|
+
|
730
733
|
# Special-case where 'foo: "%{bar}"'; this allows for passing around hashes and
|
731
734
|
# arrays without interpolation into strings
|
732
|
-
single_value = %r
|
735
|
+
single_value = %r/\A%{([^}]+)}\Z/.match(expandable_string)
|
733
736
|
return @declarations[single_value[1].to_sym] if single_value
|
734
737
|
|
735
|
-
|
738
|
+
expand_variables(expandable_string)
|
739
|
+
end
|
740
|
+
|
741
|
+
# Generic form of #expand_variables_in_string that recursively replaces
|
742
|
+
# variable forms ("%{foo}") in a string with the text of the variable from
|
743
|
+
# @declarations
|
744
|
+
def expand_variables(expandable_string)
|
745
|
+
# Any pattern starting with '%{', followed by 1 or more characters, ending with '}'
|
746
|
+
variable_match_data = %r/%{([^}]+)}/.match(expandable_string)
|
747
|
+
|
748
|
+
# Return the string if there's nothing to expand
|
749
|
+
return expandable_string if variable_match_data.nil?
|
750
|
+
|
751
|
+
variable_expression = variable_match_data[0]
|
752
|
+
variable_symbol = variable_match_data[1].to_sym
|
753
|
+
variable_value = @declarations[variable_symbol].value
|
754
|
+
variable_stringified_value = variable_value.to_s
|
755
|
+
|
756
|
+
if variable_value.nil?
|
757
|
+
fatal "Unknown variable: \"#{variable_expression}\""
|
758
|
+
end
|
759
|
+
|
760
|
+
modified_string = expandable_string.sub(variable_expression, variable_stringified_value)
|
761
|
+
expand_variables(modified_string)
|
736
762
|
end
|
737
763
|
|
738
764
|
# Return a list of all the unset variables in a template
|
@@ -816,7 +842,6 @@ module Tefoji
|
|
816
842
|
# reasonable way to check assignability prior to the
|
817
843
|
# epic/issue being created, we should implement it in place
|
818
844
|
# of using the get_username method.
|
819
|
-
|
820
845
|
def check_assignees(main_template_data)
|
821
846
|
valid_users = []
|
822
847
|
invalid_users_to_epics = {}
|
@@ -897,7 +922,7 @@ module Tefoji
|
|
897
922
|
|
898
923
|
def get_username_from_assignee(assignee)
|
899
924
|
# Assignee is a variable or direct input
|
900
|
-
username =
|
925
|
+
username = expand_variables_in_string(assignee) if assignee.is_a?(String)
|
901
926
|
# Assignee is a function
|
902
927
|
if assignee.is_a?(Hash) && assignee.key?('function')
|
903
928
|
username = user_function_call(assignee['function'])
|
@@ -922,6 +947,57 @@ module Tefoji
|
|
922
947
|
end
|
923
948
|
end
|
924
949
|
|
950
|
+
# Takes a Tefoji template as input and verifies that all components used in the template exist
|
951
|
+
# in the associated project.
|
952
|
+
def check_components(main_template_data)
|
953
|
+
invalid_components = Set.new
|
954
|
+
main_template_data['epics']&.each do |epic|
|
955
|
+
next unless epic['components']
|
956
|
+
|
957
|
+
project = expand_right_value(epic['project']).value
|
958
|
+
update_valid_components(project)
|
959
|
+
epic['components'].each do |component|
|
960
|
+
invalid_components.add([project, component]) unless @valid_components[project].include?(component)
|
961
|
+
end
|
962
|
+
end
|
963
|
+
|
964
|
+
if main_template_data.dig('epic', 'components')
|
965
|
+
project = expand_right_value(main_template_data['epic']['project']).value
|
966
|
+
update_valid_components(project)
|
967
|
+
main_template_data['epic']['components'].each do |component|
|
968
|
+
invalid_components.add([project, component]) unless @valid_components[project].include?(component)
|
969
|
+
end
|
970
|
+
end
|
971
|
+
|
972
|
+
main_template_data['issues'].each do |issue|
|
973
|
+
next unless issue['components']
|
974
|
+
|
975
|
+
project = if issue['project']
|
976
|
+
expand_right_value(issue['project']).value
|
977
|
+
else
|
978
|
+
expand_right_value(main_template_data['issue_defaults']['project']).value
|
979
|
+
end
|
980
|
+
update_valid_components(project)
|
981
|
+
issue['components'].each do |component|
|
982
|
+
invalid_components.add([project, component]) unless @valid_components[project].include?(component)
|
983
|
+
end
|
984
|
+
end
|
985
|
+
|
986
|
+
fail_message = "component validation failed. the following components do not exist within the given projects: \n"
|
987
|
+
invalid_components.each do |component_project, component|
|
988
|
+
fail_message += "#{component_project}, #{component} \n"
|
989
|
+
end
|
990
|
+
fatal fail_message unless invalid_components.empty?
|
991
|
+
end
|
992
|
+
|
993
|
+
def update_valid_components(project)
|
994
|
+
return if @valid_components.key?(project)
|
995
|
+
|
996
|
+
components_response = @jira_api.get_components(project)
|
997
|
+
valid_components = components_response.map { |component_data| component_data['name'] }
|
998
|
+
@valid_components[project] = valid_components
|
999
|
+
end
|
1000
|
+
|
925
1001
|
def logger_initialize(log_location)
|
926
1002
|
logger = Logger.new(log_location, progname: 'tefoji', level: @log_level)
|
927
1003
|
logger.formatter = proc do |severity, _, script_name, message|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tefoji
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet By Perforce
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: debug
|
@@ -153,7 +153,7 @@ dependencies:
|
|
153
153
|
description: 'From a YAML specification, create a batch of Jira issues allowing for
|
154
154
|
variable substitutions.
|
155
155
|
|
156
|
-
'
|
156
|
+
'
|
157
157
|
email: release@puppet.com
|
158
158
|
executables:
|
159
159
|
- tefoji
|
@@ -167,6 +167,7 @@ files:
|
|
167
167
|
- lib/tefoji/cli.rb
|
168
168
|
- lib/tefoji/declared_value.rb
|
169
169
|
- lib/tefoji/jira_api.rb
|
170
|
+
- lib/tefoji/version.rb
|
170
171
|
homepage: https://github.com/puppetlabs/tefoji
|
171
172
|
licenses:
|
172
173
|
- Apache-2.0
|
@@ -189,7 +190,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
189
190
|
- !ruby/object:Gem::Version
|
190
191
|
version: '0'
|
191
192
|
requirements: []
|
192
|
-
rubygems_version: 3.
|
193
|
+
rubygems_version: 3.2.33
|
193
194
|
signing_key:
|
194
195
|
specification_version: 4
|
195
196
|
summary: Generate Jira issues from a YAML specification.
|