knife-topo 0.0.3

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.
@@ -0,0 +1,178 @@
1
+ #
2
+ # Author:: Christine Draper (<christine_draper@thirdwaveinsights.com>)
3
+ # Copyright:: Copyright (c) 2014 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
+ require_relative 'topology_helper'
20
+ require 'chef/knife/cookbook_create'
21
+
22
+ class Chef
23
+ class Knife
24
+ class TopoCookbookCreate < Chef::Knife
25
+
26
+ deps do
27
+ Chef::Knife::CookbookCreate.load_deps
28
+ end
29
+
30
+ banner "knife topo cookbook create TOPOLOGY [ TOPOLOGY_FILE ] (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
+ # Make the base cookbook create options available on topo cookbook
38
+ self.options = (Chef::Knife::CookbookCreate.options).merge(self.options)
39
+
40
+ def initialize (args)
41
+ super
42
+ @cookbook_create_args = initialize_cmd_args(args, [ 'cookbook', 'create' ])
43
+
44
+ # All called commands need to accept union of options
45
+ Chef::Knife::CookbookCreate.options = options
46
+
47
+ end
48
+
49
+ def run
50
+ if !@name_args[0]
51
+ show_usage
52
+ ui.fatal("You must specify the name of a topology")
53
+ exit 1
54
+ end
55
+
56
+ @bag_name = topo_bag_name(config[:data_bag])
57
+ @topo_name = @name_args[0]
58
+ @topo_file = @name_args[1]
59
+
60
+ # Get the topology data from either the file or the server
61
+ if @topo_file
62
+ topologies = load_topologies(@topo_file)
63
+ index = topologies.find_index{ |t| t['name'] == @topo_name}
64
+ unless index
65
+ ui.fatal("Topology #{@topo_name} was not found in topology file #{@topo_file}")
66
+ exit(1)
67
+ end
68
+ topo = topologies[index]
69
+ else
70
+ unless topo = load_from_server(@bag_name, @topo_name )
71
+ ui.fatal("Topology #{@bag_name}/#{@topo_name} does not exist on the server - use 'knife topo create' first")
72
+ exit(1)
73
+ end
74
+ end
75
+
76
+ # create the topology cookbooks
77
+ attribute_cookbooks = topo['cookbook_attributes']
78
+ @failed = []
79
+ succeeded = 0
80
+ if attribute_cookbooks && attribute_cookbooks.length > 0
81
+ attribute_cookbooks.each do |cookbook_spec|
82
+ run_create_cookbook(cookbook_spec['cookbook'])
83
+ create_attr_file(@cookbook_path, cookbook_spec['cookbook'],
84
+ cookbook_spec['filename'] + ".rb", cookbook_spec)
85
+ succeeded += 1
86
+ end
87
+ else
88
+ ui.info "No cookbook attributes found for topology #{@topo_name}"
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ include Chef::Knife::TopologyHelper
95
+
96
+ def run_create_cookbook(cookbook_name)
97
+ @cookbook_create_args[2] = cookbook_name
98
+ begin
99
+ command = run_cmd(Chef::Knife::CookbookCreate, @cookbook_create_args)
100
+ rescue Exception => e
101
+ raise if Chef::Config[:verbosity] == 2
102
+ @failed << cookbook_name
103
+ ui.warn "create of cookbook #{cookbook_name} exited with error"
104
+ humanize_exception(e)
105
+ end
106
+
107
+ # Store the cookbook path for use later
108
+ @cookbook_path = File.expand_path(Array(command.config[:cookbook_path]).first)
109
+ @copyright = command.config[:cookbook_copyright] || "YOUR_COMPANY_NAME"
110
+
111
+ end
112
+
113
+ def create_attr_file(dir, cookbook_name, filename, attrs)
114
+
115
+ ui.info("** Creating attribute file #{filename}")
116
+
117
+ open(File.join(dir, cookbook_name, "attributes", filename), "w") do |file|
118
+ file.puts <<-EOH
119
+ #
120
+ # THIS FILE IS GENERATED BY THE KNIFE TOPO PLUGIN - MANUAL CHANGES WILL BE OVERWRITTEN
121
+ #
122
+ # Cookbook Name:: #{cookbook_name}
123
+ # Attribute File:: #{filename}
124
+ #
125
+ # Copyright #{Time.now.year}, #{@copyright}
126
+ #
127
+ EOH
128
+
129
+ # Print out attribute line
130
+ def print_attr(file, lhs, value1)
131
+ if value1.is_a?(Hash)
132
+ value1.each do |key, value2|
133
+ print_attr(file, "#{lhs}['#{key}']", value2)
134
+ end
135
+ else
136
+ file.write "#{lhs} = \"#{value1}\"\n"
137
+ end
138
+ end
139
+
140
+ # Print out attributes hashed by priority
141
+ def print_priority_attrs(file, attrs, indent=0)
142
+ %w(default force_default normal override force_override).each do |priority|
143
+ if attrs[priority]
144
+ lhs = ""
145
+ indent.times { |i| lhs += " " }
146
+ lhs += priority
147
+ print_attr(file, lhs, attrs[priority])
148
+ end
149
+ end
150
+ end
151
+
152
+ # Print out qualified attributes
153
+ def print_qualified_attr(file, qualifier, qualifier_hash)
154
+ qualifier_hash.each do |qualifier_value, qualified_attrs|
155
+ file.puts "if node['topo']['#{qualifier}'] == \"#{qualifier_value}\""
156
+ print_priority_attrs(file, qualified_attrs, 2)
157
+ file.puts "end"
158
+ end
159
+ end
160
+
161
+ # Process the attributes not needing qualification
162
+ print_priority_attrs(file, attrs)
163
+ file.puts
164
+
165
+ # Process attributes that need to be qualified (must appear after the above in cookbook)
166
+ %w(node_type).each do |qualifier|
167
+ if attrs[qualifier]
168
+ file.puts "# Attributes for specific #{qualifier}"
169
+ print_qualified_attr(file, qualifier, attrs[qualifier])
170
+ end
171
+ end
172
+
173
+ end
174
+ end
175
+
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,84 @@
1
+ #
2
+ # Author:: Christine Draper (<christine_draper@thirdwaveinsights.com>)
3
+ # Copyright:: Copyright (c) 2014 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
+ require 'chef/knife'
20
+ require_relative 'topology_helper'
21
+
22
+ ## only require if not already defined (to prevent warning about already initialized constants)
23
+ require 'chef/knife/cookbook_upload' if !defined? Chef::Knife::CookbookUpload
24
+
25
+ class Chef
26
+ class Knife
27
+ class TopoCookbookUpload < Chef::Knife
28
+
29
+ deps do
30
+ Chef::Knife::CookbookUpload.load_deps
31
+ end
32
+
33
+ banner "knife topo cookbook upload [ TOPOLOGY ] (options)"
34
+
35
+ option :data_bag,
36
+ :short => '-D DATA_BAG',
37
+ :long => "--data-bag DATA_BAG",
38
+ :description => "The data bag the topologies are stored in"
39
+
40
+ # Make called command options available
41
+ self.options = (Chef::Knife::CookbookUpload.options).merge(self.options)
42
+
43
+ def initialize (args)
44
+ super
45
+ @topo_upload_args = initialize_cmd_args(args, [ 'cookbook', 'upload' ])
46
+
47
+ # All called commands need to accept union of options
48
+ Chef::Knife::CookbookUpload.options = options
49
+ end
50
+
51
+ def run
52
+ if !@name_args[0]
53
+ show_usage
54
+ ui.fatal("You must specify the name of a topology")
55
+ exit 1
56
+ end
57
+
58
+ bag_name = topo_bag_name(config[:data_bag])
59
+ topo_name = @name_args[0]
60
+
61
+ # Load the topology data
62
+ unless topo = load_from_file(bag_name, topo_name )
63
+ ui.fatal("Topology file #{topologies_path}/#{bag_name}/#{topo_name}.json not found - use 'knife topo import' first")
64
+ exit(1)
65
+ end
66
+
67
+ # Run cookbook upload command on the topology cookbooks
68
+ if topo['cookbook_attributes'].length > 0
69
+ argPos = 2
70
+ topo['cookbook_attributes'].each do |entry|
71
+ @topo_upload_args[argPos] = entry['cookbook']
72
+ argPos += 1
73
+ end
74
+ run_cmd(Chef::Knife::CookbookUpload, @topo_upload_args)
75
+ else
76
+ ui.info("No cookbooks found for topology #{topo_name}.")
77
+ end
78
+ end
79
+
80
+ include Chef::Knife::TopologyHelper
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,110 @@
1
+ #
2
+ # Author:: Christine Draper (<christine_draper@thirdwaveinsights.com>)
3
+ # Copyright:: Copyright (c) 2014 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
+ require 'chef/knife'
20
+ require_relative 'topology_helper'
21
+ require_relative 'topo_bootstrap'
22
+ require_relative 'topo_cookbook_upload'
23
+
24
+ class Chef
25
+ class Knife
26
+ class TopoCreate < Chef::Knife
27
+
28
+ deps do
29
+ Chef::Knife::TopoCookbookUpload.load_deps
30
+ Chef::Knife::TopoBootstrap.load_deps
31
+ end
32
+
33
+ banner "knife topo create TOPOLOGY (options)"
34
+
35
+ option :data_bag,
36
+ :short => '-D DATA_BAG',
37
+ :long => "--data-bag DATA_BAG",
38
+ :description => "The data bag the topologies are stored in"
39
+
40
+ option :bootstrap,
41
+ :long => "--bootstrap",
42
+ :description => "Whether to bootstrap newly created nodes",
43
+ :boolean => false
44
+
45
+ option :no_upload,
46
+ :long => "--no-upload",
47
+ :description => "Do not upload cookbooks",
48
+ :boolean => false
49
+
50
+ # Make called command options available
51
+ opts = self.options
52
+ self.options = (Chef::Knife::TopoBootstrap.options).merge(Chef::Knife::TopoCookbookUpload.options)
53
+ self.options.merge!(opts)
54
+
55
+ def initialize (args)
56
+ super
57
+ @topo_bootstrap_args = initialize_cmd_args(args, [ 'topo', 'bootstrap', @name_args[0] ])
58
+ @topo_upload_args = initialize_cmd_args(args, [ 'topo', 'cookbook', 'upload', @name_args[0] ])
59
+
60
+ # All called commands need to accept union of options
61
+ Chef::Knife::TopoBootstrap.options = options
62
+ Chef::Knife::TopoCookbookUpload.options = options
63
+ end
64
+
65
+ def run
66
+ if !@name_args[0]
67
+ show_usage
68
+ ui.fatal("You must specify the name of a topology")
69
+ exit 1
70
+ end
71
+
72
+ bag_name = topo_bag_name(config[:data_bag])
73
+ topo_name = @name_args[0]
74
+
75
+ # Load the topology data & create the topology bag
76
+ unless topo = load_from_file(bag_name, topo_name )
77
+ ui.fatal("Topology file #{topologies_path}/#{bag_name}/#{topo_name}.json not found - use 'knife topo import' first")
78
+ exit(1)
79
+ end
80
+ @data_bag = create_bag(bag_name)
81
+
82
+ # Add topology item to the data bag on the server
83
+ begin
84
+ topo.create
85
+ rescue Net::HTTPServerException => e
86
+ raise unless e.to_s =~ /^409/
87
+ ui.confirm("Topology already exists - do you want to re-create it", true, false)
88
+ end
89
+
90
+ # make sure env and cookbooks are in place
91
+ check_chef_env(topo['chef_environment']) if topo['chef_environment']
92
+ upload_cookbooks(@topo_upload_args) if (!config[:no_upload])
93
+
94
+ # setup the actual nodes on the server
95
+ create_or_update_nodes(topo)
96
+ ui.info("Topology created")
97
+
98
+ # if bootstrap is specified, run the bootstrap command
99
+ run_cmd(Chef::Knife::TopoBootstrap, @topo_bootstrap_args) if config[:bootstrap]
100
+
101
+ end
102
+
103
+
104
+
105
+
106
+ include Chef::Knife::TopologyHelper
107
+
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,137 @@
1
+ #
2
+ # Author:: Christine Draper (<christine_draper@thirdwaveinsights.com>)
3
+ # Copyright:: Copyright (c) 2014 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
+ require 'chef/knife'
20
+
21
+ require_relative 'topology_helper'
22
+
23
+ # NOTE: This command exports to stdout - do not add ui messages (other than fatal) to this command
24
+
25
+ class Chef
26
+ class Knife
27
+ class TopoExport < Chef::Knife
28
+
29
+ deps do
30
+ end
31
+
32
+ banner "knife topo export [ TOPOLOGY [ NODE ... ]] (options)"
33
+
34
+ option :data_bag,
35
+ :short => '-D DATA_BAG',
36
+ :long => "--data-bag DATA_BAG",
37
+ :description => "The data bag the topologies are stored in"
38
+
39
+ def run
40
+
41
+ @bag_name = topo_bag_name(config[:data_bag])
42
+
43
+ @topo_name = @name_args[0]
44
+ @node_names = @name_args[1..-1]
45
+
46
+ if @topo_name
47
+ if topo = load_from_server(@bag_name, @topo_name)
48
+ export = topo.raw_data
49
+ else
50
+ export = empty_topology
51
+ end
52
+
53
+ # merge in data for nodes that user explicitly specified
54
+ @node_names.each do |node_name|
55
+ merge_node_properties!(export['nodes'], node_name)
56
+ end
57
+
58
+ else
59
+ # export all topologies
60
+ export = []
61
+ if dbag = load_from_server(@bag_name)
62
+ dbag.keys.each do |topo_name|
63
+ if topo = load_from_server(@bag_name, topo_name)
64
+ export << topo.raw_data
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ output(Chef::JSONCompat.to_json_pretty(export))
71
+ end
72
+
73
+ # give user a template to get started
74
+ def empty_topology
75
+ {
76
+ "id" => @topo_name || "topo1",
77
+ "name" => @topo_name || "topo1",
78
+ "chef_environment" => "_default",
79
+ "tags" => [ ],
80
+ "normal" => { },
81
+ "nodes" => [
82
+ "node1" => empty_node("node1")
83
+ ],
84
+ "cookbook_attributes" => [{
85
+ "cookbook" => @topo_name || "topo1",
86
+ "filename" => "topology"
87
+ }]
88
+ }
89
+ end
90
+
91
+ def empty_node(name)
92
+ {
93
+ "name" => name,
94
+ "run_list" => [],
95
+ "ssh_host" => name,
96
+ "ssh_port" => "22",
97
+ "normal" => {},
98
+ "tags" => []
99
+ }
100
+ end
101
+
102
+ # get actual node properties for export
103
+ def node_export (node_name)
104
+ begin
105
+ node = Chef::Node.load(node_name).to_hash
106
+ rescue Net::HTTPServerException => e
107
+ raise unless e.to_s =~ /^404/
108
+ node = empty_node(node_name)
109
+ end
110
+ props = %w{name chef_environment normal run_list recipes}
111
+ node_data = {}
112
+
113
+ props.each do |prop|
114
+ node_data[prop] = node[prop] if node.has_key?(prop)
115
+ end
116
+
117
+ node_data[tags] = node_data[normal][tags]
118
+
119
+ node_data
120
+ end
121
+
122
+ # merge hash properties with the actual node properties
123
+ def merge_node_properties!(nodes, node_name)
124
+ # find out if the node is already in the array
125
+ node = nodes.find{|n| n["name"] == node_name }
126
+ if node
127
+ node.merge!(node_export(node_name))
128
+ else
129
+ nodes.push(node_export(node_name))
130
+ end
131
+ end
132
+
133
+ include Chef::Knife::TopologyHelper
134
+
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,100 @@
1
+ #
2
+ # Author:: Christine Draper (<christine_draper@thirdwaveinsights.com>)
3
+ # Copyright:: Copyright (c) 2014 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
+ require 'chef/knife'
20
+
21
+ require_relative 'topology_helper'
22
+ require_relative 'topo_cookbook_create'
23
+
24
+ class Chef
25
+ class Knife
26
+ class TopoImport < Chef::Knife
27
+
28
+ deps do
29
+ Chef::Knife::TopoCookbookCreate.load_deps
30
+ end
31
+
32
+ banner "knife topo import [ TOPOLOGY_FILE [ TOPOLOGY ... ]] (options)"
33
+
34
+ option :data_bag,
35
+ :short => '-D DATA_BAG',
36
+ :long => "--data-bag DATA_BAG",
37
+ :description => "The data bag to store the topologies in"
38
+
39
+ # Make called command options available
40
+ self.options = Chef::Knife::TopoCookbookCreate.options.merge(self.options)
41
+
42
+ def initialize (args)
43
+ super
44
+ @topo_cookbook_args = initialize_cmd_args(args, [ 'topo', 'cookbook', '', '' ])
45
+
46
+ # All called commands need to accept union of options
47
+ Chef::Knife::TopoCookbookCreate.options = options
48
+ end
49
+
50
+ def run
51
+
52
+ # load data from the topologies file
53
+ topo_file = @name_args[0] || 'topology.json'
54
+ topologies = load_topologies(topo_file)
55
+ bag_name = topo_bag_name(config[:data_bag])
56
+ topo_names = @name_args[1..-1] if @name_args[1]
57
+
58
+ # make sure the topology bag directory exists
59
+ path = File.join(topologies_path, bag_name)
60
+ FileUtils.mkdir_p(path)
61
+
62
+ topologies.each do |topo_data|
63
+
64
+ topo_name = topo_data['name'] || topo_data['id']
65
+ topo_data['id'] ||= topo_name
66
+
67
+ # check against specific topology list
68
+ if topo_names
69
+ if topo_names.include?(topo_name)
70
+ topo_names.delete(topo_name)
71
+ else
72
+ next
73
+ end
74
+ end
75
+
76
+ # write the databag for this topology
77
+ path = File.join(topologies_path, bag_name, topo_name + '.json')
78
+ File.open(path,"w") do |f|
79
+ f.write(Chef::JSONCompat.to_json_pretty(topo_data))
80
+ f.close()
81
+ ui.info "Imported topology #{topo_name} into #{path}"
82
+ end
83
+
84
+ # run topo cookbook to generate the cookbooks for this topology
85
+ @topo_cookbook_args[2] = topo_name
86
+ @topo_cookbook_args[3] = topo_file
87
+ run_cmd(Chef::Knife::TopoCookbookCreate, @topo_cookbook_args)
88
+
89
+ end
90
+
91
+ ui.info "Did not find topologies #{topo_names.join(', ')} in the exchange file" if topo_names && topo_names.length > 0
92
+ ui.info "Import finished"
93
+
94
+ end
95
+
96
+ include Chef::Knife::TopologyHelper
97
+
98
+ end
99
+ end
100
+ end