noms-client 1.10.0 → 1.12.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ROADMAP.rst +215 -0
- data/bin/noms +59 -3
- data/lib/noms/client/version.rb +1 -1
- data/lib/noms/cmdb.rb +28 -1
- data/lib/noms/httpclient.rb +12 -3
- data/lib/noms/nagui.rb +77 -6
- data/spec/01nomscmdb_spec.rb +42 -1
- data/spec/04cmdb-mock_spec.rb +32 -31
- data/spec/05restmock-persist_spec.rb +3 -2
- data/spec/06noms-mock.sh +19 -0
- metadata +26 -14
- checksums.yaml +0 -7
- data/control.m4 +0 -8
data/ROADMAP.rst
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
noms
|
2
|
+
====
|
3
|
+
|
4
|
+
This is the roadmap for a second-generation of the 'noms' general purpose interface.
|
5
|
+
|
6
|
+
**noms** is a remote command-line interface interpreter. It's designed to be a stable runtime environment for interpreting server-defined command-line interfaces for (principally) rest-like data stores (or for commands that are side-effect free, but *not* commands that change any state on the system on which the command runs).
|
7
|
+
|
8
|
+
The web browser is a platform in which the operator of a web service can implement a graphical user interface to data it controls. For example, it's a common pattern to offer access to services and data via a ReST or ReST-like interface, making http requests against a remote API understanding the HTTP verbs and returning results in the form of HTTP responses, header metadata and response bodies containing serialized data (e.g. JSON). Such interfaces are generally implemented in a combination of HTML documents and Javascript which modifies the document model of the HTML page(s).
|
9
|
+
|
10
|
+
**noms** enables an author to offer a command-line interface designed along the same pattern: structured documents modified by javascript programs, interpreted and rendered on the client. **noms** has sandboxing similar to web browsers (no modifying of local storage outside of restricted, application-specific local storage and automatic caching of javascript files).
|
11
|
+
|
12
|
+
**noms** is *not* a web browser and is not designed to offer terminal user interfaces like lynx or elinks. It is also *not* an interactive shell--it's designed to be used from a shell. It maintains authenticated sessions state when necessary.
|
13
|
+
|
14
|
+
Syntax
|
15
|
+
------
|
16
|
+
|
17
|
+
The basic way of invoking an **noms** command is as follows::
|
18
|
+
|
19
|
+
noms *url* *options* *arguments*
|
20
|
+
|
21
|
+
**noms** invokes the app at *url* with the given options and arguments, displaying the results.
|
22
|
+
|
23
|
+
Special URLs
|
24
|
+
~~~~~~~~~~~~
|
25
|
+
|
26
|
+
Certain invalid "URLs" are interpreted specially:
|
27
|
+
|
28
|
+
* ``noms login *url*``
|
29
|
+
|
30
|
+
Normally **noms** handles user authentication implicitly. With this command, it does a HEAD request against the application URL, forcing login if required.
|
31
|
+
|
32
|
+
* ``noms logout *url*``
|
33
|
+
|
34
|
+
Causes **noms** to forget its session state for the given application URL.
|
35
|
+
|
36
|
+
* ``noms *bookmark*[/arg] ...``
|
37
|
+
|
38
|
+
The **noms** command itself has a configuration file (``~/.noms``, ``/usr/local/etc/noms.conf``, ``/etc/noms.conf`` in that order) which defines bookmarks to different URLs. For example, given the following in ``/etc/noms.conf``::
|
39
|
+
|
40
|
+
{
|
41
|
+
"cmdb": "https://cmdb.noms-example.com/cmdb.json",
|
42
|
+
"instance": "https://ncc-api.noms-example.com/ncc-api.json",
|
43
|
+
"nagios": "https://nagios.noms-example.com/nagui.json",
|
44
|
+
"nag": "https://nagios.noms-exmaple.com/nagui.json"
|
45
|
+
}
|
46
|
+
|
47
|
+
When invoked in the following ways, it's the equivalent to the command on the right:
|
48
|
+
|
49
|
+
================================= ==================================================================
|
50
|
+
Command given Equivalent command
|
51
|
+
================================= ==================================================================
|
52
|
+
``noms cmdb query fqdn~^m00`` ``noms https://cmdb.noms-example.com/cmdb.json query fqdn~^m00``
|
53
|
+
(``document.argv[0]`` set to ``cmdb``)
|
54
|
+
``noms cmdb/env list`` ``noms https://cmdb.noms-example.com/cmdb.json list``
|
55
|
+
(``document.argv[0]`` set to ``cmdb/env``)
|
56
|
+
``noms nag alerts`` ``noms https://cmdb.noms-example.com/nagui.json alerts``
|
57
|
+
(``document.argv[0]`` set to ``nag``)
|
58
|
+
================================= ==================================================================
|
59
|
+
|
60
|
+
Implementation
|
61
|
+
--------------
|
62
|
+
|
63
|
+
If the type is ``text/plain``, it's simply displayed.
|
64
|
+
|
65
|
+
If the type is a recognized data serialization format (JSON or YAML):
|
66
|
+
|
67
|
+
* application/json
|
68
|
+
* application/x-json
|
69
|
+
* text/json
|
70
|
+
* application/yaml
|
71
|
+
* application/x-yaml
|
72
|
+
* text/yaml
|
73
|
+
|
74
|
+
If the fetched content is a single object and the object has the top-level key '$doctype', it may be interpreted according to "Dynamic Doctypes", below. Otherwise, it is assumed to be either a single object to display or a list of such. Otherwise **noms** will render the object or array using its default format (usually YAML).
|
75
|
+
|
76
|
+
Dynamic Doctypes
|
77
|
+
~~~~~~~~~~~~~~~~
|
78
|
+
|
79
|
+
The principle dynamic doctype is the ``noms-v2``, which is an object with the following top-level attributes:
|
80
|
+
|
81
|
+
``$doctype``
|
82
|
+
Must be ``noms-v2``. In future, backwards-incompatible extensions may be implemented in ``noms-v3`` or higher doctypes.
|
83
|
+
|
84
|
+
``$script``
|
85
|
+
An ordered array of scripts to fetch and evaluate.
|
86
|
+
|
87
|
+
``$format-fields``
|
88
|
+
An array of objects, each having (at least) a ``name`` and ``width`` attribute. May also include a ``label`` attribute
|
89
|
+
for the column heading.
|
90
|
+
|
91
|
+
``$body``
|
92
|
+
The body of the document is the data to display. See `Output Description Notation`_ below.
|
93
|
+
|
94
|
+
Output Description Notation
|
95
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
96
|
+
|
97
|
+
The following entities are allowed in the body of a **noms=2** document.
|
98
|
+
|
99
|
+
* Arrays - Each item in the array is concatenated with a line-break between them.
|
100
|
+
* Strings and numbers - A string or number is just displayed
|
101
|
+
* Raw objects - Raw objects are rendered using **noms** default formatting (usually YAML)
|
102
|
+
* Described objects - Described objects are data along with information on how to render them. A described object
|
103
|
+
has a top-level attribute called **$type** which defines how the described object is rendered.
|
104
|
+
|
105
|
+
* ``$type``: **object-list** An object list is a (usually) tabular list of objects with information on how
|
106
|
+
wide to make the fields or how to otherwise serialize the objects. It has the following attributes:
|
107
|
+
|
108
|
+
* **render**: The format in which to render, one of: **json**, **yaml**, **text** (default **text**)
|
109
|
+
* **fields**: Field names, headings and widths
|
110
|
+
* **objects**: The objects to render
|
111
|
+
|
112
|
+
* ``$type``: **object** An object described-object has the following attributes:
|
113
|
+
|
114
|
+
* **render**: The format in which to render, one of: **json**, **yaml**, **text** (default **yaml**)
|
115
|
+
* **object**: The object data
|
116
|
+
|
117
|
+
Putting it all together
|
118
|
+
-----------------------
|
119
|
+
|
120
|
+
Example **noms** conversation::
|
121
|
+
|
122
|
+
bash$ noms https://cmdb.noms-example.com/cmdb.json --format=csv system fqdn~^m00
|
123
|
+
|
124
|
+
noms >> GET https://cmdb.noms-example.com/cmdb.json
|
125
|
+
noms << set 'document' to retrieved object:
|
126
|
+
{ "$doctype": "appdoc",
|
127
|
+
"$script": ["lib/optconfig.js", "noms/cmdb.js", "noms/cli.js"],
|
128
|
+
"$body": null
|
129
|
+
}
|
130
|
+
noms << set 'document.argv' to ["https://cmdb.noms-example.com/cmdb.json", "--format=csv", "system", "fqdn~^m00"]
|
131
|
+
noms << set 'document.exitcode' to 0
|
132
|
+
noms >> GET https://cmdb.noms-example.com/lib/optconfig.js
|
133
|
+
noms << evaluate javascript option-parsing library optconfig.js
|
134
|
+
noms >> GET https://cmdb.noms-example.com/noms/cmdb.js
|
135
|
+
noms << evaluate noms cmdb client library
|
136
|
+
noms >> GET https://cmdb.noms-example.com/noms/cli.js
|
137
|
+
noms << evaluate noms cli library
|
138
|
+
cli.js << calls optconfig().parse with optspec
|
139
|
+
optconfig.js << sets document.argv to ["system", "fqdn~^m00"]
|
140
|
+
optconfig.js << sets document.options to { "format": "csv" }
|
141
|
+
cli.js << call noms_cmdb().query("system", "fqdn~^m00")
|
142
|
+
noms/cmdb.js << http.request("https://cmdb.noms-example.com/cmdb_api/v1/system/?fqdn~^m00")
|
143
|
+
cli.js << sets document.body to return objects to render
|
144
|
+
{ "$doctype": "appdoc",
|
145
|
+
"$script": ["lib/optconfig.js", "noms/cmdb.js", "noms/cli.js"],
|
146
|
+
"$body": [{
|
147
|
+
"$type": "object-list",
|
148
|
+
"render": "csv",
|
149
|
+
"fields": [
|
150
|
+
{ "name": "fqdn", "width": 36 },
|
151
|
+
{ "name": "environment_name", "width": 16, "heading": "environment" },
|
152
|
+
{ "name": "status", "width": 15 },
|
153
|
+
{ "name": "roles", "width": 15 },
|
154
|
+
{ "name": "ipaddress", "width": 15 },
|
155
|
+
{ "name": "data_center_code": 11, "heading": "datacenter" } ],
|
156
|
+
"objects": [
|
157
|
+
{ "fqdn": "m001.noms-example.com",
|
158
|
+
"environment_name": "production",
|
159
|
+
"status": "production",
|
160
|
+
"roles": "build",
|
161
|
+
"ipaddress": "10.8.9.10",
|
162
|
+
"data_center_code": "US2" },
|
163
|
+
{ "fqdn": "m002.noms-example.com",
|
164
|
+
"environment_name": "testing",
|
165
|
+
"status": "allocated",
|
166
|
+
"roles": "webserver",
|
167
|
+
"ipaddress": "10.8.9.11",
|
168
|
+
"data_center_code": "US2" }
|
169
|
+
]
|
170
|
+
}
|
171
|
+
]
|
172
|
+
}
|
173
|
+
|
174
|
+
noms >> print output
|
175
|
+
fqdn,environment,status,roles,ipaddress,datacenter
|
176
|
+
"m001.noms-example.com",production,production,build,10.8.9.10,US2
|
177
|
+
"m002.noms-example.com",allocated,testing,webserver,10.8.9.11,US2
|
178
|
+
|
179
|
+
bash$ noms https://ncc-api.noms-example.com/ncc.json show m002.noms-example.com
|
180
|
+
|
181
|
+
{ "$doctype": "appdoc",
|
182
|
+
"$script": ["noms/optconfig.js",
|
183
|
+
{ "name": "name", "width": 36 },
|
184
|
+
{ "name": "status", "width": 10 },
|
185
|
+
{ "name": "size", "width": 10 },
|
186
|
+
{ "name": "image", "width": 15 },
|
187
|
+
{ "name": "id", "width": 37 }
|
188
|
+
]
|
189
|
+
"$body": null
|
190
|
+
}
|
191
|
+
|
192
|
+
name status size image id
|
193
|
+
m0000291.noms-example.net active m1.small deb6 d8c4c29e-785f-49ef-9d31-e4a71e9954fc
|
194
|
+
m0000290.noms-example.net active m1.small deb7 33a88a1d-49a4-4c26-9a0c-b699703f5e64
|
195
|
+
m0000289.noms-example.net active m1.small deb7 fd82f522-f305-4150-a969-1b8b9fd2d91d
|
196
|
+
m0000288.noms-example.net error m1.small deb6 9d7f1c55-5f8f-4f98-9bf8-c1156a0506d2
|
197
|
+
m0000287.noms-example.net active m1.small deb6 c4a6310d-4927-4e79-8170-443172eb9a7c
|
198
|
+
m0000286.noms-example.net active m1.small centos6.2 88c654b6-77f2-4995-affb-c3a3bac16bd0
|
199
|
+
m0000277.noms-example.net active m1.small deb6 e34e4a8f-81ef-42a3-a9c0-40933be7595f
|
200
|
+
|
201
|
+
Invoked scripts have access to the following global objects:
|
202
|
+
|
203
|
+
* **window** - This has information about the terminal environment in which **noms** is being invoked. It has the following attributes:
|
204
|
+
* **height** - Height (if known)
|
205
|
+
* **width** - Width (if known)
|
206
|
+
* **isatty** - true if the output stream is a terminal
|
207
|
+
* **document** - The document global object
|
208
|
+
* **document** - The document object is the current document being rendered by **noms**. In addition to the attributes of the document itself, it has the following:
|
209
|
+
* **argv** - The arguments being invoked. The first element of this array is the first argument passed to **noms** itself (not the script it ultimately fetches, but how it's invoked, similar to ``$1``
|
210
|
+
* **exitcode** - The numeric exit code with which **noms** will exit. Initially 0.
|
211
|
+
|
212
|
+
Web 1.0 vs Web 2.0
|
213
|
+
------------------
|
214
|
+
|
215
|
+
Like the "real web", **noms** commands can choose to do some calculation on the server and some on the client: **noms** doesn't care. You can use no ``$script`` tag at all and just calculate the entire document to be rendered in the client (though this currently odoesn't allow for argument interpretation, in the future the arguments may be passed in request headers or **noms** may allow a way for them to show up in a query string or POST request--but **noms** is not really a command-line http client either). This is up to the application designer.
|
data/bin/noms
CHANGED
@@ -63,7 +63,13 @@
|
|
63
63
|
# show type name - Show information for named type [hostgroup,host,service]
|
64
64
|
# detail host - Show object detail for named host
|
65
65
|
# query type [condition [...]] - Query for object by type [hostgroup,host,service]
|
66
|
-
# check
|
66
|
+
# check host [service] - Run service check for host (or host check) synchronously
|
67
|
+
# ack host [service] <comment - acknowledge a warning or critical host/service
|
68
|
+
# comment host [service] comment - add a comment to a host or service
|
69
|
+
# downtime host [service] duration comment - put a host or service into maintenance
|
70
|
+
# undowntime host [service] - Remove downtime from a host or service
|
71
|
+
# alerts - List all unacknowledged problems that are in a hard non-OK
|
72
|
+
# state and are not in a scheduled downtime period
|
67
73
|
#
|
68
74
|
# Options:
|
69
75
|
# --names - Just print entity names, equivalent to
|
@@ -242,7 +248,7 @@ $opt = Optconfig.new('noms', {
|
|
242
248
|
'state' => 5,
|
243
249
|
'plugin_output' => 36
|
244
250
|
}
|
245
|
-
},
|
251
|
+
},
|
246
252
|
'service' => {
|
247
253
|
'fields' => [
|
248
254
|
'host_name','description','state', 'plugin_output'
|
@@ -517,7 +523,7 @@ end
|
|
517
523
|
|
518
524
|
def cmdb_show(args)
|
519
525
|
fqdn = args.shift
|
520
|
-
system = $cmdb.
|
526
|
+
system = $cmdb.system(fqdn)
|
521
527
|
record = if ! args.empty?
|
522
528
|
system.keys.inject({}) do |h, k|
|
523
529
|
if args.include? k
|
@@ -618,6 +624,52 @@ def nagios(args)
|
|
618
624
|
end
|
619
625
|
when 'check'
|
620
626
|
record_formatted_output(nagcheck(args),'nagcheck')
|
627
|
+
when 'ack'
|
628
|
+
host=args.shift
|
629
|
+
if args.length == 1
|
630
|
+
comment=args.shift
|
631
|
+
$nagui.ack_host(host,get_username,comment)
|
632
|
+
else
|
633
|
+
service=args.shift
|
634
|
+
if(args.length > 1)
|
635
|
+
comment = args.join(' ')
|
636
|
+
else
|
637
|
+
comment = args.shift
|
638
|
+
end
|
639
|
+
$nagui.ack_service(host,service,get_username,comment)
|
640
|
+
end
|
641
|
+
when 'alerts'
|
642
|
+
formatted_output($nagui.query('service',['acknowledged=0','state!=0','state_type=1',"scheduled_downtime_depth=0","host_scheduled_downtime_depth=0"]),'service')
|
643
|
+
when 'comment'
|
644
|
+
host=args.shift
|
645
|
+
if args.length == 1
|
646
|
+
comment=args.shift
|
647
|
+
$nagui.comment(host,nil,get_username,comment)
|
648
|
+
else
|
649
|
+
service=args.shift
|
650
|
+
if(args.length > 1)
|
651
|
+
comment = args.join(' ')
|
652
|
+
else
|
653
|
+
comment = args.shift
|
654
|
+
end
|
655
|
+
$nagui.comment(host,service,get_username,comment)
|
656
|
+
end
|
657
|
+
when 'downtime','down'
|
658
|
+
host = args.shift
|
659
|
+
if args.length == 2
|
660
|
+
length = args.shift
|
661
|
+
comment = args.shift
|
662
|
+
$nagui.downtime_host(host,length,get_username,comment)
|
663
|
+
else
|
664
|
+
service = args.shift
|
665
|
+
length = args.shift
|
666
|
+
comment = args.join(' ')
|
667
|
+
$nagui.downtime_service(host,service,length,get_username,comment)
|
668
|
+
end
|
669
|
+
when 'undowntime','undown'
|
670
|
+
host = args.shift
|
671
|
+
service = args.shift if !args.empty? || nil
|
672
|
+
$nagui.undowntime(host,service)
|
621
673
|
else
|
622
674
|
$stderr.puts "Unknown nagios command '#{cmd}'"
|
623
675
|
Process.exit(2)
|
@@ -640,6 +692,10 @@ def cmdb(args)
|
|
640
692
|
fqdn = args.shift
|
641
693
|
updated = $cmdb.update('system', opt, fqdn)
|
642
694
|
''
|
695
|
+
when 'add'
|
696
|
+
fqdn = args.shift
|
697
|
+
created = $cmdb.create('system', opt, fqdn)
|
698
|
+
''
|
643
699
|
else
|
644
700
|
$stderr.puts "Unknown cmdb command '#{cmd}'"
|
645
701
|
Process.exit(2)
|
data/lib/noms/client/version.rb
CHANGED
data/lib/noms/cmdb.rb
CHANGED
@@ -61,6 +61,20 @@ class NOMS::CMDB < NOMS::HttpClient
|
|
61
61
|
do_request(:GET => "pcmsystemname/#{serial}")
|
62
62
|
end
|
63
63
|
|
64
|
+
def create(type, obj, key=nil)
|
65
|
+
keyfield = key_field_of(type)
|
66
|
+
objkey = obj[keyfield]
|
67
|
+
|
68
|
+
if key.nil? and objkey.nil?
|
69
|
+
raise NOMS::Error, "Must specify a key value or imply it in the object's #{key_field_of(type)} value"
|
70
|
+
end
|
71
|
+
if (obj.has_key?(keyfield) and (! key.nil?) and obj[keyfield] != key)
|
72
|
+
raise NOMS::Error, "You specified different keys for a new #{type} object: #{objkey} in the object vs. #{key}"
|
73
|
+
end
|
74
|
+
newobj = { keyfield => (objkey || key) }.merge obj
|
75
|
+
do_request(:POST => type, :body => newobj)
|
76
|
+
end
|
77
|
+
|
64
78
|
def update(type, obj, key=nil)
|
65
79
|
key ||= obj[key_field_of(type)]
|
66
80
|
do_request(:PUT => "#{type}/#{key}", :body => obj)
|
@@ -119,12 +133,25 @@ class NOMS::CMDB::Mock < NOMS::HttpClient::RestMock
|
|
119
133
|
|
120
134
|
@@machine_id = 0
|
121
135
|
|
136
|
+
def allow_put_to_create
|
137
|
+
false
|
138
|
+
end
|
139
|
+
|
140
|
+
def id_field(path)
|
141
|
+
case path
|
142
|
+
when %r{system$}
|
143
|
+
'fqdn'
|
144
|
+
else
|
145
|
+
'id'
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
122
149
|
def handle_mock(method, uri, opt)
|
123
150
|
if m = Regexp.new('/pcmsystemname/([^/]+)').match(uri.path)
|
124
151
|
serial = m[1]
|
125
152
|
@@machine_id += 1
|
126
153
|
name = "m-%03d.mock" % @@machine_id
|
127
|
-
do_request :
|
154
|
+
do_request :POST => "system",
|
128
155
|
:body => {
|
129
156
|
'serial' => serial,
|
130
157
|
'fqdn' => name
|
data/lib/noms/httpclient.rb
CHANGED
@@ -132,6 +132,10 @@ class NOMS::HttpClient
|
|
132
132
|
false
|
133
133
|
end
|
134
134
|
|
135
|
+
def allow_put_to_create
|
136
|
+
true
|
137
|
+
end
|
138
|
+
|
135
139
|
def default_content_type
|
136
140
|
'application/json'
|
137
141
|
end
|
@@ -253,9 +257,14 @@ class NOMS::HttpClient::RestMock < NOMS::HttpClient
|
|
253
257
|
object_index =
|
254
258
|
@data[url.host][collection_path].index { |el| el[id_field(collection_path)] == id }
|
255
259
|
if object_index.nil?
|
256
|
-
|
257
|
-
|
258
|
-
|
260
|
+
if allow_put_to_create
|
261
|
+
object = opt[:body].merge({ id_field(collection_path) => id })
|
262
|
+
dbg "creating in collection #{collection_path}: #{object.inspect}"
|
263
|
+
@data[url.host][collection_path] << opt[:body].merge({ id_field(collection_path) => id })
|
264
|
+
else
|
265
|
+
dbg "not creating in collection #{collection_path}: #{id} (allow_put_to_create is false)"
|
266
|
+
raise NOMS::Error, "There is no resource at this location"
|
267
|
+
end
|
259
268
|
else
|
260
269
|
if allow_partial_updates
|
261
270
|
object = @data[url.host][collection_path][object_index].merge(opt[:body])
|
data/lib/noms/nagui.rb
CHANGED
@@ -16,7 +16,7 @@
|
|
16
16
|
# */
|
17
17
|
|
18
18
|
require 'noms/httpclient'
|
19
|
-
require '
|
19
|
+
require 'cgi'
|
20
20
|
|
21
21
|
class NOMS
|
22
22
|
|
@@ -80,10 +80,11 @@ class NOMS::Nagui < NOMS::HttpClient
|
|
80
80
|
Process.exit(1)
|
81
81
|
end
|
82
82
|
query_string = make_lql(type,queries)
|
83
|
-
results = do_request(:GET => '/nagui/nagios_live.cgi', :query =>
|
83
|
+
results = do_request(:GET => '/nagui/nagios_live.cgi', :query => "query=#{CGI.escape(query_string)}")
|
84
84
|
end
|
85
85
|
def hostgroup(name)
|
86
|
-
|
86
|
+
lql = "GET hostgroups|Filter: name = #{name}"
|
87
|
+
results = do_request(:GET => '/nagui/nagios_live.cgi', :query => "query=#{CGI.escape(lql)}")
|
87
88
|
if results.kind_of?(Array) && results.length > 0
|
88
89
|
results[0]
|
89
90
|
else
|
@@ -91,7 +92,8 @@ class NOMS::Nagui < NOMS::HttpClient
|
|
91
92
|
end
|
92
93
|
end
|
93
94
|
def service(host,description)
|
94
|
-
|
95
|
+
lql = "GET services|Filter: host_name = #{host}|Filter: description = #{description}"
|
96
|
+
results = do_request(:GET => '/nagui/nagios_live.cgi', :query => "query=#{CGI.escape(lql)}")
|
95
97
|
if results.kind_of?(Array) && results.length > 0
|
96
98
|
results[0]
|
97
99
|
else
|
@@ -99,7 +101,8 @@ class NOMS::Nagui < NOMS::HttpClient
|
|
99
101
|
end
|
100
102
|
end
|
101
103
|
def servicegroup(name)
|
102
|
-
|
104
|
+
lql = "query=GET hosts|Filter: name = #{name}"
|
105
|
+
results = do_request(:GET => '/nagui/nagios_live.cgi', :query => "query=#{CGI.escape(lql)}")
|
103
106
|
if results.kind_of?(Array) && results.length > 0
|
104
107
|
results[0]
|
105
108
|
else
|
@@ -107,7 +110,8 @@ class NOMS::Nagui < NOMS::HttpClient
|
|
107
110
|
end
|
108
111
|
end
|
109
112
|
def host(hostname)
|
110
|
-
|
113
|
+
lql = "GET hosts|Filter: name = #{hostname}"
|
114
|
+
results = do_request(:GET => '/nagui/nagios_live.cgi', :query => "query=#{CGI.escape(lql)}")
|
111
115
|
if results.kind_of?(Array) && results.length > 0
|
112
116
|
results[0]
|
113
117
|
else
|
@@ -115,6 +119,73 @@ class NOMS::Nagui < NOMS::HttpClient
|
|
115
119
|
end
|
116
120
|
end
|
117
121
|
|
122
|
+
def calc_time(str)
|
123
|
+
case str
|
124
|
+
when /(\d+)m/
|
125
|
+
#minutes
|
126
|
+
$1.to_i * 60
|
127
|
+
when /(\d+)h/
|
128
|
+
#hours
|
129
|
+
$1.to_i * 3600
|
130
|
+
when /(\d+)d/
|
131
|
+
#days
|
132
|
+
$1.to_i * 86400
|
133
|
+
else
|
134
|
+
str.to_i
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def process_command(cmd)
|
139
|
+
do_request(:POST => '/nagui/nagios_live.cgi', :body => "query=#{CGI.escape(cmd)}", :content_type => 'application/x-www-form-urlencoded')
|
140
|
+
end
|
141
|
+
|
142
|
+
def downtime_host(host,length,user,comment)
|
143
|
+
starttime=Time.now.to_i
|
144
|
+
endtime = starttime + calc_time(length)
|
145
|
+
cmd="COMMAND [#{Time.now.to_i}] SCHEDULE_HOST_DOWNTIME;#{host};#{starttime};#{endtime};1;0;0;#{user};#{comment}"
|
146
|
+
process_command(cmd)
|
147
|
+
end
|
148
|
+
def downtime_service(host,service,length,user,comment)
|
149
|
+
starttime=Time.now.to_i
|
150
|
+
endtime = starttime + calc_time(length)
|
151
|
+
cmd="COMMAND [#{Time.now.to_i}] SCHEDULE_SVC_DOWNTIME;#{host};#{service};#{starttime};#{endtime};1;0;0;#{user};#{comment}"
|
152
|
+
process_command(cmd)
|
153
|
+
end
|
154
|
+
def undowntime(host,service=nil)
|
155
|
+
if service == nil
|
156
|
+
host_record = host(host)
|
157
|
+
host_record['downtimes'].each do |id|
|
158
|
+
cmd = "COMMAND [#{Time.now.to_i}] DEL_HOST_DOWNTIME;#{id}"
|
159
|
+
process_command(cmd)
|
160
|
+
end
|
161
|
+
else
|
162
|
+
service_record = service(host,service)
|
163
|
+
service_record['downtimes'].each do |id|
|
164
|
+
cmd = "COMMAND [#{Time.now.to_i}] DEL_SVC_DOWNTIME;#{id}"
|
165
|
+
process_command(cmd)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def comment(host,service,user,comment)
|
171
|
+
if service == nil
|
172
|
+
cmd="COMMAND [#{Time.now.to_i}] ADD_HOST_COMMENT;#{host};1;#{user};#{comment}"
|
173
|
+
else
|
174
|
+
cmd="COMMAND [#{Time.now.to_i}] ADD_SVC_COMMENT;#{host};#{service};1;#{user};#{comment}"
|
175
|
+
end
|
176
|
+
process_command(cmd)
|
177
|
+
end
|
178
|
+
|
179
|
+
def ack_host(host,user,comment)
|
180
|
+
cmd="COMMAND [#{Time.now.to_i}] ACKNOWLEDGE_HOST_PROBLEM;#{host};0;1;1;#{user};#{comment}"
|
181
|
+
process_command(cmd)
|
182
|
+
end
|
183
|
+
|
184
|
+
def ack_service(host,service,user,comment)
|
185
|
+
cmd="COMMAND [#{Time.now.to_i}] ACKNOWLEDGE_SVC_PROBLEM;#{host};#{service};0;1;1;#{user};#{comment}"
|
186
|
+
process_command(cmd)
|
187
|
+
end
|
188
|
+
|
118
189
|
def nagcheck_host(host)
|
119
190
|
url = "/nagcheck/host/#{host}"
|
120
191
|
dbg("nagcheck url= #{url}")
|
data/spec/01nomscmdb_spec.rb
CHANGED
@@ -5,7 +5,11 @@ require 'spec_helper'
|
|
5
5
|
|
6
6
|
describe NOMS::CMDB do
|
7
7
|
|
8
|
-
before(:all)
|
8
|
+
before(:all) do
|
9
|
+
init_test
|
10
|
+
|
11
|
+
NOMS::CMDB.mock! nil
|
12
|
+
end
|
9
13
|
|
10
14
|
describe "#new" do
|
11
15
|
|
@@ -18,4 +22,41 @@ describe NOMS::CMDB do
|
|
18
22
|
|
19
23
|
end
|
20
24
|
|
25
|
+
describe "#create" do
|
26
|
+
|
27
|
+
before(:each) { @cmdb = NOMS::CMDB.new $opt }
|
28
|
+
|
29
|
+
it 'creates an entry' do
|
30
|
+
obj = @cmdb.create('system', { 'fqdn' => 'test0', 'inventory_component_type' => 'system'})
|
31
|
+
expect(obj).to have_key 'fqdn'
|
32
|
+
expect(obj).to have_key 'inventory_component_type'
|
33
|
+
expect(obj['fqdn']).to eq 'test0'
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'finds the same entry' do
|
37
|
+
@cmdb.create('system', { 'fqdn' => 'test2', 'inventory_component_type' => 'system'})
|
38
|
+
obj = @cmdb.system('test2')
|
39
|
+
expect(obj).to have_key 'fqdn'
|
40
|
+
expect(obj).to include 'inventory_component_type' => 'system'
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'creates an entry under the specified key' do
|
44
|
+
@cmdb.create('system', { 'inventory_component_type' => 'system' }, 'test3')
|
45
|
+
obj = @cmdb.system('test3')
|
46
|
+
expect(obj).to include 'fqdn' => 'test3'
|
47
|
+
expect(obj).to include 'inventory_component_type' => 'system'
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'fails to create a keyless entry' do
|
51
|
+
expect { @cmdb.create('system', { 'environment_name' => 'random' }) }.to raise_error(NOMS::Error)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'fails when different keys are specified' do
|
55
|
+
expect { @cmdb.create('system',
|
56
|
+
{ 'fqdn' => 'test3', 'environment_name' => 'random' },
|
57
|
+
'test4') }.to raise_error(NOMS::Error)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
21
62
|
end
|
data/spec/04cmdb-mock_spec.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
require 'noms/cmdb'
|
4
4
|
require 'spec_helper'
|
5
5
|
|
6
|
+
# The NOMS::CMDB::RestMock has
|
7
|
+
# a non-upserting PUT
|
6
8
|
describe NOMS::CMDB::RestMock do
|
7
9
|
|
8
10
|
before(:all) do
|
@@ -62,39 +64,28 @@ describe NOMS::CMDB::RestMock do
|
|
62
64
|
it 'synthesizes an id' do
|
63
65
|
result = @cmdb.do_request :POST => '/environments', :body => { 'name' => 'production' }
|
64
66
|
expect(result).to have_key 'id'
|
65
|
-
expect(@cmdb.all_data[$server][$cmdbapi + '/environments']).to include { |o| o['id']
|
67
|
+
expect(@cmdb.all_data[$server][$cmdbapi + '/environments']).to include { |o| o['id'] == result['id'] }
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'understands alternate id fields' do
|
71
|
+
result = @cmdb.do_request :POST => '/system', :body => {
|
72
|
+
'fqdn' => 'test0',
|
73
|
+
'environment_name' => 'production' }
|
74
|
+
expect(result).to have_key 'fqdn'
|
75
|
+
expect(@cmdb.all_data[$server][$cmdbapi + '/system']).to include { |o| o['fqdn'] == 'test0' }
|
66
76
|
end
|
67
77
|
|
68
78
|
end
|
69
79
|
|
70
80
|
context :PUT do
|
71
81
|
|
72
|
-
it '
|
73
|
-
@cmdb.do_request
|
74
|
-
|
75
|
-
expect(@cmdb.all_data[$server][$cmdbapi + '/environments']).to include { |o| o['name'] == 'production' }
|
76
|
-
end
|
77
|
-
|
78
|
-
it 'creates several new entries' do
|
79
|
-
@cmdb.do_request :PUT => '/environments/1', :body => {
|
80
|
-
'name' => 'production', 'environment_name' => 'production' }
|
81
|
-
10.times do |i|
|
82
|
-
@cmdb.do_request :PUT => "/environments/#{100 + i}", :body => {
|
83
|
-
'name' => "environment-#{i}", 'environment_name' => 'production' }
|
84
|
-
end
|
85
|
-
expect(@cmdb.all_data[$server][$cmdbapi + '/environments']).to have(11).items
|
86
|
-
expect(@cmdb.all_data[$server][$cmdbapi + '/environments']).to include { |o| o['name'] == 'production' }
|
87
|
-
end
|
88
|
-
|
89
|
-
it 'infers the id' do
|
90
|
-
result = @cmdb.do_request :PUT => '/environments/1', :body => { 'name' => 'production' }
|
91
|
-
expect(result).to have_key 'id'
|
92
|
-
expect(@cmdb.all_data[$server][$cmdbapi + '/environments'][0]).to have_key 'id'
|
93
|
-
expect(@cmdb.all_data[$server][$cmdbapi + '/environments'][0]['id']).to eq '1'
|
82
|
+
it 'fails to create a new entry' do
|
83
|
+
expect { @cmdb.do_request(:PUT => '/environments/1', :body => {
|
84
|
+
'name' => 'production', 'environment_name' => 'production' }) }.to raise_error(NOMS::Error)
|
94
85
|
end
|
95
86
|
|
96
87
|
it 'replaces an existing object' do
|
97
|
-
@cmdb.do_request :
|
88
|
+
@cmdb.do_request :POST => '/environments', :body => { 'id' => '1',
|
98
89
|
'name' => 'production', 'environment_name' => 'production' }
|
99
90
|
@cmdb.do_request :PUT => '/environments/1', :body => {
|
100
91
|
'name' => 'testing', 'note' => 'testing environment' }
|
@@ -110,7 +101,7 @@ describe NOMS::CMDB::RestMock do
|
|
110
101
|
def @cmdb.allow_partial_updates
|
111
102
|
true
|
112
103
|
end
|
113
|
-
@cmdb.do_request :
|
104
|
+
@cmdb.do_request :POST => '/environments', :body => { 'id' => '1',
|
114
105
|
'name' => 'production', 'environment_name' => 'production' }
|
115
106
|
@cmdb.do_request :PUT => '/environments/1', :body => {
|
116
107
|
'name' => 'testing', 'note' => 'testing environment' }
|
@@ -134,6 +125,16 @@ describe NOMS::CMDB::RestMock do
|
|
134
125
|
expect(result).to include 'name' => 'production'
|
135
126
|
end
|
136
127
|
|
128
|
+
it 'finds an existing entry by alternate id' do
|
129
|
+
@cmdb.do_request :POST => '/system', :body => {
|
130
|
+
'fqdn' => 'test0', 'environment_name' => 'production'
|
131
|
+
}
|
132
|
+
result = @cmdb.do_request :GET => '/system/test0'
|
133
|
+
expect(result).to be_a Hash
|
134
|
+
expect(result).to include 'fqdn' => 'test0'
|
135
|
+
expect(result).to include 'environment_name' => 'production'
|
136
|
+
end
|
137
|
+
|
137
138
|
it 'retrieves all entries' do
|
138
139
|
@cmdb.do_request :POST => '/environments', :body => {
|
139
140
|
'name' => 'production', 'environment_name' => 'production'
|
@@ -151,7 +152,7 @@ describe NOMS::CMDB::RestMock do
|
|
151
152
|
|
152
153
|
it 'raises an exception for missing entries' do
|
153
154
|
expect { @cmdb.do_request :GET => '/environments/1' }.to raise_error
|
154
|
-
@cmdb.do_request :
|
155
|
+
@cmdb.do_request :POST => '/environments', :body => { 'id' => '1', 'name' => 'production' }
|
155
156
|
expect { @cmdb.do_request :GET => '/chorizo' }.to raise_error
|
156
157
|
expect { @cmdb.do_request :GET => '/environments/2' }.to raise_error
|
157
158
|
end
|
@@ -161,16 +162,16 @@ describe NOMS::CMDB::RestMock do
|
|
161
162
|
context :DELETE do
|
162
163
|
|
163
164
|
it 'deletes an existing entry' do
|
164
|
-
@cmdb.do_request :
|
165
|
+
@cmdb.do_request :POST => '/environments', :body => { 'id' => '1', 'name' => 'production' }
|
165
166
|
result = @cmdb.do_request :DELETE => '/environments/1'
|
166
167
|
expect(result).to be true
|
167
168
|
expect(@cmdb.all_data[$server][$cmdbapi + '/environments']).to have(0).items
|
168
169
|
end
|
169
170
|
|
170
171
|
it 'deletes an existing entry among many' do
|
171
|
-
@cmdb.do_request :
|
172
|
+
@cmdb.do_request :POST => '/environments', :body => { 'id' => '1', 'name' => 'production' }
|
172
173
|
10.times do |i|
|
173
|
-
@cmdb.do_request :
|
174
|
+
@cmdb.do_request :POST => "/environments", :body => { 'id' => "#{100 + i}",
|
174
175
|
'name' => "environment-#{i}", 'environment_name' => 'production' }
|
175
176
|
end
|
176
177
|
@cmdb.do_request :DELETE => '/environments/103'
|
@@ -180,14 +181,14 @@ describe NOMS::CMDB::RestMock do
|
|
180
181
|
end
|
181
182
|
|
182
183
|
it 'deletes a whole collection' do
|
183
|
-
@cmdb.do_request :
|
184
|
+
@cmdb.do_request :POST => '/environments', :body => {'id' => '1', 'name' => 'production' }
|
184
185
|
@cmdb.do_request :DELETE => '/environments'
|
185
186
|
expect(@cmdb.all_data[$server]).not_to have_key '/environments'
|
186
187
|
end
|
187
188
|
|
188
189
|
it 'raises an exception for nonexistent entries' do
|
189
190
|
expect { @cmdb.do_request :DELETE => '/environments/2' }.to raise_error
|
190
|
-
@cmdb.do_request :
|
191
|
+
@cmdb.do_request :POST => '/environments', :body => { 'id' => '1', 'name' => 'production' }
|
191
192
|
expect { @cmdb.do_request :DELETE => '/environments/2' }.to raise_error
|
192
193
|
expect { @cmdb.do_request :DELETE => '/chorizo' }.to raise_error
|
193
194
|
end
|
@@ -42,10 +42,11 @@ describe NOMS::CMDB::RestMock do
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
context :
|
45
|
+
context :POST do
|
46
46
|
|
47
47
|
it 'creates a new entry' do
|
48
|
-
@cmdb.do_request :
|
48
|
+
@cmdb.do_request :POST => '/environments', :body => {
|
49
|
+
'id' => 'production',
|
49
50
|
'name' => 'production', 'environment_name' => 'production' }
|
50
51
|
expect(File.exist? $datafile).to be true
|
51
52
|
end
|
data/spec/06noms-mock.sh
CHANGED
@@ -33,3 +33,22 @@ teardown() {
|
|
33
33
|
EOF
|
34
34
|
noms --config=test/etc/noms.conf --mock=test/data.json cmdb show test1.example.com
|
35
35
|
}
|
36
|
+
|
37
|
+
@test "noms-mock cmdb add" {
|
38
|
+
cat >test/data.json <<EOF
|
39
|
+
{ "cmdb": {
|
40
|
+
"/cmdb_api/v1/system": [
|
41
|
+
{ "id": "test1.example.com",
|
42
|
+
"fqdn": "test1.example.com",
|
43
|
+
"status": "idle",
|
44
|
+
"environment": "testing",
|
45
|
+
"data_center_code": "DC1",
|
46
|
+
"ip_address": "10.0.0.1" }
|
47
|
+
]
|
48
|
+
}
|
49
|
+
}
|
50
|
+
EOF
|
51
|
+
noms --config=test/etc/noms.conf --mock=test/data.json cmdb add test2.example.com inventory_component_type=system
|
52
|
+
type=$(noms --config=test/etc/noms.conf --mock=test/data.json --nolabel cmdb show test2.example.com inventory_component_type)
|
53
|
+
[ "$type" = 'system' ]
|
54
|
+
}
|
metadata
CHANGED
@@ -1,18 +1,20 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: noms-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.12.0
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Jeremy Brinkley
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2016-01-27 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: bundler
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
16
18
|
requirements:
|
17
19
|
- - ~>
|
18
20
|
- !ruby/object:Gem::Version
|
@@ -20,6 +22,7 @@ dependencies:
|
|
20
22
|
type: :development
|
21
23
|
prerelease: false
|
22
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
23
26
|
requirements:
|
24
27
|
- - ~>
|
25
28
|
- !ruby/object:Gem::Version
|
@@ -27,6 +30,7 @@ dependencies:
|
|
27
30
|
- !ruby/object:Gem::Dependency
|
28
31
|
name: rake
|
29
32
|
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
30
34
|
requirements:
|
31
35
|
- - ~>
|
32
36
|
- !ruby/object:Gem::Version
|
@@ -34,6 +38,7 @@ dependencies:
|
|
34
38
|
type: :development
|
35
39
|
prerelease: false
|
36
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
37
42
|
requirements:
|
38
43
|
- - ~>
|
39
44
|
- !ruby/object:Gem::Version
|
@@ -41,43 +46,49 @@ dependencies:
|
|
41
46
|
- !ruby/object:Gem::Dependency
|
42
47
|
name: rspec
|
43
48
|
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
44
50
|
requirements:
|
45
|
-
- - '>='
|
51
|
+
- - ! '>='
|
46
52
|
- !ruby/object:Gem::Version
|
47
53
|
version: '0'
|
48
54
|
type: :development
|
49
55
|
prerelease: false
|
50
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
51
58
|
requirements:
|
52
|
-
- - '>='
|
59
|
+
- - ! '>='
|
53
60
|
- !ruby/object:Gem::Version
|
54
61
|
version: '0'
|
55
62
|
- !ruby/object:Gem::Dependency
|
56
63
|
name: rspec-collection_matchers
|
57
64
|
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
58
66
|
requirements:
|
59
|
-
- - '>='
|
67
|
+
- - ! '>='
|
60
68
|
- !ruby/object:Gem::Version
|
61
69
|
version: '0'
|
62
70
|
type: :development
|
63
71
|
prerelease: false
|
64
72
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
65
74
|
requirements:
|
66
|
-
- - '>='
|
75
|
+
- - ! '>='
|
67
76
|
- !ruby/object:Gem::Version
|
68
77
|
version: '0'
|
69
78
|
- !ruby/object:Gem::Dependency
|
70
79
|
name: optconfig
|
71
80
|
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
72
82
|
requirements:
|
73
|
-
- - '>='
|
83
|
+
- - ! '>='
|
74
84
|
- !ruby/object:Gem::Version
|
75
85
|
version: '0'
|
76
86
|
type: :runtime
|
77
87
|
prerelease: false
|
78
88
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
79
90
|
requirements:
|
80
|
-
- - '>='
|
91
|
+
- - ! '>='
|
81
92
|
- !ruby/object:Gem::Version
|
82
93
|
version: '0'
|
83
94
|
description:
|
@@ -93,11 +104,11 @@ files:
|
|
93
104
|
- Gemfile
|
94
105
|
- LICENSE
|
95
106
|
- README.rst
|
107
|
+
- ROADMAP.rst
|
96
108
|
- Rakefile
|
97
109
|
- TODO
|
98
110
|
- bin/ansible-cmdb
|
99
111
|
- bin/noms
|
100
|
-
- control.m4
|
101
112
|
- etc/noms.conf
|
102
113
|
- lib/ncc/client.rb
|
103
114
|
- lib/noms/client/version.rb
|
@@ -117,26 +128,27 @@ files:
|
|
117
128
|
homepage: http://github.com/evernote/noms-client
|
118
129
|
licenses:
|
119
130
|
- Apache-2
|
120
|
-
metadata: {}
|
121
131
|
post_install_message:
|
122
132
|
rdoc_options: []
|
123
133
|
require_paths:
|
124
134
|
- lib
|
125
135
|
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
126
137
|
requirements:
|
127
|
-
- - '>='
|
138
|
+
- - ! '>='
|
128
139
|
- !ruby/object:Gem::Version
|
129
140
|
version: '0'
|
130
141
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
142
|
+
none: false
|
131
143
|
requirements:
|
132
|
-
- - '>='
|
144
|
+
- - ! '>='
|
133
145
|
- !ruby/object:Gem::Version
|
134
146
|
version: '0'
|
135
147
|
requirements: []
|
136
148
|
rubyforge_project:
|
137
|
-
rubygems_version:
|
149
|
+
rubygems_version: 1.8.23
|
138
150
|
signing_key:
|
139
|
-
specification_version:
|
151
|
+
specification_version: 3
|
140
152
|
summary: Client libraries and command-line tool for NOMS components
|
141
153
|
test_files:
|
142
154
|
- spec/01nomscmdb_spec.rb
|
checksums.yaml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz: 9e24aa514edf837a8d6d263ffe707e86aefcc76f
|
4
|
-
data.tar.gz: 09e54474d3b41a4a257fa1cf98a248ad16b66cd5
|
5
|
-
SHA512:
|
6
|
-
metadata.gz: c6707f63d16653725c7d9e00be5421e282db4a7195a932bd793643021f70ef8da14a04d698bcd940c1fc41ffa3d11613ec24180bb54b59e357ccb9970fb7bc1e
|
7
|
-
data.tar.gz: 35bf1ffa2984e3f338927060d46f1021b8c2b21b09d8656f70cd965468c920800aaf4752995cc3435f9cc7679767781d3f224a1cd32aac131946042e3c0f523f
|
data/control.m4
DELETED