tefoji 3.3.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7de8bcf06db002032d9477d09b5db5b033d2e8ccfdd4a23d4593ae3b1905f6ec
4
- data.tar.gz: 2fa6bcbd050208e764a4be84fb33b5dab452169f170ee532eccc0d39c136fefd
3
+ metadata.gz: 69675edd7a74569645d7b4124adf46b6b89fe61b6b249e8773ce3a90e37d4cbd
4
+ data.tar.gz: 9925aa98c156453976e2e54b55231bd22dfb2de0fe9867ffb385d3030a3684a4
5
5
  SHA512:
6
- metadata.gz: f3e12ada8d4ea8fcef49e6928dabe30c37a1d431e870a4d898444f58ceb8fba99356acc1243b89b8e3dbc355f307ff560b4d55ff01e963cf0dd747781a99f3df
7
- data.tar.gz: c51c23d9ceff772762c061f587a6cbb53d1d980d12b09bbc2e12b31361096516a03036a105230a7c528f20eba7a5e242052441b253fdd8cb2ff5fe88342ffc3b
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
@@ -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
- attr_accessor :value, :properties
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
- def initialize(value = nil)
6
- @value = value
7
- @value = value.value if value.is_a? DeclaredValue
8
- @properties = {}
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
- @value.prepend(*prepended_things)
26
+ value.prepend(*prepended_things)
13
27
  end
14
28
 
15
29
  def to_i
16
- @value.to_i
30
+ value.to_i
17
31
  end
18
32
 
19
33
  def to_s
20
- @value.to_s
34
+ value.to_s
21
35
  end
22
36
 
23
37
  def to_a
24
- if @value.is_a? Array
25
- @value
38
+ current = value
39
+ if current.is_a? Array
40
+ current
26
41
  else
27
- [@value]
42
+ [current]
28
43
  end
29
44
  end
30
45
 
31
46
  def %(other)
32
- @value % other
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?
@@ -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 = @logger if @log_level == Logger::DEBUG
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"
@@ -1,3 +1,3 @@
1
1
  module Tefoji
2
- VERSION = '3.3.0'
2
+ VERSION = '3.4.0'
3
3
  end
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(expand_string(formatted))
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
- argument = function_definition['argument']
633
- arguments = function_definition['arguments']
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(expand_string(value)) if value.is_a?(String)
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 DeclaredValue.new(user_function_call(value['function']))
702
+ return user_function_call(value['function'])
703
703
  end
704
704
 
705
705
  # Return the original hash with the values variable-expanded
@@ -726,21 +726,39 @@ module Tefoji
726
726
  raise "Unimplemented condition in expand_right_value: (#{value.class}) #{value}"
727
727
  end
728
728
 
729
- def expand_string(value)
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{\A%{(.+?)}\Z}.match(value)
735
+ single_value = %r/\A%{([^}]+)}\Z/.match(expandable_string)
733
736
  return @declarations[single_value[1].to_sym] if single_value
734
737
 
735
- return value if @declarations.nil?
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)
736
747
 
737
- # Substitute all variables in the string
738
- variable_substituted_value = value
739
- @declarations.each_key do |key|
740
- variable_substituted_value = variable_substituted_value.gsub("%{#{key}}",
741
- @declarations[key].value.to_s)
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}\""
742
758
  end
743
- return variable_substituted_value
759
+
760
+ modified_string = expandable_string.sub(variable_expression, variable_stringified_value)
761
+ expand_variables(modified_string)
744
762
  end
745
763
 
746
764
  # Return a list of all the unset variables in a template
@@ -824,7 +842,6 @@ module Tefoji
824
842
  # reasonable way to check assignability prior to the
825
843
  # epic/issue being created, we should implement it in place
826
844
  # of using the get_username method.
827
-
828
845
  def check_assignees(main_template_data)
829
846
  valid_users = []
830
847
  invalid_users_to_epics = {}
@@ -905,7 +922,7 @@ module Tefoji
905
922
 
906
923
  def get_username_from_assignee(assignee)
907
924
  # Assignee is a variable or direct input
908
- username = expand_string(assignee) if assignee.is_a?(String)
925
+ username = expand_variables_in_string(assignee) if assignee.is_a?(String)
909
926
  # Assignee is a function
910
927
  if assignee.is_a?(Hash) && assignee.key?('function')
911
928
  username = user_function_call(assignee['function'])
@@ -930,6 +947,57 @@ module Tefoji
930
947
  end
931
948
  end
932
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
+
933
1001
  def logger_initialize(log_location)
934
1002
  logger = Logger.new(log_location, progname: 'tefoji', level: @log_level)
935
1003
  logger.formatter = proc do |severity, _, script_name, message|
metadata CHANGED
@@ -1,150 +1,150 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tefoji
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.0
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet By Perforce
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-10-27 00:00:00.000000000 Z
11
+ date: 2024-01-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
+ name: debug
14
15
  requirement: !ruby/object:Gem::Requirement
15
16
  requirements:
16
17
  - - ">="
17
18
  - !ruby/object:Gem::Version
18
19
  version: 1.0.0
19
- name: debug
20
- prerelease: false
21
20
  type: :development
21
+ prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.0.0
27
27
  - !ruby/object:Gem::Dependency
28
+ name: rake
28
29
  requirement: !ruby/object:Gem::Requirement
29
30
  requirements:
30
31
  - - "~>"
31
32
  - !ruby/object:Gem::Version
32
33
  version: '13.0'
33
- name: rake
34
- prerelease: false
35
34
  type: :development
35
+ prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
+ name: rspec
42
43
  requirement: !ruby/object:Gem::Requirement
43
44
  requirements:
44
45
  - - "~>"
45
46
  - !ruby/object:Gem::Version
46
47
  version: '3.0'
47
- name: rspec
48
- prerelease: false
49
48
  type: :development
49
+ prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
55
  - !ruby/object:Gem::Dependency
56
+ name: rubocop
56
57
  requirement: !ruby/object:Gem::Requirement
57
58
  requirements:
58
59
  - - ">="
59
60
  - !ruby/object:Gem::Version
60
61
  version: '0'
61
- name: rubocop
62
- prerelease: false
63
62
  type: :development
63
+ prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
+ name: rubocop-rake
70
71
  requirement: !ruby/object:Gem::Requirement
71
72
  requirements:
72
73
  - - ">="
73
74
  - !ruby/object:Gem::Version
74
75
  version: '0'
75
- name: rubocop-rake
76
- prerelease: false
77
76
  type: :development
77
+ prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
+ name: rubocop-rspec
84
85
  requirement: !ruby/object:Gem::Requirement
85
86
  requirements:
86
87
  - - ">="
87
88
  - !ruby/object:Gem::Version
88
89
  version: '0'
89
- name: rubocop-rspec
90
- prerelease: false
91
90
  type: :development
91
+ prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
+ name: webmock
98
99
  requirement: !ruby/object:Gem::Requirement
99
100
  requirements:
100
101
  - - ">="
101
102
  - !ruby/object:Gem::Version
102
103
  version: '0'
103
- name: webmock
104
- prerelease: false
105
104
  type: :development
105
+ prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
+ name: yard
112
113
  requirement: !ruby/object:Gem::Requirement
113
114
  requirements:
114
115
  - - "~>"
115
116
  - !ruby/object:Gem::Version
116
117
  version: '0.9'
117
- name: yard
118
- prerelease: false
119
118
  type: :development
119
+ prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0.9'
125
125
  - !ruby/object:Gem::Dependency
126
+ name: docopt
126
127
  requirement: !ruby/object:Gem::Requirement
127
128
  requirements:
128
129
  - - "~>"
129
130
  - !ruby/object:Gem::Version
130
131
  version: '0.6'
131
- name: docopt
132
- prerelease: false
133
132
  type: :runtime
133
+ prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0.6'
139
139
  - !ruby/object:Gem::Dependency
140
+ name: rest-client
140
141
  requirement: !ruby/object:Gem::Requirement
141
142
  requirements:
142
143
  - - "~>"
143
144
  - !ruby/object:Gem::Version
144
145
  version: '2.1'
145
- name: rest-client
146
- prerelease: false
147
146
  type: :runtime
147
+ prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - "~>"
@@ -175,7 +175,7 @@ metadata:
175
175
  homepage_uri: https://github.com/puppetlabs/tefoji
176
176
  source_code_uri: https://github.com/puppetlabs/tefoji
177
177
  changelog_uri: https://github.com/puppetlabs/tefoji/CHANGELOG.md
178
- post_install_message:
178
+ post_install_message:
179
179
  rdoc_options: []
180
180
  require_paths:
181
181
  - lib
@@ -190,8 +190,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
190
190
  - !ruby/object:Gem::Version
191
191
  version: '0'
192
192
  requirements: []
193
- rubygems_version: 3.3.26
194
- signing_key:
193
+ rubygems_version: 3.2.33
194
+ signing_key:
195
195
  specification_version: 4
196
196
  summary: Generate Jira issues from a YAML specification.
197
197
  test_files: []