-
Notifications
You must be signed in to change notification settings - Fork 16
/
rtf
executable file
·805 lines (708 loc) · 27.9 KB
/
rtf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
#!/usr/bin/python
"""
Rudder test framework
Usage:
rtf platform list
rtf platform status <platform>
rtf platform setup <platform> [<version>]
rtf platform destroy <platform>
rtf platform share <platform>
rtf platform export <platform>
rtf platform update-rudder <platform> <version>
rtf platform update-os <platform> <version>
rtf host list <platform>
rtf host update-rudder <host> <version>
rtf host update-os <host> <version>
rtf scenario list
rtf scenario env <platform>
rtf scenario run <platform> <scenario> [--no-finally] [--filter=<test1>,<test2>,...] [--format=<format>]
rtf test from-rule <platform> <uuid> <test_name> [--with-scenario]
Options:
--no-finally Do not run tests tagged FINALLY in the scenario, to avoid coming back to initial state
--filter=... Only run provided test list from scenario
--format=<format> Output format to use (progress, documentation, html or json) [Default: documentation]
--create-scenario Generate a sample scenario file to use this test independantly
"""
from __future__ import print_function
import argparse
import json
import os
import tempfile
import shutil
import copy
import re
import scenario.lib
import importlib
import docopt
import pexpect
import signal
from subprocess import check_output, CalledProcessError
from pprint import pprint
# Hack to import rudder lib, remove, some day ...
import sys
sys.path.insert(0,"./rudder-api-client/lib.python")
from rudder import RudderEndPoint, RudderError
class Host:
"""Generic Host
Inherit to create host managed by a specific provider
Look at Vagrant class for the list of methods to implement
"""
def __init__(self, host_info):
self.info = host_info
### Vagrant specific code ###
def host_lines(platform, hosts, pf_id):
""" Return a Vagrantfile host line """
pf = platform.split('.')[0]
lines=[]
i=1
host_list = sorted(hosts.keys(), reverse=True)
hostnames = " ".join(host_list)
# detect the need for windows support
windows_support = False
for hostname in host_list:
if hosts[hostname].info['system'].startswith("win"):
windows_support = True
# detect default server node
server = ""
for hostname in host_list:
if hosts[hostname].info['rudder-setup'] == 'server':
server = hostname
# write a line for each host
for hostname in host_list:
host = hosts[hostname].info
if host['run-with'] == 'vagrant':
os = host['system']
if host['rudder-setup'] == 'server':
host_id = "0"
else:
host_id = str(i)
i += 1
# Define server the node is pointing to
own_server = ""
if 'server' in host:
own_server = host['server']
elif host['rudder-setup'] != 'server':
own_server = server
line = "configure(config, $" + os + ", '" + pf + "', " + str(pf_id) + ", '" + hostname +"', " + host_id
line += ", setup:'" + host['rudder-setup'] + "', version:'" + host['rudder-version'] + "', server:'" + own_server + "', host_list:'" + hostnames + "'"
# windows support
if host['rudder-setup'] == 'server' and windows_support:
line += ", windows_plugin:true"
line += ")\n"
lines.append(line)
return lines
def init_vagrantfile():
""" Initialize an empty Vagrantfile """
if os.path.isfile("Vagrantfile"):
return
with open("Vagrantfile", "w") as vagrant:
vagrant.write("""# -*- mode: ruby -*-
# vi: set ft=ruby :
require_relative 'vagrant.rb'
Vagrant.configure("2") do |config|
### AUTOGEN TAG
end
""")
class Vagrant(Host):
""" Vagrant managed host """
def __init__(self, platform, name, host_info):
Host.__init__(self, host_info)
pf = platform.split('.')[0]
self.platform = pf
self.name = name
self.hostid = pf + '_' + name
init_vagrantfile()
def start(self):
""" Setup and run this host """
os.system("vagrant up " + self.hostid)
def stop(self):
""" Destroy this host """
os.system("vagrant destroy -f " + self.hostid)
def export(self, directory):
""" Export this VM using Virtualbox commads """
fullname = os.path.basename(os.getcwd()).replace('.', '') + '_' + self.hostid
# List VMs and do something on the one we are working on
for line in os.popen("VBoxManage list vms"):
m = re.match(r'"' + fullname + r'[0-9_]+" \{(.*)\}', line)
if m:
uuid = m.group(1)
running = False
print("Exporting " + fullname + " / " + uuid + " to directory " + directory)
# If the VM is running, pause it (and save its state) and rerun it later
for running_line in os.popen("VBoxManage list runningvms"):
if re.search(uuid, running_line):
running = True
if running:
os.system("VBoxManage controlvm " + uuid + " savestate")
else:
print("Currently only running VMs can be exported.")
print("If you wish to export a stopped VM, please open ticket to ask for the feature.")
os.system("VBoxManage clonevm " + uuid + " --options keepallmacs --options keepdisknames --name " + self.hostid + " --basefolder " + directory)
disk_uuid = ""
# work around a virtualbox bug, when you clone a vm, the new disk is registered, whateverthe parameters -> unregiser it
for disk_line in os.popen("VBoxManage list hdds"):
m = re.match(r'UUID:\s+([0-9a-f\-]+)', disk_line)
if m:
disk_uuid = m.group(1)
if re.match(r'Location:\s+'+directory+'/'+self.hostid, disk_line):
os.system("VBoxManage closemedium disk " + disk_uuid)
if running:
os.system("VBoxManage startvm " + uuid + " --type headless")
# get ssh configuration to connect to this machine
def ssh_config(self, key_directory):
try:
output = check_output("vagrant ssh-config " + self.hostid, shell=True)
except CalledProcessError:
print("Cannot get ssh-configuration for " + self.hostid)
print("A patch for vagrant is available here https://github.com/peckpeck/vagrant/commit/93c5b853511548087fba7e8813c34ee45226e1cc")
print("Halting!")
exit(14)
m = re.search(r'\sIdentityFile\s+(\S+)', output)
if m:
src = m.group(1)
output = re.sub(r'(\sIdentityFile)\s+\S+', r'\1 '+os.path.basename(key_directory)+'/'+self.hostid, output)
shutil.copy(src, key_directory+'/'+self.hostid)
return output
def run(self, command):
""" Run a command as root on this host """
value = check_output("vagrant ssh " + self.hostid + " -c \"sudo PATH=$PATH:/vagrant/scripts " + command + "\" -- -q", shell=True)
return value .rstrip()
def share(self, password):
""" Shares this box via vagrant cloud """
# make sure the VM is running
os.system("vagrant up " + self.hostid)
# run vagrant share, provide a password, print its output and extract the command to run
process = pexpect.spawn("/usr/bin/vagrant share --disable-http --ssh "+ self.hostid)
process.expect("Please enter a password to encrypt the key: ")
print(process.before, end='')
process.sendline(password)
process.expect("Repeat the password to confirm: ")
print(process.before, end='')
process.sendline(password)
process.expect(r'==> \w+: simply has to run `(vagrant connect --ssh .*?)`')
command = process.match.group(1)
print(process.before, end='')
process.expect(r'in some secure way..*')
print(process.before, end='')
print(process.after, end='')
return (self.hostid, command, process)
def get_url(self):
""" Get matching server URL """
# port autodetection
port = None
for line in open("Vagrantfile"):
# match configure(config, pf_name, 'platform', id, 0, 'version')
m = re.match(r'configure\((?:.*?,){2}\s*' + "'" + self.platform + "'" + r'\s*,\s*(\d+)', line)
if m:
port = str(int(m.group(1)) * 100 + 8081)
if port is None:
return None
return "https://localhost:" + str(port) + "/rudder"
@staticmethod
def status(platform, hosts):
""" Return the status of all hosts on a given platform """
host_list = [ platform + '_' + h for h in hosts ]
os.system("vagrant status " + " ".join(host_list))
@staticmethod
def reset_platform(name, hosts):
""" Update or replace the Vagrantfile configuration for the given platform """
lines = []
# parse Vagrantfile into the lines array (and update it)
with open("Vagrantfile", "r+") as fd:
updated = False
line=fd.readline()
max_pf_id = 0
header_re = re.compile(r'### AUTOGENERATED FOR PLATFORM (\w+) \((\d+)\)')
while line:
# find max platform id so that we can add a new one if needed
m = header_re.match(line)
if m:
pf_id = int(m.group(2))
if pf_id > max_pf_id:
max_pf_id = pf_id
# look for the platform we want to modify and update its content
if not updated and m and m.group(1) == name:
lines.append(line) # re-add the header
pf_id = int(m.group(2))
while not re.match(r'### END OF AUTOGENERATION FOR ' + name, line):
line = fd.readline()
lines.extend(host_lines(name, hosts, pf_id))
lines.append(line) # re-add the footer
updated = True
# no existing platform, create a new one
elif not updated and re.match(r'### AUTOGEN TAG', line):
lines.append('### AUTOGENERATED FOR PLATFORM ' + name + " (" + str(max_pf_id+1) + ") https://localhost:8" + str(max_pf_id+1) + "81/ \n")
lines.extend(host_lines(name, hosts, max_pf_id+1))
lines.append('### END OF AUTOGENERATION FOR ' + name + "\n")
lines.append("\n### AUTOGEN TAG\n")
updated = True
# unknown line, keep it
else:
lines.append(line)
line=fd.readline()
# rewrite the file
fd.seek(0)
fd.truncate()
fd.writelines(lines)
### End of vagrant specific code ###
# Global list of host type
host_types = { 'vagrant': Vagrant }
class Platform:
""" A test platform
Can be setup or teared down at once
Can be used to run a test scenario
"""
def __init__(self, name, platform_info, override):
self.name = name
self.hosts = {}
# manage default values
default = platform_info['default']
for hostname, host in platform_info.items():
if hostname == "default":
continue
host_info = copy.deepcopy(default)
host_info.update(host)
host_info.update(override)
class_name = host_info['run-with']
# New host object (Vagrant/AWS/...)
self.hosts[hostname] = host_types[class_name](name, hostname, host_info)
def setup(self, client_path):
""" Startup the full platform """
for host_type in host_types.values():
host_type.reset_platform(self.name, self.hosts)
for hostname in sorted(self.hosts.keys(), reverse=True):
self.hosts[hostname].start()
relay_list = []
# Find all relays uuid
for host in self.hosts.values():
if host.info['rudder-setup'] == 'relay':
uuid = host.run("cat /opt/rudder/etc/uuid.hive")
relay_list.append(uuid)
# Promote relays on servers
if relay_list:
for host in self.hosts.values():
if host.info['rudder-setup'] == 'server':
# Treat inventory received on the server
print(host.run("/var/rudder/cfengine-community/bin/cf-agent -K"))
# Environment setup
rudder_url = host.get_url()
token = host.run('cat /root/rudder-token')
setenv(client_path, rudder_url, token)
for relay in relay_list:
# Accept the relay
rcli = "rudder-cli --skip-verify --url=" + rudder_url + " --token=" + token
command = rcli + " nodes accept "+ relay
print(check_output(command, shell=True))
# Promote to relay
print(host.run("/opt/rudder/bin/rudder-node-to-relay "+relay))
def export(self):
""" Export the full platform in a tgz """
# This is virtualbox/vagrant specific and should be refactored when we add a new provider
dirname = os.getcwd() + "/rtf-" + self.name
if not os.path.exists(dirname):
os.mkdir(dirname)
# create ssh configuration early since it can fail (fail early)
keydir = dirname + "/keys"
if not os.path.exists(keydir):
os.mkdir(keydir)
with open(dirname+'/ssh_config', 'w') as outfile:
for host in self.hosts.values():
outfile.write(host.ssh_config(keydir))
# create vm dumps
for host in self.hosts.values():
host.export(dirname)
print("Creating package")
# create startup script
with open(dirname+'/run', 'w') as outfile:
outfile.write("#!/bin/sh\n")
for host in self.hosts.values():
outfile.write("VBoxManage registervm $(pwd)/" + host.hostid + "/" + host.hostid + ".vbox\n")
outfile.write('UUID=$(VBoxManage list vms | grep "\\"' + host.hostid + '\\"" | ' + "perl -pe 's/.*\{(.*)\}.*/$1/')\n")
outfile.write("VBoxManage startvm $UUID --type headless\n")
outfile.write("echo ''\n")
outfile.write("echo 'You can now connect to VMs using ssh -F ssh_config <vmname>'\n")
outfile.write("echo 'Available VMs are: '\n")
for host in self.hosts.values():
outfile.write("echo '"+host.hostid+"'\n")
outfile.write("echo ''\n")
os.chmod(dirname+'/run', 0755)
# create shutdown script
with open(dirname+'/terminate', 'w') as outfile:
outfile.write("#!/bin/sh\n")
for host in self.hosts.values():
outfile.write('UUID=$(VBoxManage list vms | grep "\\"' + host.hostid + '\\"" | ' + "perl -pe 's/.*\{(.*)\}.*/$1/')\n")
outfile.write("[ -n \"$UUID\" ] && VBoxManage controlvm $UUID poweroff\n")
outfile.write("[ -n \"$UUID\" ] && VBoxManage unregistervm $UUID\n")
outfile.write("echo 'You can now safely remove this directory!'\n")
os.chmod(dirname+'/terminate', 0755)
# Create tgz
os.system("tar czf rtf-" + self.name + ".tgz " + os.path.basename(dirname))
shutil.rmtree(dirname)
def teardown(self):
""" Stop and destroy he full platform """
for host in self.hosts.values():
host.stop()
def share(self):
""" Share the platform via vagrant cloud """
# Some of this is vagrant specific, it should be refactored when we add a new provider
password = "password" # this can be a security risk, but:
# it's only test machines
# you need to know they are running
# you need to know their share ID
# Check that the user is logged in
code = os.system("vagrant login -c")
if code != 0:
print("You need an atlas hashicorp login to use this feature.")
print("Go to https://atlas.hashicorp.com/ to create one.")
print("")
print("If you already have an account, type 'vagrant login' and then re-run this command.")
exit(4)
signal.signal(signal.SIGINT, empty_handler)
signal.signal(signal.SIGTERM, empty_handler)
# share
shared_process = []
for host in self.hosts.values():
shared_process.append(host.share(password))
# display info
print("")
print("Now you can tell your coworker to run the following commands (he needs atlas account too):")
print("")
print("vagrant login")
for (hostid, cmd, process) in shared_process:
print(cmd + " # " + hostid)
print("")
# wait for ctrl-c and propagate it to stop sharing
print("Press ctrl-c to stop sharing")
signal.pause()
print("Unsharing")
for (hostid, cmd, process) in shared_process:
process.sendintr()
process.wait()
def update_rudder(self, version):
""" Update rudder version on all hosts """
for host in self.hosts.values():
print(host.run("rudder-setup upgrade-" + host.info['rudder-setup'] + " " + version))
def status(self):
""" Show platform status """
for host_type in host_types.values():
host_list = []
for hostname,host in self.hosts.items():
if isinstance(host, host_type):
host_list.append(hostname)
host_type.status(self.name, host_list)
def api_connection_info(self):
""" Get informations to connect to the server via the api """
rudder_url = None
token = None
for hostname, host in self.hosts.items():
if host.info['rudder-setup'] == "server":
rudder_url = host.get_url()
token = host.run('cat /root/rudder-token')
if rudder_url is None or token is None:
print("This platform has no rudder server, can't run this command")
exit(2)
return (rudder_url, token)
def run_scenario(self, name, frmt, run_finally, run_only, client_path):
""" Run a scenario on this platform """
# test ruby binary
rubyver = check_output("ruby --version", shell=True)
if re.match(r'jruby', rubyver):
if not re.match(r'jruby 1.7', rubyver):
print("WARNING: this is not JRuby 1.7, compatibility unknown")
elif not re.match(r'ruby 2', rubyver):
print("ERROR: MRI Ruby needs be version 2")
exit(3)
# Test rspec command
rspec = "ruby -S rspec --order defined --fail-fast --format " + frmt
check_output(rspec, shell=True)
# Get api command line
(rudder_url, token) = self.api_connection_info()
rcli = "rudder-cli --skip-verify --url=" + rudder_url + " --token=" + token
# load and run
scenario.lib.scenario = scenario.lib.Scenario(self, rspec, rcli, frmt, run_only, run_finally)
setenv(client_path, rudder_url, token)
importlib.import_module("scenario." + name)
def print_environment(self, client_path):
""" Print environment used to run tests on this platform """
(rudder_url, token) = self.get_server_info()
setenv(client_path, rudder_url, token)
print("export PATH=" + os.environ['PATH'])
print("export PYTHONPATH=" + os.environ['PYTHONPATH'])
print("export RUDDER_SERVER=" + os.environ['RUDDER_SERVER'])
print("export RUDDER_TOKEN=" + os.environ['RUDDER_TOKEN'])
print("alias rcli='./rudder-cli --skip-verify --url=" + rudder_url + " --token=" + token + "'")
def export_test(self, rule_uuid, test_name, scenario=False):
""" Export a given rule, for use in a test or a scenario """
# Retrieve data
(rudder_url, token) = self.get_server_info()
endpoint = RudderEndPoint(rudder_url, token, verify=False)
rule = endpoint.rule_details(rule_uuid)['rules'][0]
directives = []
for directive_uuid in rule['directives']:
directive = endpoint.directive_details(directive_uuid)['directives'][0]
del directive['id']
directives.append(directive)
# create test files
rule_file = "spec/tests/"+test_name+"_rule.rb"
test_file = "spec/tests/"+test_name+"_test.rb"
scenario_file = "scenario/"+test_name+".py"
make_rule_testfile(rule_file, rule, directives)
make_user_testfile(test_file)
print("""
A test file to add the rule via the API has been created in %(rule_file)s
This file is where you can make change to the generated rule or directive, but it works as is.
A generic test file to test if the rule has been properly applied has been created in %(test_file)s
It contains demo code, but since we don't know what the rule does it doesn't contain code.
-> Pleas edit %(rule_file)s !
Add the test to an existing scenario:
- Add the following line before the call to wait_for_generation on all agents
run('localhost', '%(test_name)s_rule', Err.BREAK, NAME="Test %(test_name)s", GROUP="special:all")
- Add the following line after the call to wait_for_generation
run_on_agents('%(test_name)s_test', Err.CONTINUE)
- Add the following lines before the removal of the agent nodes
run('localhost', 'directive_delete', Err.FINALLY, DELETE="%(directive_name)s", GROUP="special:all")
run('localhost', 'rule_delete', Err.FINALLY, DELETE="%(rule_name)s", GROUP="special:all")
""" % { 'rule_file': rule_file, 'test_file': test_file, 'test_name': test_name,
'directive_name': directive['displayName'], 'rule_name': rule['displayName']
} )
if scenario:
make_scenario_file(scenario_file, test_name)
print("Additionnaly, a scenario has been created in " + scenario_file)
###################
# Utility methods #
###################
def empty_handler(signum, frame):
pass
def setenv(client_path, url, token):
""" Set environment variables for command calls """
if client_path is not None:
os.environ['PATH'] += ":" + client_path + "/cli"
os.environ['PYTHONPATH'] = client_path + "/lib.python"
os.environ['RUDDER_SERVER'] = url
os.environ['RUDDER_TOKEN'] = token
def load_json(filename):
""" Load a commented json """
# read json from file
file = open(filename, 'r')
data = file.read()
file.close()
data = re.sub("\\/\\/.*", "", data)
return json.loads(data)
_platform = None
def get_platform(name, override={}):
""" Get a platform object given its name """
global _platform
if _platform is not None:
return _platform
platform_description = load_json("platforms/" + name + ".json")
_platform = Platform(name, platform_description, override)
return _platform
def list_platforms():
""" List available platforms """
for file in os.listdir("platforms"):
print(file.replace(".json", ""))
def list_scenarii():
""" List available scenarios """
for f in os.listdir("scenario"):
if not f.endswith(".py"):
continue
file = f.replace(".py", "")
if file != "__init__" and file != "lib":
print(file)
def make_rule_testfile(filename, rule, directives):
with open(filename, "w") as fd:
data = """# File generated using rtf test from-rule
# This test creates a rule named '%(displayName)s' and its directive(s)
# And is checks that the api returned correctly
require 'spec_helper'
group = $params['GROUP']
directiveFile = "/tmp/directive.json"
ruleFile = "/tmp/rule.json"
describe "Add a test directive and a rule" do
""" % { 'displayName': rule['displayName'] }
for idx,directive in enumerate(directives):
data += """
# Add directive
describe command($rudderCli + " directive create --json=" + directiveFile + " %(technique)s %(name)s") do
before(:all) {
File.open(directiveFile, 'w') { |file|
file << <<EOF
%(directive)s
EOF
}
}
after(:all) {
File.delete(directiveFile)
}
its(:exit_status) { should eq 0 }
its(:stdout) { should match /^"[0-9a-f\\-]+"$/ }
it {
# register output uuid for next command
$uuid%(id)i = subject.stdout.gsub(/^"|"$/, "").chomp()
}
end
""" % { 'technique': directive['techniqueName'], 'name': directive['displayName'], 'directive': json.dumps(directive, indent=2), 'id': idx }
data += """
# create a rule
describe command($rudderCli + " rule create --json=" + ruleFile + " testRule") do
before(:all) {
File.open(ruleFile, 'w') { |file|
file << <<EOF
{
"directives": [
"""
data += ",".join([ '"#{$uuid' + str(i) + '}"' for i in range(0, len(directives)) ])
data += """
],
"displayName": "%(displayName)s Rule",
"longDescription": "%(longDescription)s ",
"shortDescription": "%(shortDescription)s",
"targets": [
{
"exclude": {
"or": []
},
"include": {
"or": [
"#{group}"
]
}
}
]
}
EOF
}
}
after(:all) {
File.delete(ruleFile)
}
its(:exit_status) { should eq 0 }
its(:stdout) { should match /^"[0-9a-f\\-]+"$/ }
it {
# register output uuid for next command
$uuid = subject.stdout.gsub(/^"|"$/, "").chomp()
}
end
end
""" % { 'displayName': rule['displayName'], 'longDescription': rule['longDescription'], 'shortDescription': rule['shortDescription'] }
fd.write(data)
def make_user_testfile(filename):
with open(filename, "w") as fd:
fd.write("""# Sample file generated using rtf test from-rule
# This is where you test your rule
require 'spec_helper'
# Please add your test here
# see http://serverspec.org/resource_types.html for a full documentation of available tests
## Ex: Test that a a package has been installed
#describe package'apache2') do
# it { should be_installed }
# it { should be_installed.with_version('2.4.10') }
#end
## Ex: Test that a user exist
#describe user('testuser') do
# it { should exist }
# it { should have_home_directory '/home/testuser' }
#end
## Ex: Test that a file exists
#describe file('/etc/passwd') do
# it { should be_file }
# it { should be_mode 640 }
# it { should be_owned_by 'root' }
# its(:content) { should match /regex to match/ }
#end
## Ex: Test the output of a command
#describe command('ls -al /') do
# its(:stdout) { should match /bin/ }
# its(:stderr) { should match /No such file or directory/ }
# its(:exit_status) { should eq 0 }
#end
""")
def make_scenario_file(filename, test_name):
with open(filename, "w") as fd:
fd.write("""# File generated using rtf test from-rule
from scenario.lib import *
# test begins, register start time
start()
run_on_all('agent', Err.CONTINUE)
# force inventory
run_on_agents('run_agent', Err.CONTINUE, PARAMS="-D force_inventory")
run_on_servers('run_agent', Err.CONTINUE, PARAMS="")
# accept nodes
for host in scenario.agent_nodes():
run('localhost', 'agent_accept', Err.BREAK, ACCEPT=host)
# Add a rule
date0 = host_date('wait', Err.CONTINUE, "server")
run('localhost', '%(test_name)s_rule', Err.BREAK, NAME="Test %(test_name)s", GROUP="special:all")
for host in scenario.agent_nodes():
wait_for_generation('wait', Err.CONTINUE, "server", date0, host, 20)
# Run agent
run_on_agents('run_agent', Err.CONTINUE, PARAMS="-f failsafe.cf")
run_on_agents('run_agent', Err.CONTINUE, PARAMS="")
# Test rule result
run_on_agents('%(test_name)s_test', Err.CONTINUE)
# remove rule/directive
run('localhost', 'directive_delete', Err.FINALLY, DELETE="Test %(test_name)s Directive", GROUP="special:all")
run('localhost', 'rule_delete', Err.FINALLY, DELETE="Test %(test_name)s Rule", GROUP="special:all")
# remove agent
for host in scenario.agent_nodes():
run('localhost', 'agent_delete', Err.FINALLY, DELETE=host)
# test end, print summary
finish()
""" % { 'test_name': test_name } )
##########################
# Command line interface #
##########################
if __name__ == "__main__":
args = docopt.docopt(__doc__)
# Hack nedded because there is not api client package yet
client_path = "rudder-api-client"
if not os.path.exists(client_path):
print("Can't find rudder-api-client, if you install rudder-api-client package, you should patch this script")
print("If you want to use rudder-api-client from a local repository please type this command:")
print("ln -s ~/<path_to>/rudder-api-client")
exit(1)
if args['platform']:
if args['list']:
list_platforms()
else:
if args['status']:
platform = get_platform(args['<platform>'])
platform.status()
elif args['setup']:
override = {}
if args['<version>'] is not None:
override['rudder-version'] = args['<version>']
platform = get_platform(args['<platform>'], override)
platform.setup(client_path)
elif args['destroy']:
platform = get_platform(args['<platform>'])
platform.teardown()
elif args['share']:
platform = get_platform(args['<platform>'])
platform.share()
elif args['export']:
platform = get_platform(args['<platform>'])
platform.export()
elif args['update-rudder']:
platform = get_platform(args['<platform>'])
platform.update_rudder(args['<version>'])
elif args['update-os']:
pass
elif args['host']:
pass
elif args['scenario']:
if args['list']:
list_scenarii()
elif args['env']:
platform = get_platform(args['<platform>'])
platform.print_environment(client_path)
elif args['run']:
filter=args['--filter']
if filter == []:
filter = None
platform = get_platform(args['<platform>'])
platform.run_scenario(args['<scenario>'], args['--format'], not args['--no-finally'], filter, client_path)
elif args['test']:
if args['from-rule']:
platform = get_platform(args['<platform>'])
platform.export_test(args['<uuid>'], args['<test_name>'], args['--create-scenario'])