tefoji 3.2.0 → 3.4.0
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/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.
|