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.
- checksums.yaml +4 -4
- data/README.md +125 -148
- data/knife-topo.gemspec +12 -12
- data/lib/chef/knife/topo/bootstrap_helper.rb +75 -0
- data/lib/chef/knife/topo/command_helper.rb +71 -0
- data/lib/chef/knife/topo/consts.rb +4 -0
- data/lib/chef/knife/topo/loader.rb +137 -0
- data/lib/chef/knife/topo/node_update_helper.rb +111 -0
- data/lib/chef/knife/topo/processor.rb +76 -0
- data/lib/chef/knife/topo/processor/via_cookbook.rb +118 -0
- data/lib/chef/knife/topo/processor/via_cookbook_print.rb +97 -0
- data/lib/chef/knife/topo/version.rb +3 -4
- data/lib/chef/knife/topo_bootstrap.rb +107 -79
- data/lib/chef/knife/topo_cookbook_create.rb +41 -144
- data/lib/chef/knife/topo_cookbook_upload.rb +40 -54
- data/lib/chef/knife/topo_create.rb +120 -132
- data/lib/chef/knife/topo_delete.rb +67 -65
- data/lib/chef/knife/topo_export.rb +108 -133
- data/lib/chef/knife/topo_import.rb +62 -76
- data/lib/chef/knife/topo_list.rb +18 -25
- data/lib/chef/knife/topo_search.rb +61 -61
- data/lib/chef/knife/topo_update.rb +25 -110
- data/lib/chef/topo/converter.rb +75 -0
- data/lib/chef/topo/converter/topo_v1.rb +123 -0
- data/lib/chef/topology.rb +137 -0
- metadata +13 -3
- data/lib/chef/knife/topology_helper.rb +0 -366
@@ -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
|
-
|
23
|
-
|
24
|
-
|
21
|
+
module KnifeTopo
|
22
|
+
# knife topo search
|
23
|
+
class TopoSearch < Chef::Knife::Search
|
24
|
+
deps do
|
25
|
+
end
|
25
26
|
|
26
|
-
|
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
|
-
|
39
|
-
|
29
|
+
option(
|
30
|
+
:topo,
|
31
|
+
long: '--topo TOPOLOGY',
|
32
|
+
description: 'Restrict search to nodes in the specified topology'
|
33
|
+
)
|
40
34
|
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
46
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
20
|
+
require 'chef/knife/topo_create'
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
111
|
-
|
112
|
-
|
35
|
+
def run
|
36
|
+
validate_args
|
37
|
+
load_topo_from_server_or_exit(@topo_name)
|
38
|
+
update_topo
|
113
39
|
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
120
|
-
|
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
|
-
|
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
|