knife-topo 1.1.2 → 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.
@@ -15,79 +15,79 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
-
19
- require_relative 'topology_helper'
20
18
  require 'chef/knife/search'
19
+ require 'chef/knife/topo/command_helper'
21
20
 
22
- class Chef
23
- class Knife
24
- class TopoSearch < Chef::Knife::Search
21
+ module KnifeTopo
22
+ # knife topo search
23
+ class TopoSearch < Chef::Knife::Search
24
+ deps do
25
+ end
25
26
 
26
- banner "knife topo search [ QUERY ] (options)"
27
-
28
- option :topo,
29
- :long => "--topo TOPOLOGY",
30
- :description => "Restrict search to nodes in the specified topology"
31
-
32
- option :no_topo,
33
- :long => "--no-topo",
34
- :description => "Restrict search to nodes that are not in any topology",
35
- :boolean => true,
36
- :default => true
27
+ banner 'knife topo search [ QUERY ] (options)'
37
28
 
38
- # Make the base search options available on topo search
39
- self.options = (Chef::Knife::Search.options).merge(self.options)
29
+ option(
30
+ :topo,
31
+ long: '--topo TOPOLOGY',
32
+ description: 'Restrict search to nodes in the specified topology'
33
+ )
40
34
 
41
- def initialize (args)
42
- super
43
- topo_query = constrain_query(@name_args[0] || config[:query], config[:topo], !config[:no_topo].nil?)
35
+ option(
36
+ :no_topo,
37
+ long: '--no-topo',
38
+ description: 'Restrict search to nodes that are not in any topology',
39
+ boolean: true
40
+ )
44
41
 
45
- # force a node search
46
- @name_args[0] = "node"
42
+ # Make the base search options available on topo search
43
+ orig_opts = KnifeTopo::TopoSearch.options
44
+ self.options = (Chef::Knife::Search.options).merge(orig_opts)
47
45
 
48
- # override any query
49
- if config[:query]
50
- config[:query] = topo_query
51
- else
52
- @name_args[1] = topo_query
53
- end
46
+ include KnifeTopo::CommandHelper
54
47
 
55
- end
48
+ def run
49
+ setup_query
50
+ super
51
+ rescue StandardError => e
52
+ raise if Chef::Config[:verbosity] == 2
53
+ ui.error "Topology search for \"#{@query}\" exited with error"
54
+ humanize_exception(e)
55
+ end
56
+
57
+ def setup_query
58
+ query_str = @name_args[0] || config[:query]
59
+ topo_query = constrain_query(query_str, config[:topo])
56
60
 
57
- def run
58
- begin
59
- super
60
- rescue Exception => e
61
- raise if Chef::Config[:verbosity] == 2
62
- ui.error "Topology search for \"#{@query}\" exited with error"
63
- humanize_exception(e)
64
- end
61
+ # force a node search
62
+ @name_args[0] = 'node'
63
+
64
+ # override any query
65
+ if config[:query]
66
+ config[:query] = topo_query
67
+ else
68
+ @name_args[1] = topo_query
65
69
  end
70
+ end
71
+
72
+ def constrain_query(query, topo_name)
73
+ # group existing query workaround for strange behavior with
74
+ # NOTs and invalid query if put brackets round them
75
+ group_query = query && !query.start_with?('NOT') ? "(#{query})" : query
66
76
 
67
- private
77
+ # search specific topologies or all/none
78
+ constraint = (topo_name) ? 'topo_name:' + topo_name : 'topo_name:*'
79
+
80
+ # combine the grouped query and constraint
81
+ combine(query, group_query, constraint)
82
+ end
68
83
 
69
- include Chef::Knife::TopologyHelper
70
-
71
- def constrain_query(query, topo_name, no_topo)
72
-
73
- # group existing query
74
- # workaround for strange behavior with NOTs and invalid query if put brackets round them
75
- group_query = query && !query.start_with?("NOT") ? "(#{query})" : query
76
-
77
- # search specific topologies or all/none
78
- constraint = (topo_name) ? "topo_name:" + topo_name : "topo_name:*"
79
-
80
- # combine the grouped query and constraint
81
- if no_topo
82
- result = query ? "#{group_query} NOT #{constraint}" : "NOT #{constraint}"
83
- else
84
- result = query ? "#{constraint} AND #{group_query}" : constraint
85
- end
86
-
87
- result
84
+ def combine(query, group_query, constraint)
85
+ find_in_topo = config[:topo] || config[:no_topo].nil?
86
+ if find_in_topo
87
+ query ? "#{constraint} AND #{group_query}" : constraint
88
+ else
89
+ query ? "#{group_query} NOT #{constraint}" : "NOT #{constraint}"
88
90
  end
89
-
90
-
91
91
  end
92
92
  end
93
- end
93
+ end
@@ -17,122 +17,37 @@
17
17
  #
18
18
 
19
19
  require 'chef/knife'
20
- require_relative 'topo_cookbook_upload'
20
+ require 'chef/knife/topo_create'
21
21
 
22
- class Chef
23
- class Knife
24
- class TopoUpdate < Chef::Knife
25
-
26
- deps do
27
- Chef::Knife::TopoCookbookUpload.load_deps
28
- end
29
-
30
- banner "knife topo update [ TOPOLOGY ] (options)"
31
-
32
- option :data_bag,
33
- :short => '-D DATA_BAG',
34
- :long => "--data-bag DATA_BAG",
35
- :description => "The data bag the topologies are stored in"
36
-
37
- option :disable_upload,
38
- :long => "--disable-upload",
39
- :description => "Do not upload topo cookbooks",
40
- :boolean => true
41
-
42
- # Make called command options available
43
- self.options = Chef::Knife::TopoCookbookUpload.options.merge(self.options)
44
-
45
- def initialize (args)
46
- super
47
- @topo_upload_args = initialize_cmd_args(args, [ 'topo', 'cookbook', 'upload', '' ])
48
-
49
- # All called commands need to accept union of options
50
- Chef::Knife::TopoCookbookUpload.options = options
51
- end
52
-
53
- def run
54
-
55
- bag_name = topo_bag_name(config[:data_bag])
56
-
57
- if !@name_args[0]
58
- ui.confirm("Do you want to update all topologies in the #{bag_name} data bag", true, true)
59
- end
60
-
61
- topo_name = @name_args[0]
62
-
63
- if topo_name
64
- # update a specific topo
65
-
66
- unless current_topo = load_from_server(bag_name, topo_name)
67
- ui.fatal "Topology #{bag_name}/#{topo_name} does not exist on server - use 'knife topo create' first"
68
- exit(1)
69
- end
70
-
71
- unless topo = load_from_file(bag_name, topo_name)
72
- ui.info "No topology found in #{topologies_path}/#{bag_name}/#{topo_name}.json - exiting without action"
73
- exit(0)
74
- end
75
-
76
- msg = "Updating topology #{display_name(current_topo)}"
77
- msg = msg + " to version " + format_topo_version(topo) if topo['version']
78
- ui.info msg
79
-
80
- update_topo(topo)
81
-
82
- ui.info "Updated topology " + display_name(topo)
83
- ui.info("Build information: " + topo['buildstamp']) if topo['buildstamp']
84
-
85
- else
86
- # find all topologies from server then update them from file, skipping any that have no file
87
- ui.info "Updating all topologies in data bag: #{bag_name}"
88
-
89
- unless dbag = load_from_server(bag_name)
90
- ui.fatal "Data bag #{bag_name} does not exist on server - use 'knife topo create' first"
91
- exit(1)
92
- end
93
-
94
- dbag.keys.each do |topo_name|
22
+ module KnifeTopo
23
+ # knife topo update
24
+ class TopoUpdate < KnifeTopo::TopoCreate
25
+ deps do
26
+ KnifeTopo::TopoCreate.load_deps
27
+ end
28
+ banner 'knife topo update TOPOLOGY (options)'
95
29
 
96
- topo = load_from_file(bag_name, topo_name)
97
- if !topo
98
- # do not update topologies that are not in the local workspace
99
- ui.info("No topology file found in #{topologies_path}/#{bag_name}/#{topo_name}.json - skipping")
100
- else
101
- msg = "Updating topology " + topo_name
102
- msg = msg + " to version " + format_topo_version(topo) if topo['version']
103
- ui.info msg
104
- ui.info("Build information: " + topo['buildstamp']) if topo['buildstamp']
105
- update_topo(topo)
106
- end
107
- end
108
- ui.info "Updated topologies"
30
+ # Make called command options available
31
+ orig_opts = KnifeTopo::TopoCreate.options
32
+ bootstrap_opts = KnifeTopo::TopoBootstrap.options
33
+ self.options = bootstrap_opts.merge(orig_opts)
109
34
 
110
- end
111
-
112
- end
35
+ def run
36
+ validate_args
37
+ load_topo_from_server_or_exit(@topo_name)
38
+ update_topo
113
39
 
114
- def update_topo(topo)
115
- topo.save
116
- @topo_upload_args[3] = topo['name']
117
- upload_cookbooks(@topo_upload_args) if (!config[:disable_upload])
40
+ check_chef_env(@topo['chef_environment'])
41
+ upload_artifacts unless config[:disable_upload]
42
+ update_nodes
118
43
 
119
- topo_hash = topo.raw_data
120
- nodes = merge_topo_properties(topo_hash['nodes'], topo_hash)
121
- config[:disable_editing] = true
122
-
123
- if nodes && nodes.length > 0
124
- nodes.each do |updates|
125
- node_name = updates['name']
126
- node = update_node(updates)
127
- ui.info "Node #{node_name} does not exist - skipping update" if (!node)
128
- end
129
- else
130
- ui.info "No nodes found for topology #{topo_hash['name']}"
131
- end
132
- end
44
+ report
45
+ end
133
46
 
134
- include Chef::Knife::TopologyHelper
135
-
47
+ def update_topo
48
+ # Load the topology data & update the topology bag
49
+ @topo = load_local_topo_or_exit(@topo_name)
50
+ @topo.save
136
51
  end
137
52
  end
138
53
  end
@@ -0,0 +1,75 @@
1
+ #
2
+ # Author:: Christine Draper (<christine_draper@thirdwaveinsights.com>)
3
+ # Copyright:: Copyright (c) 2015 ThirdWave Insights LLC
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ #
20
+ # The converter class converts data in a given format into "topo" format data
21
+ #
22
+
23
+ class Chef
24
+ module Topo
25
+ # Base converter
26
+ class Converter
27
+ # rubocop:disable Style/ClassVars
28
+ # Converters for each format
29
+ @@converter_classes = {}
30
+
31
+ def self.register_converter(format, class_name)
32
+ @@converter_classes[format] = class_name
33
+ end
34
+
35
+ # Get the right converter for the input format
36
+ def self.converter(format)
37
+ converter_class = @@converter_classes[format]
38
+ converter_class = load_converter(format) unless converter_class
39
+
40
+ Object.const_get(converter_class).new(format)
41
+ end
42
+
43
+ def self.load_converter(format)
44
+ require "chef/topo/converter/#{format}"
45
+ @@converter_classes[format]
46
+ rescue LoadError
47
+ STDERR.puts("#{format} is not a known format for the topology file")
48
+ exit(1)
49
+ end
50
+
51
+ def self.convert(format, data)
52
+ converter = self.converter(format)
53
+ converter.convert(data)
54
+ end
55
+
56
+ attr_accessor :input
57
+
58
+ def initialize(format, data = nil)
59
+ @format = format
60
+ @input = data
61
+ @output = { 'nodes' => [] }
62
+ end
63
+
64
+ # Other format converters should override the following methods
65
+ register_converter('default', name)
66
+ register_converter('topo', name)
67
+
68
+ def convert(data = nil)
69
+ @input = data if data
70
+ @output = @input
71
+ @output
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,123 @@
1
+ #
2
+ # Author:: Christine Draper (<christine_draper@thirdwaveinsights.com>)
3
+ # Copyright:: Copyright (c) 2015 ThirdWave Insights LLC
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ #
20
+ # Converts data in a given format into V2 topo JSON format
21
+ #
22
+ require 'chef/mixin/deep_merge'
23
+ require 'chef/topo/converter'
24
+
25
+ class Chef
26
+ module Topo
27
+ # Convert V1 topology JSON to V2
28
+ class TopoV1Converter < Chef::Topo::Converter
29
+ PRIORITIES = %w(default force_default normal override force_override)
30
+
31
+ register_converter('topo_v1', name)
32
+
33
+ def convert(data = nil)
34
+ @input = data if data
35
+ @output = @input.dup
36
+ determine_strategy
37
+ @output['nodes'] = []
38
+ @input['nodes'].each do |n|
39
+ @output['nodes'] << convert_node(n)
40
+ end
41
+ cleanup_output
42
+ end
43
+
44
+ def convert_node(n)
45
+ combined = merge_cookbook_attrs(n)
46
+ type = node_type(n)
47
+ combined['node_type'] ||= type if type
48
+ combined
49
+ end
50
+
51
+ def determine_strategy
52
+ @output['strategy'] = 'direct_to_node'
53
+ cookbooks = @input['cookbook_attributes']
54
+ return unless cookbooks && cookbooks.length > 0
55
+
56
+ cookbooks.each do |cb|
57
+ cond = cb['conditional'] || []
58
+ next unless !cond.empty? || PRIORITIES.any? { |k| cb.key?(k) }
59
+ via_cookbook_strategy(cb)
60
+ break
61
+ end
62
+ end
63
+
64
+ def via_cookbook_strategy(cb)
65
+ @output['strategy'] = 'via_cookbook'
66
+ @output['strategy_data'] = {
67
+ 'cookbook' => cb['cookbook'] || @output['name'],
68
+ 'filename' => cb['filename'] || 'attributes'
69
+ }
70
+ end
71
+
72
+ # move source[p] contents and merge into dest[p]
73
+ def merge_copy(dest, source)
74
+ PRIORITIES.reverse_each do |p|
75
+ if source.key?(p)
76
+ dest[p] = Chef::Mixin::DeepMerge.merge(source[p], dest[p])
77
+ end
78
+ end
79
+ end
80
+
81
+ # Combine cookbook attributes into node
82
+ def merge_cookbook_attrs(node)
83
+ cb_attr_array = @input['cookbook_attributes']
84
+ combined = node.dup
85
+ return combined unless cb_attr_array
86
+
87
+ # merge unqualified attributes into node
88
+ cb_attr_array.each do |cb_attrs|
89
+ merge_copy(combined, cb_attrs)
90
+
91
+ # find qualified attributes for node
92
+ cond = cb_attrs['conditional']
93
+ next unless cond
94
+
95
+ merge_copy_cond_attrs(combined, cond)
96
+ end
97
+
98
+ combined
99
+ end
100
+
101
+ def merge_copy_cond_attrs(combined, cond)
102
+ topo = (combined['normal'] || {})['topo'] || {}
103
+ cond.each do |cond_attrs|
104
+ if topo[cond_attrs['qualifier']] == cond_attrs['value']
105
+ merge_copy(combined, cond_attrs)
106
+ end
107
+ end
108
+ combined
109
+ end
110
+
111
+ def node_type(node)
112
+ return node['node_type'] if node['node_type']
113
+ return nil unless node['normal'] && node['normal']['topo']
114
+ node['normal']['topo']['node_type']
115
+ end
116
+
117
+ def cleanup_output
118
+ @output.delete('cookbook_attributes')
119
+ @output
120
+ end
121
+ end
122
+ end
123
+ end