blob: 5058d17b224ad86b34f3edfefdde3fa0152acc2c [file] [log] [blame]
David K. Bainbridgeb5415042016-05-13 17:06:10 -07001package main
2
3import (
David K. Bainbridgeefa951d2016-05-26 10:54:25 -07004 "encoding/json"
David K. Bainbridgeb5415042016-05-13 17:06:10 -07005 "fmt"
David K. Bainbridgeb5415042016-05-13 17:06:10 -07006 "net/url"
David K. Bainbridgeefa951d2016-05-26 10:54:25 -07007 "os/exec"
David K. Bainbridgeb5415042016-05-13 17:06:10 -07008 "regexp"
9 "strconv"
10 "strings"
David K. Bainbridgeefa951d2016-05-26 10:54:25 -070011 "time"
David K. Bainbridgeb5415042016-05-13 17:06:10 -070012
13 maas "github.com/juju/gomaasapi"
14)
15
16// Action how to get from there to here
17type Action func(*maas.MAASObject, MaasNode, ProcessingOptions) error
18
19// Transition the map from where i want to be from where i might be
20type Transition struct {
21 Target string
22 Current string
23 Using Action
24}
25
David K. Bainbridge6ea57c12016-06-06 23:29:12 -070026type Power struct {
27 Name string `json:"name"`
28 MacAddress string `json:"mac_address"`
29 PowerPassword string `json:"power_password"`
30 PowerAddress string `json:"power_address"`
31}
32
David K. Bainbridgeb5415042016-05-13 17:06:10 -070033// ProcessingOptions used to determine on what hosts to operate
34type ProcessingOptions struct {
35 Filter struct {
36 Zones struct {
37 Include []string
38 Exclude []string
39 }
40 Hosts struct {
41 Include []string
42 Exclude []string
43 }
44 }
David K. Bainbridge6ea57c12016-06-06 23:29:12 -070045 Mappings map[string]interface{}
David K. Bainbridge6ea57c12016-06-06 23:29:12 -070046 Preview bool
47 AlwaysRename bool
David K. Bainbridge068e87d2016-06-30 13:53:19 -070048 Provisioner Provisioner
David K. Bainbridge6ea57c12016-06-06 23:29:12 -070049 ProvisionURL string
50 ProvisionTTL time.Duration
51 PowerHelper string
52 PowerHelperUser string
53 PowerHelperHost string
David K. Bainbridgeb5415042016-05-13 17:06:10 -070054}
55
56// Transitions the actual map
57//
58// Currently this is a hand compiled / optimized "next step" table. This should
59// really be generated from the state machine chart input. Once this has been
60// accomplished you should be able to determine the action to take given your
61// target state and your current state.
David K. Bainbridgeefa951d2016-05-26 10:54:25 -070062var Transitions = map[string]map[string][]Action{
David K. Bainbridgeb5415042016-05-13 17:06:10 -070063 "Deployed": {
David K. Bainbridgeefa951d2016-05-26 10:54:25 -070064 "New": []Action{Reset, Commission},
65 "Deployed": []Action{Provision, Done},
66 "Ready": []Action{Reset, Aquire},
67 "Allocated": []Action{Reset, Deploy},
68 "Retired": []Action{Reset, AdminState},
69 "Reserved": []Action{Reset, AdminState},
70 "Releasing": []Action{Reset, Wait},
71 "DiskErasing": []Action{Reset, Wait},
72 "Deploying": []Action{Reset, Wait},
73 "Commissioning": []Action{Reset, Wait},
74 "Missing": []Action{Reset, Fail},
75 "FailedReleasing": []Action{Reset, Fail},
76 "FailedDiskErasing": []Action{Reset, Fail},
77 "FailedDeployment": []Action{Reset, Fail},
78 "Broken": []Action{Reset, Fail},
79 "FailedCommissioning": []Action{Reset, Fail},
David K. Bainbridgeb5415042016-05-13 17:06:10 -070080 },
81}
82
83const (
84 // defaultStateMachine Would be nice to drive from a graph language
85 defaultStateMachine string = `
David K. Bainbridged9b966f2016-05-31 13:30:05 -070086 (New)->(Commissioning)
David K. Bainbridgeb5415042016-05-13 17:06:10 -070087 (Commissioning)->(FailedCommissioning)
88 (FailedCommissioning)->(New)
89 (Commissioning)->(Ready)
90 (Ready)->(Deploying)
91 (Ready)->(Allocated)
92 (Allocated)->(Deploying)
93 (Deploying)->(Deployed)
94 (Deploying)->(FailedDeployment)
95 (FailedDeployment)->(Broken)
96 (Deployed)->(Releasing)
97 (Releasing)->(FailedReleasing)
98 (FailedReleasing)->(Broken)
99 (Releasing)->(DiskErasing)
100 (DiskErasing)->(FailedEraseDisk)
101 (FailedEraseDisk)->(Broken)
102 (Releasing)->(Ready)
103 (DiskErasing)->(Ready)
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700104 (Broken)->(Ready)
David K. Bainbridged9b966f2016-05-31 13:30:05 -0700105 (Deployed)->(Provisioning)
106 (Provisioning)->|a|
107 |a|->(Execute Script)->|b|
108 |a|->(HTTP PUT)
109 (HTTP PUT)->(HTTP GET)
110 (HTTP GET)->(HTTP GET)
111 (HTTP GET)->|b|
112 |b|->(Provisioned)
113 |b|->(ProvisionError)
114 (ProvisionError)->(Provisioning)`
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700115)
116
117// updateName - changes the name of the MAAS node based on the configuration file
118func updateNodeName(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
119 macs := node.MACs()
120
121 // Get current node name and strip off domain name
122 current := node.Hostname()
123 if i := strings.IndexRune(current, '.'); i != -1 {
124 current = current[:i]
125 }
126 for _, mac := range macs {
127 if entry, ok := options.Mappings[mac]; ok {
128 if name, ok := entry.(map[string]interface{})["hostname"]; ok && current != name.(string) {
129 nodesObj := client.GetSubObject("nodes")
130 nodeObj := nodesObj.GetSubObject(node.ID())
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700131 log.Infof("RENAME '%s' to '%s'\n", node.Hostname(), name.(string))
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700132
133 if !options.Preview {
134 nodeObj.Update(url.Values{"hostname": []string{name.(string)}})
135 }
136 }
137 }
138 }
139 return nil
140}
141
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700142// Reset we are at the target state, nothing to do
143var Reset = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700144 log.Debugf("RESET: %s", node.Hostname())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700145
146 if options.AlwaysRename {
147 updateNodeName(client, node, options)
148 }
149
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700150 options.Provisioner.Clear(node.ID())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700151
152 return nil
153}
154
155// Provision we are at the target state, nothing to do
156var Provision = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700157 log.Debugf("CHECK PROVISION: %s", node.Hostname())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700158
159 if options.AlwaysRename {
160 updateNodeName(client, node, options)
161 }
162
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700163 record, err := options.Provisioner.Get(node.ID())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700164 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700165 log.Warningf("unable to retrieve provisioning state of node '%s' : %s", node.Hostname(), err)
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700166 } else if record == nil || record.Status == Failed {
167 var label string
168 if record == nil {
169 label = "NotFound"
170 } else {
171 label = record.Status.String()
172 }
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700173 log.Debugf("Current state of node '%s' is '%s'", node.Hostname(), label)
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700174 ips := node.IPs()
175 ip := ""
176 if len(ips) > 0 {
177 ip = ips[0]
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700178 }
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700179 macs := node.MACs()
180 mac := ""
181 if len(macs) > 0 {
182 mac = macs[0]
183 }
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700184 log.Debugf("POSTing '%s' (%s) to '%s'", node.Hostname(), node.ID(), options.ProvisionURL)
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700185 err = options.Provisioner.Provision(&ProvisionRequest{
186 Id: node.ID(),
187 Name: node.Hostname(),
188 Ip: ip,
189 Mac: mac,
190 })
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700191
192 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700193 log.Errorf("unable to provision '%s' (%s) : %s", node.ID, node.Hostname(), err)
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700194 }
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700195
196 } else if options.ProvisionTTL > 0 &&
197 record.Status == Running && time.Since(time.Unix(record.Timestamp, 0)) > options.ProvisionTTL {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700198 log.Errorf("Provisioning of node '%s' has passed provisioning TTL of '%v'",
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700199 node.Hostname(), options.ProvisionTTL)
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700200 options.Provisioner.Clear(node.ID())
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700201 } else {
202 log.Debugf("Not invoking provisioning for '%s', current state is '%s'", node.Hostname(),
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700203 record.Status.String())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700204 }
205
206 return nil
207}
208
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700209// Done we are at the target state, nothing to do
210var Done = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
211 // As devices are normally in the "COMPLETED" state we don't want to
212 // log this fact unless we are in verbose mode. I suspect it would be
213 // nice to log it once when the device transitions from a non COMPLETE
214 // state to a complete state, but that would require keeping state.
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700215 log.Debugf("COMPLETE: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700216
217 if options.AlwaysRename {
218 updateNodeName(client, node, options)
219 }
220
221 return nil
222}
223
224// Deploy cause a node to deploy
225var Deploy = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700226 log.Infof("DEPLOY: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700227
228 if options.AlwaysRename {
229 updateNodeName(client, node, options)
230 }
231
232 if !options.Preview {
233 nodesObj := client.GetSubObject("nodes")
234 myNode := nodesObj.GetSubObject(node.ID())
235 // Start the node with the trusty distro. This should really be looked up or
236 // a parameter default
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700237 _, err := myNode.CallPost("start", url.Values{"distro_series": []string{"trusty"}})
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700238 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700239 log.Errorf("DEPLOY '%s' : '%s'", node.Hostname(), err)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700240 return err
241 }
242 }
243 return nil
244}
245
246// Aquire aquire a machine to a specific operator
247var Aquire = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700248 log.Infof("AQUIRE: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700249 nodesObj := client.GetSubObject("nodes")
250
251 if options.AlwaysRename {
252 updateNodeName(client, node, options)
253 }
254
255 if !options.Preview {
256 // With a new version of MAAS we have to make sure the node is linked
257 // to the subnet vid DHCP before we move to the Aquire state. To do this
258 // We need to unlink the interface to the subnet and then relink it.
259 //
260 // Iterate through all the interfaces on the node, searching for ones
261 // that are valid and not DHCP and move them to DHCP
262 ifcsObj := client.GetSubObject("nodes").GetSubObject(node.ID()).GetSubObject("interfaces")
263 ifcsListObj, err := ifcsObj.CallGet("", url.Values{})
264 if err != nil {
265 return err
266 }
267
268 ifcsArray, err := ifcsListObj.GetArray()
269 if err != nil {
270 return err
271 }
272
273 for _, ifc := range ifcsArray {
274 ifcMap, err := ifc.GetMap()
275 if err != nil {
276 return err
277 }
278
279 // Iterate over the links assocated with the interface, looking for
280 // links with a subnect as well as a mode of "auto"
281 links, ok := ifcMap["links"]
282 if ok {
283 linkArray, err := links.GetArray()
284 if err != nil {
285 return err
286 }
287
288 for _, link := range linkArray {
289 linkMap, err := link.GetMap()
290 if err != nil {
291 return err
292 }
293 subnet, ok := linkMap["subnet"]
294 if ok {
295 subnetMap, err := subnet.GetMap()
296 if err != nil {
297 return err
298 }
299
300 val, err := linkMap["mode"].GetString()
301 if err != nil {
302 return err
303 }
304
305 if val == "auto" {
306 // Found one we like, so grab the subnet from the data and
307 // then relink this as DHCP
308 cidr, err := subnetMap["cidr"].GetString()
309 if err != nil {
310 return err
311 }
312
313 fifcID, err := ifcMap["id"].GetFloat64()
314 if err != nil {
315 return err
316 }
317 ifcID := strconv.Itoa(int(fifcID))
318
319 flID, err := linkMap["id"].GetFloat64()
320 if err != nil {
321 return err
322 }
323 lID := strconv.Itoa(int(flID))
324
325 ifcObj := ifcsObj.GetSubObject(ifcID)
326 _, err = ifcObj.CallPost("unlink_subnet", url.Values{"id": []string{lID}})
327 if err != nil {
328 return err
329 }
330 _, err = ifcObj.CallPost("link_subnet", url.Values{"mode": []string{"DHCP"}, "subnet": []string{cidr}})
331 if err != nil {
332 return err
333 }
334 }
335 }
336 }
337 }
338 }
339 _, err = nodesObj.CallPost("acquire",
340 url.Values{"name": []string{node.Hostname()}})
341 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700342 log.Errorf("AQUIRE '%s' : '%s'", node.Hostname(), err)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700343 return err
344 }
345 }
346 return nil
347}
348
349// Commission cause a node to be commissioned
350var Commission = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
351 updateNodeName(client, node, options)
352
353 // Need to understand the power state of the node. We only want to move to "Commissioning" if the node
354 // power is off. If the node power is not off, then turn it off.
355 state := node.PowerState()
356 switch state {
357 case "on":
358 // Attempt to turn the node off
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700359 log.Infof("POWER DOWN: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700360 if !options.Preview {
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700361 //POST /api/1.0/nodes/{system_id}/ op=stop
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700362 nodesObj := client.GetSubObject("nodes")
363 nodeObj := nodesObj.GetSubObject(node.ID())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700364 _, err := nodeObj.CallPost("stop", url.Values{"stop_mode": []string{"soft"}})
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700365 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700366 log.Errorf("Commission '%s' : changing power start to off : '%s'", node.Hostname(), err)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700367 }
368 return err
369 }
370 break
371 case "off":
372 // We are off so move to commissioning
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700373 log.Infof("COMISSION: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700374 if !options.Preview {
375 nodesObj := client.GetSubObject("nodes")
376 nodeObj := nodesObj.GetSubObject(node.ID())
377
378 updateNodeName(client, node, options)
379
380 _, err := nodeObj.CallPost("commission", url.Values{})
381 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700382 log.Errorf("Commission '%s' : '%s'", node.Hostname(), err)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700383 }
384 return err
385 }
386 break
387 default:
388 // We are in a state from which we can't move forward.
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700389 log.Warningf("%s has invalid power state '%s'", node.Hostname(), state)
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700390
391 // If a power helper script is set, we have an unknown power state, and
392 // we have not power type then attempt to use the helper script to discover
393 // and set the power settings
394 if options.PowerHelper != "" && node.PowerType() == "" {
395 cmd := exec.Command(options.PowerHelper,
396 append([]string{options.PowerHelperUser, options.PowerHelperHost},
397 node.MACs()...)...)
398 stdout, err := cmd.Output()
399 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700400 log.Errorf("Failed while executing power helper script '%s' : %s",
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700401 options.PowerHelper, err)
402 return err
403 }
404 power := Power{}
405 err = json.Unmarshal(stdout, &power)
406 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700407 log.Errorf("Failed to parse output of power helper script '%s' : %s",
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700408 options.PowerHelper, err)
409 return err
410 }
411 switch power.Name {
412 case "amt":
413 params := map[string]string{
414 "mac_address": power.MacAddress,
415 "power_pass": power.PowerPassword,
416 "power_address": power.PowerAddress,
417 }
418 node.UpdatePowerParameters(power.Name, params)
419 default:
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700420 log.Warningf("Unsupported power type discovered '%s'", power.Name)
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700421 }
422 }
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700423 break
424 }
425 return nil
426}
427
428// Wait a do nothing state, while work is being done
429var Wait = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700430 log.Infof("WAIT: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700431 return nil
432}
433
434// Fail a state from which we cannot, currently, automatically recover
435var Fail = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700436 log.Infof("FAIL: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700437 return nil
438}
439
440// AdminState an administrative state from which we should make no automatic transition
441var AdminState = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700442 log.Infof("ADMIN: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700443 return nil
444}
445
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700446func findActions(target string, current string) ([]Action, error) {
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700447 targets, ok := Transitions[target]
448 if !ok {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700449 log.Warningf("unable to find transitions to target state '%s'", target)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700450 return nil, fmt.Errorf("Could not find transition to target state '%s'", target)
451 }
452
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700453 actions, ok := targets[current]
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700454 if !ok {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700455 log.Warningf("unable to find transition from current state '%s' to target state '%s'",
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700456 current, target)
457 return nil, fmt.Errorf("Could not find transition from current state '%s' to target state '%s'",
458 current, target)
459 }
460
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700461 return actions, nil
462}
463
464// ProcessActions
465func ProcessActions(actions []Action, client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
466 var err error
467 for _, action := range actions {
468 if err = action(client, node, options); err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700469 log.Errorf("Error while processing action for node '%s' : %s",
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700470 node.Hostname(), err)
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700471 break
472 }
473 }
474 return err
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700475}
476
477// ProcessNode something
478func ProcessNode(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
479 substatus, err := node.GetInteger("substatus")
480 if err != nil {
481 return err
482 }
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700483 actions, err := findActions("Deployed", MaasNodeStatus(substatus).String())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700484 if err != nil {
485 return err
486 }
487
488 if options.Preview {
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700489 ProcessActions(actions, client, node, options)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700490 } else {
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700491 go ProcessActions(actions, client, node, options)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700492 }
493 return nil
494}
495
496func buildFilter(filter []string) ([]*regexp.Regexp, error) {
497
498 results := make([]*regexp.Regexp, len(filter))
499 for i, v := range filter {
500 r, err := regexp.Compile(v)
501 if err != nil {
502 return nil, err
503 }
504 results[i] = r
505 }
506 return results, nil
507}
508
509func matchedFilter(include []*regexp.Regexp, target string) bool {
510 for _, e := range include {
511 if e.MatchString(target) {
512 return true
513 }
514 }
515 return false
516}
517
518// ProcessAll something
519func ProcessAll(client *maas.MAASObject, nodes []MaasNode, options ProcessingOptions) []error {
520 errors := make([]error, len(nodes))
521 includeHosts, err := buildFilter(options.Filter.Hosts.Include)
522 if err != nil {
523 log.Fatalf("[error] invalid regular expression for include filter '%s' : %s", options.Filter.Hosts.Include, err)
524 }
525
526 includeZones, err := buildFilter(options.Filter.Zones.Include)
527 if err != nil {
528 log.Fatalf("[error] invalid regular expression for include filter '%v' : %s", options.Filter.Zones.Include, err)
529 }
530
531 for i, node := range nodes {
532 // For hostnames we always match on an empty filter
533 if len(includeHosts) >= 0 && matchedFilter(includeHosts, node.Hostname()) {
534
535 // For zones we don't match on an empty filter
536 if len(includeZones) >= 0 && matchedFilter(includeZones, node.Zone()) {
537 err := ProcessNode(client, node, options)
538 if err != nil {
539 errors[i] = err
540 } else {
541 errors[i] = nil
542 }
543 } else {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700544 log.Debugf("ignoring node '%s' as its zone '%s' didn't match include zone name filter '%v'",
545 node.Hostname(), node.Zone(), options.Filter.Zones.Include)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700546 }
547 } else {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700548 log.Debugf("ignoring node '%s' as it didn't match include hostname filter '%v'",
549 node.Hostname(), options.Filter.Hosts.Include)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700550 }
551 }
552 return errors
553}