chef-provisioning 2.0.0 → 2.0.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +899 -885
  3. data/Gemfile +17 -17
  4. data/LICENSE +201 -201
  5. data/README.md +312 -312
  6. data/Rakefile +55 -55
  7. data/chef-provisioning.gemspec +38 -38
  8. data/lib/chef/provider/load_balancer.rb +75 -75
  9. data/lib/chef/provider/machine.rb +219 -219
  10. data/lib/chef/provider/machine_batch.rb +224 -224
  11. data/lib/chef/provider/machine_execute.rb +36 -35
  12. data/lib/chef/provider/machine_file.rb +55 -55
  13. data/lib/chef/provider/machine_image.rb +105 -105
  14. data/lib/chef/provisioning.rb +110 -110
  15. data/lib/chef/provisioning/action_handler.rb +68 -68
  16. data/lib/chef/provisioning/add_prefix_action_handler.rb +35 -35
  17. data/lib/chef/provisioning/chef_managed_entry_store.rb +128 -128
  18. data/lib/chef/provisioning/chef_provider_action_handler.rb +74 -74
  19. data/lib/chef/provisioning/chef_run_data.rb +132 -132
  20. data/lib/chef/provisioning/convergence_strategy.rb +28 -28
  21. data/lib/chef/provisioning/convergence_strategy/ignore_convergence_failure.rb +54 -54
  22. data/lib/chef/provisioning/convergence_strategy/install_cached.rb +188 -188
  23. data/lib/chef/provisioning/convergence_strategy/install_msi.rb +71 -71
  24. data/lib/chef/provisioning/convergence_strategy/install_sh.rb +71 -71
  25. data/lib/chef/provisioning/convergence_strategy/no_converge.rb +35 -35
  26. data/lib/chef/provisioning/convergence_strategy/precreate_chef_objects.rb +255 -255
  27. data/lib/chef/provisioning/driver.rb +323 -323
  28. data/lib/chef/provisioning/load_balancer_spec.rb +14 -14
  29. data/lib/chef/provisioning/machine.rb +112 -112
  30. data/lib/chef/provisioning/machine/basic_machine.rb +84 -84
  31. data/lib/chef/provisioning/machine/unix_machine.rb +288 -288
  32. data/lib/chef/provisioning/machine/windows_machine.rb +108 -108
  33. data/lib/chef/provisioning/machine_image_spec.rb +34 -34
  34. data/lib/chef/provisioning/machine_spec.rb +58 -58
  35. data/lib/chef/provisioning/managed_entry.rb +121 -121
  36. data/lib/chef/provisioning/managed_entry_store.rb +136 -136
  37. data/lib/chef/provisioning/recipe_dsl.rb +99 -99
  38. data/lib/chef/provisioning/rspec.rb +27 -27
  39. data/lib/chef/provisioning/transport.rb +100 -100
  40. data/lib/chef/provisioning/transport/ssh.rb +403 -403
  41. data/lib/chef/provisioning/transport/winrm.rb +144 -156
  42. data/lib/chef/provisioning/version.rb +5 -5
  43. data/lib/chef/resource/chef_data_bag_resource.rb +146 -146
  44. data/lib/chef/resource/load_balancer.rb +57 -57
  45. data/lib/chef/resource/machine.rb +128 -128
  46. data/lib/chef/resource/machine_batch.rb +78 -78
  47. data/lib/chef/resource/machine_execute.rb +30 -29
  48. data/lib/chef/resource/machine_file.rb +34 -34
  49. data/lib/chef/resource/machine_image.rb +35 -35
  50. data/lib/chef_metal.rb +1 -1
  51. data/spec/chef/provisioning/convergence_strategy/ignore_convergence_failure_spec.rb +86 -86
  52. data/spec/spec_helper.rb +27 -27
  53. metadata +5 -5
@@ -1,108 +1,108 @@
1
- require 'chef/provisioning/machine/basic_machine'
2
-
3
- class Chef
4
- module Provisioning
5
- class Machine
6
- class WindowsMachine < BasicMachine
7
- def initialize(machine_spec, transport, convergence_strategy)
8
- super
9
- end
10
-
11
- # Options include:
12
- #
13
- # command_prefix - prefix to put in front of any command, e.g. sudo
14
- attr_reader :options
15
-
16
- # Delete file
17
- def delete_file(action_handler, path)
18
- if file_exists?(path)
19
- action_handler.perform_action "delete file #{escape(path)} on #{machine_spec.name}" do
20
- transport.execute("Remove-Item #{escape(path)}").error!
21
- end
22
- end
23
- end
24
-
25
- def is_directory?(path)
26
- parse_boolean(transport.execute("Test-Path #{escape(path)} -pathtype container", :read_only => true).stdout)
27
- end
28
-
29
- # Return true or false depending on whether file exists
30
- def file_exists?(path)
31
- parse_boolean(transport.execute("Test-Path #{escape(path)}", :read_only => true).stdout)
32
- end
33
-
34
- def files_different?(path, local_path, content=nil)
35
- if !file_exists?(path) || (local_path && !File.exists?(local_path))
36
- return true
37
- end
38
-
39
- # Get remote checksum of file (from http://stackoverflow.com/a/13926809)
40
- result = transport.execute(<<-EOM, :read_only => true)
41
- $md5 = [System.Security.Cryptography.MD5]::Create("MD5")
42
- $fd = [System.IO.File]::OpenRead(#{path.inspect})
43
- $buf = new-object byte[] (1024*1024*8) # 8mb buffer
44
- while (($read_len = $fd.Read($buf,0,$buf.length)) -eq $buf.length){
45
- $total += $buf.length
46
- $md5.TransformBlock($buf,$offset,$buf.length,$buf,$offset)
47
- }
48
- # finalize the last read
49
- $md5.TransformFinalBlock($buf,0,$read_len)
50
- $hash = $md5.Hash
51
- # convert hash bytes to hex formatted string
52
- $hash | foreach { $hash_txt += $_.ToString("x2") }
53
- $hash_txt
54
- EOM
55
- result.error!
56
- remote_sum = result.stdout.split(' ')[0]
57
- digest = Digest::SHA256.new
58
- if content
59
- digest.update(content)
60
- else
61
- File.open(local_path, 'rb') do |io|
62
- while (buf = io.read(4096)) && buf.length > 0
63
- digest.update(buf)
64
- end
65
- end
66
- end
67
- remote_sum != digest.hexdigest
68
- end
69
-
70
- def create_dir(action_handler, path)
71
- if !file_exists?(path)
72
- action_handler.perform_action "create directory #{path} on #{machine_spec.name}" do
73
- transport.execute("New-Item #{escape(path)} -Type directory")
74
- end
75
- end
76
- end
77
-
78
- def system_drive
79
- transport.execute('$env:SystemDrive').stdout.strip
80
- end
81
-
82
- # Set file attributes { :owner, :group, :rights }
83
- # def set_attributes(action_handler, path, attributes)
84
- # end
85
-
86
- # Get file attributes { :owner, :group, :rights }
87
- # def get_attributes(path)
88
- # end
89
-
90
- def dirname_on_machine(path)
91
- path.split(/[\\\/]/)[0..-2].join('\\')
92
- end
93
-
94
- def escape(string)
95
- transport.escape(string)
96
- end
97
-
98
- def parse_boolean(string)
99
- if string =~ /^\s*true\s*$/mi
100
- true
101
- else
102
- false
103
- end
104
- end
105
- end
106
- end
107
- end
108
- end
1
+ require 'chef/provisioning/machine/basic_machine'
2
+
3
+ class Chef
4
+ module Provisioning
5
+ class Machine
6
+ class WindowsMachine < BasicMachine
7
+ def initialize(machine_spec, transport, convergence_strategy)
8
+ super
9
+ end
10
+
11
+ # Options include:
12
+ #
13
+ # command_prefix - prefix to put in front of any command, e.g. sudo
14
+ attr_reader :options
15
+
16
+ # Delete file
17
+ def delete_file(action_handler, path)
18
+ if file_exists?(path)
19
+ action_handler.perform_action "delete file #{escape(path)} on #{machine_spec.name}" do
20
+ transport.execute("Remove-Item #{escape(path)}").error!
21
+ end
22
+ end
23
+ end
24
+
25
+ def is_directory?(path)
26
+ parse_boolean(transport.execute("Test-Path #{escape(path)} -pathtype container", :read_only => true).stdout)
27
+ end
28
+
29
+ # Return true or false depending on whether file exists
30
+ def file_exists?(path)
31
+ parse_boolean(transport.execute("Test-Path #{escape(path)}", :read_only => true).stdout)
32
+ end
33
+
34
+ def files_different?(path, local_path, content=nil)
35
+ if !file_exists?(path) || (local_path && !File.exists?(local_path))
36
+ return true
37
+ end
38
+
39
+ # Get remote checksum of file (from http://stackoverflow.com/a/13926809)
40
+ result = transport.execute(<<-EOM, :read_only => true)
41
+ $md5 = [System.Security.Cryptography.MD5]::Create("MD5")
42
+ $fd = [System.IO.File]::OpenRead(#{path.inspect})
43
+ $buf = new-object byte[] (1024*1024*8) # 8mb buffer
44
+ while (($read_len = $fd.Read($buf,0,$buf.length)) -eq $buf.length){
45
+ $total += $buf.length
46
+ $md5.TransformBlock($buf,$offset,$buf.length,$buf,$offset)
47
+ }
48
+ # finalize the last read
49
+ $md5.TransformFinalBlock($buf,0,$read_len)
50
+ $hash = $md5.Hash
51
+ # convert hash bytes to hex formatted string
52
+ $hash | foreach { $hash_txt += $_.ToString("x2") }
53
+ $hash_txt
54
+ EOM
55
+ result.error!
56
+ remote_sum = result.stdout.split(' ')[0]
57
+ digest = Digest::SHA256.new
58
+ if content
59
+ digest.update(content)
60
+ else
61
+ File.open(local_path, 'rb') do |io|
62
+ while (buf = io.read(4096)) && buf.length > 0
63
+ digest.update(buf)
64
+ end
65
+ end
66
+ end
67
+ remote_sum != digest.hexdigest
68
+ end
69
+
70
+ def create_dir(action_handler, path)
71
+ if !file_exists?(path)
72
+ action_handler.perform_action "create directory #{path} on #{machine_spec.name}" do
73
+ transport.execute("New-Item #{escape(path)} -Type directory")
74
+ end
75
+ end
76
+ end
77
+
78
+ def system_drive
79
+ transport.execute('$env:SystemDrive').stdout.strip
80
+ end
81
+
82
+ # Set file attributes { :owner, :group, :rights }
83
+ # def set_attributes(action_handler, path, attributes)
84
+ # end
85
+
86
+ # Get file attributes { :owner, :group, :rights }
87
+ # def get_attributes(path)
88
+ # end
89
+
90
+ def dirname_on_machine(path)
91
+ path.split(/[\\\/]/)[0..-2].join('\\')
92
+ end
93
+
94
+ def escape(string)
95
+ transport.escape(string)
96
+ end
97
+
98
+ def parse_boolean(string)
99
+ if string =~ /^\s*true\s*$/mi
100
+ true
101
+ else
102
+ false
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -1,34 +1,34 @@
1
- require 'chef/provisioning/managed_entry'
2
-
3
- class Chef
4
- module Provisioning
5
- #
6
- # Specification for a image. Sufficient information to find and contact it
7
- # after it has been set up.
8
- #
9
- class MachineImageSpec < ManagedEntry
10
- alias :location :reference
11
- alias :location= :reference=
12
-
13
- def from_image
14
- data['from_image']
15
- end
16
- def from_image=(value)
17
- data['from_image'] = value
18
- end
19
- def run_list
20
- data['run_list']
21
- end
22
- def run_list=(value)
23
- data['run_list'] = value
24
- end
25
- def machine_options
26
- @machine_options
27
- end
28
- def machine_options=(value)
29
- Chef::Log.warn("Machine options are no longer stored in machine_image_spec. Drivers that store machine_options will stop working with Provisioning 1.0.")
30
- @machine_options = value
31
- end
32
- end
33
- end
34
- end
1
+ require 'chef/provisioning/managed_entry'
2
+
3
+ class Chef
4
+ module Provisioning
5
+ #
6
+ # Specification for a image. Sufficient information to find and contact it
7
+ # after it has been set up.
8
+ #
9
+ class MachineImageSpec < ManagedEntry
10
+ alias :location :reference
11
+ alias :location= :reference=
12
+
13
+ def from_image
14
+ data['from_image']
15
+ end
16
+ def from_image=(value)
17
+ data['from_image'] = value
18
+ end
19
+ def run_list
20
+ data['run_list']
21
+ end
22
+ def run_list=(value)
23
+ data['run_list'] = value
24
+ end
25
+ def machine_options
26
+ @machine_options
27
+ end
28
+ def machine_options=(value)
29
+ Chef::Log.warn("Machine options are no longer stored in machine_image_spec. Drivers that store machine_options will stop working with Provisioning 1.0.")
30
+ @machine_options = value
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,58 +1,58 @@
1
- require 'chef/provisioning/managed_entry'
2
-
3
- class Chef
4
- module Provisioning
5
- #
6
- # Specification for a machine. Sufficient information to find and contact it
7
- # after it has been set up.
8
- #
9
- class MachineSpec < ManagedEntry
10
- def initialize(*args)
11
- super
12
- data['name'] ||= name
13
- # Upgrade from metal to chef_provisioning ASAP.
14
- if data['normal'] && !data['normal']['chef_provisioning'] && data['normal']['metal']
15
- data['normal']['chef_provisioning'] = data['normal'].delete('metal')
16
- end
17
- end
18
-
19
- alias :node :data
20
-
21
- def attrs
22
- data['normal'] ||= {}
23
- data['normal']['chef_provisioning'] ||= {}
24
- end
25
-
26
- #
27
- # Location of this machine. This should be a freeform hash, with enough
28
- # information for the driver to look it up and create a Machine object to
29
- # access it.
30
- #
31
- # This MUST include a 'driver_url' attribute with the driver's URL in it.
32
- #
33
- # chef-provisioning will do its darnedest to not lose this information.
34
- #
35
- def reference
36
- attrs['reference'] || attrs['location']
37
- end
38
-
39
- #
40
- # Set the location for this machine.
41
- #
42
- def reference=(value)
43
- attrs.delete('location')
44
- attrs['reference'] = value
45
- end
46
-
47
- alias :location :reference
48
- alias :location= :reference=
49
-
50
- def from_image
51
- attrs['from_image']
52
- end
53
- def from_image=(value)
54
- attrs['from_image'] = value
55
- end
56
- end
57
- end
58
- end
1
+ require 'chef/provisioning/managed_entry'
2
+
3
+ class Chef
4
+ module Provisioning
5
+ #
6
+ # Specification for a machine. Sufficient information to find and contact it
7
+ # after it has been set up.
8
+ #
9
+ class MachineSpec < ManagedEntry
10
+ def initialize(*args)
11
+ super
12
+ data['name'] ||= name
13
+ # Upgrade from metal to chef_provisioning ASAP.
14
+ if data['normal'] && !data['normal']['chef_provisioning'] && data['normal']['metal']
15
+ data['normal']['chef_provisioning'] = data['normal'].delete('metal')
16
+ end
17
+ end
18
+
19
+ alias :node :data
20
+
21
+ def attrs
22
+ data['normal'] ||= {}
23
+ data['normal']['chef_provisioning'] ||= {}
24
+ end
25
+
26
+ #
27
+ # Location of this machine. This should be a freeform hash, with enough
28
+ # information for the driver to look it up and create a Machine object to
29
+ # access it.
30
+ #
31
+ # This MUST include a 'driver_url' attribute with the driver's URL in it.
32
+ #
33
+ # chef-provisioning will do its darnedest to not lose this information.
34
+ #
35
+ def reference
36
+ attrs['reference'] || attrs['location']
37
+ end
38
+
39
+ #
40
+ # Set the location for this machine.
41
+ #
42
+ def reference=(value)
43
+ attrs.delete('location')
44
+ attrs['reference'] = value
45
+ end
46
+
47
+ alias :location :reference
48
+ alias :location= :reference=
49
+
50
+ def from_image
51
+ attrs['from_image']
52
+ end
53
+ def from_image=(value)
54
+ attrs['from_image'] = value
55
+ end
56
+ end
57
+ end
58
+ end
@@ -1,121 +1,121 @@
1
- class Chef
2
- module Provisioning
3
- #
4
- # Specification for a managed thing. Remembers where it was stored, and lets
5
- # you stuff reference data in it.
6
- #
7
- class ManagedEntry
8
- def initialize(managed_entry_store, resource_type, name, data=nil)
9
- @managed_entry_store = managed_entry_store
10
- @resource_type = resource_type
11
- @name = name
12
- @data = data || {}
13
- end
14
-
15
- attr_reader :managed_entry_store
16
- attr_reader :resource_type
17
- attr_reader :name
18
- attr_reader :data
19
-
20
- def attrs
21
- data
22
- end
23
-
24
- #
25
- # Globally unique identifier for this machine. Does not depend on the machine's
26
- # reference or existence.
27
- #
28
- def id
29
- managed_entry_store.identifier(resource_type, name)
30
- end
31
-
32
- #
33
- # Reference to this managed thing. This should be a freeform hash, with enough
34
- # information for the driver to look it up and create a Machine object to
35
- # access it.
36
- #
37
- # This MUST include a 'driver_url' attribute with the driver's URL in it.
38
- #
39
- # chef-provisioning will do its darnedest to not lose this information.
40
- #
41
- def reference
42
- # Backcompat: old data bags didn't have the "reference" field. If we have
43
- # no reference field in the data, and the data bag is non-empty, return
44
- # the root of the data bag.
45
- attrs['reference'] || attrs['location'] || (attrs == {} ? nil : attrs)
46
- end
47
-
48
- #
49
- # Set the reference for this machine.
50
- #
51
- def reference=(value)
52
- self.attrs['reference'] = value
53
- end
54
-
55
- # URL to the driver.
56
- def driver_url
57
- attrs['driver_url'] || (reference ? reference['driver_url'] : nil)
58
- end
59
- def driver_url=(value)
60
- attrs['driver_url'] = value
61
- end
62
-
63
- #
64
- # Save this node to the server. If you have significant information that
65
- # could be lost, you should do this as quickly as possible. Data will be
66
- # saved automatically for you after allocate_machine and ready_machine.
67
- #
68
- def save(action_handler)
69
- managed_entry_store.save_data(resource_type, name, data, action_handler)
70
- end
71
-
72
- def delete(action_handler)
73
- managed_entry_store.delete_data(resource_type, name, action_handler)
74
- end
75
-
76
-
77
- #
78
- # Subclass interface
79
- #
80
-
81
- #
82
- # Get the given data
83
- #
84
- # @param resource_type [Symbol] The type of thing to retrieve (:machine, :machine_image, :load_balancer, :aws_vpc, :aws_subnet, ...)
85
- # @param name [String] The unique identifier of the thing to retrieve
86
- #
87
- # @return [Hash,Array] The data, or `nil` if the data does not exist. Will be JSON- and YAML-compatible (Hash, Array, String, Integer, Boolean, Nil)
88
- #
89
- def get_data(resource_type, name)
90
- raise NotImplementedError, :delete_data
91
- end
92
-
93
- #
94
- # Save the given data
95
- #
96
- # @param resource_type [Symbol] The type of thing to save (:machine, :machine_image, :load_balancer, :aws_vpc, :aws_subnet ...)
97
- # @param name [String] The unique identifier of the thing to save
98
- # @param data [Hash,Array] The data to save. Must be JSON- and YAML-compatible (Hash, Array, String, Integer, Boolean, Nil)
99
- #
100
- def save_data(resource_type, name, data, action_handler)
101
- raise NotImplementedError, :delete_data
102
- end
103
-
104
- #
105
- # Delete the given data
106
- #
107
- # @param resource_type [Symbol] The type of thing to delete (:machine, :machine_image, :load_balancer, :aws_vpc, :aws_subnet, ...)
108
- # @param name [String] The unique identifier of the thing to delete
109
- #
110
- # @return [Boolean] Whether anything was deleted or not.
111
- #
112
- def delete_data(resource_type, name, action_handler)
113
- raise NotImplementedError, :delete_data
114
- end
115
-
116
- def identifier(resource_type, name)
117
- raise NotImplementedError, :identifier
118
- end
119
- end
120
- end
121
- end
1
+ class Chef
2
+ module Provisioning
3
+ #
4
+ # Specification for a managed thing. Remembers where it was stored, and lets
5
+ # you stuff reference data in it.
6
+ #
7
+ class ManagedEntry
8
+ def initialize(managed_entry_store, resource_type, name, data=nil)
9
+ @managed_entry_store = managed_entry_store
10
+ @resource_type = resource_type
11
+ @name = name
12
+ @data = data || {}
13
+ end
14
+
15
+ attr_reader :managed_entry_store
16
+ attr_reader :resource_type
17
+ attr_reader :name
18
+ attr_reader :data
19
+
20
+ def attrs
21
+ data
22
+ end
23
+
24
+ #
25
+ # Globally unique identifier for this machine. Does not depend on the machine's
26
+ # reference or existence.
27
+ #
28
+ def id
29
+ managed_entry_store.identifier(resource_type, name)
30
+ end
31
+
32
+ #
33
+ # Reference to this managed thing. This should be a freeform hash, with enough
34
+ # information for the driver to look it up and create a Machine object to
35
+ # access it.
36
+ #
37
+ # This MUST include a 'driver_url' attribute with the driver's URL in it.
38
+ #
39
+ # chef-provisioning will do its darnedest to not lose this information.
40
+ #
41
+ def reference
42
+ # Backcompat: old data bags didn't have the "reference" field. If we have
43
+ # no reference field in the data, and the data bag is non-empty, return
44
+ # the root of the data bag.
45
+ attrs['reference'] || attrs['location'] || (attrs == {} ? nil : attrs)
46
+ end
47
+
48
+ #
49
+ # Set the reference for this machine.
50
+ #
51
+ def reference=(value)
52
+ self.attrs['reference'] = value
53
+ end
54
+
55
+ # URL to the driver.
56
+ def driver_url
57
+ attrs['driver_url'] || (reference ? reference['driver_url'] : nil)
58
+ end
59
+ def driver_url=(value)
60
+ attrs['driver_url'] = value
61
+ end
62
+
63
+ #
64
+ # Save this node to the server. If you have significant information that
65
+ # could be lost, you should do this as quickly as possible. Data will be
66
+ # saved automatically for you after allocate_machine and ready_machine.
67
+ #
68
+ def save(action_handler)
69
+ managed_entry_store.save_data(resource_type, name, data, action_handler)
70
+ end
71
+
72
+ def delete(action_handler)
73
+ managed_entry_store.delete_data(resource_type, name, action_handler)
74
+ end
75
+
76
+
77
+ #
78
+ # Subclass interface
79
+ #
80
+
81
+ #
82
+ # Get the given data
83
+ #
84
+ # @param resource_type [Symbol] The type of thing to retrieve (:machine, :machine_image, :load_balancer, :aws_vpc, :aws_subnet, ...)
85
+ # @param name [String] The unique identifier of the thing to retrieve
86
+ #
87
+ # @return [Hash,Array] The data, or `nil` if the data does not exist. Will be JSON- and YAML-compatible (Hash, Array, String, Integer, Boolean, Nil)
88
+ #
89
+ def get_data(resource_type, name)
90
+ raise NotImplementedError, :delete_data
91
+ end
92
+
93
+ #
94
+ # Save the given data
95
+ #
96
+ # @param resource_type [Symbol] The type of thing to save (:machine, :machine_image, :load_balancer, :aws_vpc, :aws_subnet ...)
97
+ # @param name [String] The unique identifier of the thing to save
98
+ # @param data [Hash,Array] The data to save. Must be JSON- and YAML-compatible (Hash, Array, String, Integer, Boolean, Nil)
99
+ #
100
+ def save_data(resource_type, name, data, action_handler)
101
+ raise NotImplementedError, :delete_data
102
+ end
103
+
104
+ #
105
+ # Delete the given data
106
+ #
107
+ # @param resource_type [Symbol] The type of thing to delete (:machine, :machine_image, :load_balancer, :aws_vpc, :aws_subnet, ...)
108
+ # @param name [String] The unique identifier of the thing to delete
109
+ #
110
+ # @return [Boolean] Whether anything was deleted or not.
111
+ #
112
+ def delete_data(resource_type, name, action_handler)
113
+ raise NotImplementedError, :delete_data
114
+ end
115
+
116
+ def identifier(resource_type, name)
117
+ raise NotImplementedError, :identifier
118
+ end
119
+ end
120
+ end
121
+ end