tefoji 3.3.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 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: []