state_mate 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +6 -0
- data/ansible/library/state +59 -0
- data/lib/state_mate.rb +366 -0
- data/lib/state_mate/adapters/defaults.rb +330 -0
- data/lib/state_mate/adapters/git_config.rb +35 -0
- data/lib/state_mate/adapters/json.rb +66 -0
- data/lib/state_mate/adapters/launchd.rb +117 -0
- data/lib/state_mate/adapters/nvram.rb +44 -0
- data/lib/state_mate/adapters/time_machine.rb +60 -0
- data/lib/state_mate/version.rb +3 -0
- data/notes/state-set-steps.md +26 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/state_mate/adapters/defaults/hardware_uuid_spec.rb +15 -0
- data/spec/state_mate/adapters/defaults/hash_deep_write_spec.rb +33 -0
- data/spec/state_mate/adapters/defaults/read_defaults_spec.rb +57 -0
- data/spec/state_mate/adapters/defaults/read_spec.rb +32 -0
- data/spec/state_mate/adapters/defaults/read_type_spec.rb +27 -0
- data/spec/state_mate/adapters/defaults/write_spec.rb +29 -0
- data/spec/state_mate/adapters/git_config/read_spec.rb +36 -0
- data/spec/state_mate/adapters/git_config/write_spec.rb +17 -0
- data/spec/state_mate/adapters/json/read_spec.rb +56 -0
- data/spec/state_mate/adapters/json/write_spec.rb +54 -0
- data/spec/state_mate_spec.rb +7 -0
- data/state_mate.gemspec +25 -0
- data/test/ansible/ansible.cfg +5 -0
- data/test/ansible/clear +2 -0
- data/test/ansible/hosts +1 -0
- data/test/ansible/play +2 -0
- data/test/ansible/playbook.yml +38 -0
- data/test/ansible/read +2 -0
- metadata +167 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'nrser'
|
2
|
+
require 'nrser/exec'
|
3
|
+
|
4
|
+
using NRSER
|
5
|
+
|
6
|
+
module StateMate; end
|
7
|
+
module StateMate::Adapters; end
|
8
|
+
|
9
|
+
module StateMate::Adapters::NVRAM
|
10
|
+
def self.read key, options = {}
|
11
|
+
cmd = NRSER::Exec.sub "nvram %{key}", key: key
|
12
|
+
|
13
|
+
begin
|
14
|
+
output = NRSER::Exec.run cmd
|
15
|
+
rescue SystemCallError => e
|
16
|
+
if e.message.include? "nvram: Error getting variable"
|
17
|
+
return nil
|
18
|
+
else
|
19
|
+
raise e
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if m = /^#{ key }\t(.*)\n$/.match(output)
|
24
|
+
m[1]
|
25
|
+
else
|
26
|
+
raise tpl binding, <<-BLOCK
|
27
|
+
can't parse output for key <%= key.inspect %>:
|
28
|
+
|
29
|
+
cmd: <%= cmd.inspect %>
|
30
|
+
|
31
|
+
output: <%= output.inspect %>
|
32
|
+
BLOCK
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.write key, value, options = {}
|
37
|
+
unless value.is_a? String
|
38
|
+
raise "value must be a String, not #{ value.inspect }"
|
39
|
+
end
|
40
|
+
|
41
|
+
cmd = "nvram #{ key }='#{ value }'"
|
42
|
+
NRSER::Exec.run cmd
|
43
|
+
end
|
44
|
+
end # NVRAM
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
require 'CFPropertyList'
|
4
|
+
|
5
|
+
require 'nrser'
|
6
|
+
require 'nrser/exec'
|
7
|
+
|
8
|
+
using NRSER
|
9
|
+
|
10
|
+
module StateMate; end;
|
11
|
+
module StateMate::Adapters; end
|
12
|
+
|
13
|
+
module StateMate::Adapters::TimeMachine
|
14
|
+
EXE = '/usr/bin/tmutil'
|
15
|
+
PLIST_PATH = '/Library/Preferences/com.apple.TimeMachine.plist'
|
16
|
+
|
17
|
+
def self.local_enabled?
|
18
|
+
# seems to change the key
|
19
|
+
#
|
20
|
+
# /Library/Preferences/com.apple.TimeMachine.plist:MobileBackups
|
21
|
+
#
|
22
|
+
plist = CFPropertyList::List.new file: PLIST_PATH
|
23
|
+
data = CFPropertyList.native_types plist.value
|
24
|
+
data['MobileBackups']
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.enable_local
|
28
|
+
NRSER::Exec.run "%{exe} enablelocal", exe: EXE
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.disable_local
|
32
|
+
NRSER::Exec.run "%{exe} disablelocal", exe: EXE
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.read key, options = {}
|
36
|
+
case key
|
37
|
+
when 'local_backups'
|
38
|
+
local_enabled?
|
39
|
+
else
|
40
|
+
raise "bad key: #{ key.inspect }"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.write key, value, options = {}
|
45
|
+
case key
|
46
|
+
when 'local_backups'
|
47
|
+
case value
|
48
|
+
when true
|
49
|
+
enable_local
|
50
|
+
when false
|
51
|
+
disable_local
|
52
|
+
else
|
53
|
+
raise "bad value: #{ value.inspect }"
|
54
|
+
end
|
55
|
+
else
|
56
|
+
raise "bad key: #{ key.inspect }"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end # TimeMachine
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# StateSet steps
|
2
|
+
|
3
|
+
how creating and applying a StateSet should happen.
|
4
|
+
|
5
|
+
a StateSet is a set of (adapter, key, directive, value) tuples.
|
6
|
+
|
7
|
+
{
|
8
|
+
'defaults': [
|
9
|
+
{
|
10
|
+
'key': 'k',
|
11
|
+
'<set | unset | array_contains':
|
12
|
+
}
|
13
|
+
]
|
14
|
+
}
|
15
|
+
|
16
|
+
1. read and store (in the StateSet object) the current value for all keys.
|
17
|
+
|
18
|
+
2. see if the any of the values should be changed. if there are no changes needed, return.
|
19
|
+
|
20
|
+
3. change the needed values on the ruby object represntation. this will raise errors if clobber / create have problems.
|
21
|
+
|
22
|
+
4. iterate through each value that was changed and attempt the write through the adapter.
|
23
|
+
|
24
|
+
5. if any write produces an error, try to write the values that have been changed back to their original value. if this produces an error, leave them and report the problems.
|
25
|
+
|
26
|
+
6.
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'state_mate'
|
3
|
+
|
4
|
+
require 'nrser'
|
5
|
+
|
6
|
+
DOMAIN = 'com.nrser.state_mate'
|
7
|
+
|
8
|
+
shared_context "#{ DOMAIN } empty" do
|
9
|
+
before(:each) {
|
10
|
+
`defaults delete #{ DOMAIN } 2>&1 > /dev/null`
|
11
|
+
`defaults -currentHost delete #{ DOMAIN } 2>&1 > /dev/null`
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
shared_context "defaults" do
|
16
|
+
let(:defaults) {
|
17
|
+
StateMate::Adapters::Defaults
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
shared_context "git_config" do
|
22
|
+
let(:git_config) {
|
23
|
+
StateMate::Adapters::GitConfig
|
24
|
+
}
|
25
|
+
|
26
|
+
let(:section) {
|
27
|
+
"statemate"
|
28
|
+
}
|
29
|
+
|
30
|
+
let(:key) {
|
31
|
+
"#{ section }.test"
|
32
|
+
}
|
33
|
+
|
34
|
+
before(:each) {
|
35
|
+
`git config --global --unset-all #{ key } 2>&1`
|
36
|
+
}
|
37
|
+
|
38
|
+
after(:each) {
|
39
|
+
`git config --global --unset-all #{ key } 2>&1`
|
40
|
+
`git config --global --remove-section #{ section } 2>&1`
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'state_mate/adapters/defaults'
|
4
|
+
|
5
|
+
describe "StateMate::Adapters::Defaults.hardware_uuid" do
|
6
|
+
let(:defaults) {
|
7
|
+
StateMate::Adapters::Defaults
|
8
|
+
}
|
9
|
+
|
10
|
+
it "returns something that looks like an apple hardware uuid" do
|
11
|
+
expect( defaults.hardware_uuid ).to(
|
12
|
+
match /[0-9A-F]{8}\-[0-9A-F]{4}\-[0-9A-F]{4}\-[0-9A-F]{4}\-[0-9A-F]{12}/
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end # hardware_uuid
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'state_mate/adapters/defaults'
|
4
|
+
|
5
|
+
describe "StateMate::Adapters::Defaults.hash_deep_write!" do
|
6
|
+
let(:defaults) {
|
7
|
+
StateMate::Adapters::Defaults
|
8
|
+
}
|
9
|
+
|
10
|
+
it "does a basic set on an empty hash" do
|
11
|
+
h = {}
|
12
|
+
defaults.hash_deep_write! h, [:x], 1
|
13
|
+
expect( h ).to eq({x: 1})
|
14
|
+
end
|
15
|
+
|
16
|
+
it "does a deep set on an empty hash" do
|
17
|
+
h = {}
|
18
|
+
defaults.hash_deep_write! h, [:x, :y], 1
|
19
|
+
expect( h ).to eq({x: {y: 1}})
|
20
|
+
end
|
21
|
+
|
22
|
+
it "does a deep set on an non-empty hash" do
|
23
|
+
h = {a: 1}
|
24
|
+
defaults.hash_deep_write! h, [:x, :y], 1
|
25
|
+
expect( h ).to eq({a: 1, x: {y: 1}})
|
26
|
+
end
|
27
|
+
|
28
|
+
it "clobbers values" do
|
29
|
+
h = {x: [1, 2, 3]}
|
30
|
+
defaults.hash_deep_write! h, [:x, :y], 1
|
31
|
+
expect( h ).to eq({x: {y: 1}})
|
32
|
+
end
|
33
|
+
end # hardware_uuid
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'state_mate/adapters/defaults'
|
4
|
+
|
5
|
+
describe "StateMate::Adapters::Defaults.read_defaults" do
|
6
|
+
include_context "defaults"
|
7
|
+
include_context "#{ DOMAIN } empty"
|
8
|
+
|
9
|
+
let(:key) {
|
10
|
+
'x'
|
11
|
+
}
|
12
|
+
|
13
|
+
it "returns an empty hash when the domain is empty" do
|
14
|
+
expect( defaults.read_defaults DOMAIN ).to eq({})
|
15
|
+
end
|
16
|
+
|
17
|
+
context "string value" do
|
18
|
+
|
19
|
+
let(:string) {
|
20
|
+
'en_US@currency=USD'
|
21
|
+
}
|
22
|
+
|
23
|
+
before(:each) {
|
24
|
+
`defaults write #{ DOMAIN } #{ key } -string '#{ string }'`
|
25
|
+
}
|
26
|
+
|
27
|
+
it "reads the domain with a string in it" do
|
28
|
+
expect( defaults.read_defaults DOMAIN ).to eq({key => string})
|
29
|
+
end
|
30
|
+
|
31
|
+
it "still reads the current host domain as empty" do
|
32
|
+
expect( defaults.read_defaults DOMAIN, true ).to eq({})
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
context "string value in current host" do
|
38
|
+
|
39
|
+
let(:string) {
|
40
|
+
'en_US@currency=USD'
|
41
|
+
}
|
42
|
+
|
43
|
+
before(:each) {
|
44
|
+
`defaults -currentHost write #{ DOMAIN } #{ key } -string '#{ string }'`
|
45
|
+
}
|
46
|
+
|
47
|
+
it "reads the domain with a string in it" do
|
48
|
+
expect( defaults.read_defaults DOMAIN, true ).to eq({key => string})
|
49
|
+
end
|
50
|
+
|
51
|
+
it "still reads the current host domain as empty" do
|
52
|
+
expect( defaults.read_defaults DOMAIN, false ).to eq({})
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'state_mate/adapters/defaults'
|
4
|
+
|
5
|
+
describe "StateMate::Adapters::Defaults.read" do
|
6
|
+
include_context "defaults"
|
7
|
+
include_context "#{ DOMAIN } empty"
|
8
|
+
|
9
|
+
let(:key) {
|
10
|
+
'x'
|
11
|
+
}
|
12
|
+
|
13
|
+
it "returns nil when the key is missing" do
|
14
|
+
expect( defaults.read [DOMAIN, key] ).to be nil
|
15
|
+
end
|
16
|
+
|
17
|
+
context "string value with @ in it" do
|
18
|
+
|
19
|
+
let(:string) {
|
20
|
+
'en_US@currency=USD'
|
21
|
+
}
|
22
|
+
|
23
|
+
before(:each) {
|
24
|
+
`defaults write #{ DOMAIN } #{ key } -string '#{ string }'`
|
25
|
+
}
|
26
|
+
|
27
|
+
it "reads a string with an @ in it" do
|
28
|
+
expect( defaults.read [DOMAIN, key] ).to eq string
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'state_mate/adapters/defaults'
|
4
|
+
|
5
|
+
describe "StateMate::Adapters::Defaults.read_type" do
|
6
|
+
include_context "defaults"
|
7
|
+
include_context "#{ DOMAIN } empty"
|
8
|
+
|
9
|
+
{
|
10
|
+
string: 'en_US@currency=USD',
|
11
|
+
data: '62706c697374',
|
12
|
+
int: '1',
|
13
|
+
float: '1',
|
14
|
+
bool: 'true',
|
15
|
+
date: '2014-03-27',
|
16
|
+
array: '1 2 3',
|
17
|
+
dict: 'x 1 y 2',
|
18
|
+
}.each do |type, input|
|
19
|
+
|
20
|
+
it "reads a #{ type } type" do
|
21
|
+
`defaults write #{ DOMAIN } x -#{ type } #{ input }`
|
22
|
+
expect( defaults.read_type DOMAIN, 'x', false ).to be type
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'state_mate/adapters/defaults'
|
4
|
+
|
5
|
+
describe "StateMate::Adapters::Defaults.read" do
|
6
|
+
include_context "defaults"
|
7
|
+
include_context "#{ DOMAIN } empty"
|
8
|
+
|
9
|
+
let(:key) {
|
10
|
+
'x'
|
11
|
+
}
|
12
|
+
|
13
|
+
context "key has string value" do
|
14
|
+
|
15
|
+
let(:string) {
|
16
|
+
'blah!'
|
17
|
+
}
|
18
|
+
|
19
|
+
before(:each) {
|
20
|
+
`defaults write #{ DOMAIN } #{ key } -string '#{ string }' 2>&1`
|
21
|
+
}
|
22
|
+
|
23
|
+
it "deletes value" do
|
24
|
+
defaults.write [DOMAIN, key], nil
|
25
|
+
expect( defaults.read [DOMAIN, key] ).to eq nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'state_mate/adapters/git_config'
|
4
|
+
|
5
|
+
describe "StateMate::Adapters::GitConfig.read" do
|
6
|
+
include_context "git_config"
|
7
|
+
|
8
|
+
it "reads a missing key as nil" do
|
9
|
+
expect( git_config.read key ).to eq nil
|
10
|
+
end
|
11
|
+
|
12
|
+
context "bad key" do
|
13
|
+
let(:bad_key) {
|
14
|
+
"state_mate.test"
|
15
|
+
}
|
16
|
+
|
17
|
+
it "should error" do
|
18
|
+
expect{ git_config.read bad_key }.to raise_error SystemCallError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "has a value" do
|
23
|
+
let(:value) {
|
24
|
+
"blah"
|
25
|
+
}
|
26
|
+
|
27
|
+
before(:each) {
|
28
|
+
`git config --global --add #{ key } #{ value }`
|
29
|
+
}
|
30
|
+
|
31
|
+
it "should read the value" do
|
32
|
+
expect( git_config.read key ).to eq value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'state_mate/adapters/git_config'
|
4
|
+
|
5
|
+
describe "StateMate::Adapters::GitConfig.read" do
|
6
|
+
include_context "git_config"
|
7
|
+
|
8
|
+
let(:value) {
|
9
|
+
"blah"
|
10
|
+
}
|
11
|
+
|
12
|
+
it "writes a value" do
|
13
|
+
git_config.write key, value
|
14
|
+
|
15
|
+
expect( `git config --global --get #{ key }`.chomp ).to eq "#{ value }"
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
require 'state_mate/adapters/json'
|
6
|
+
|
7
|
+
describe "StateMate::Adapters::JSON.read" do
|
8
|
+
include_context "json"
|
9
|
+
|
10
|
+
it "reads an empty file as nil" do
|
11
|
+
expect( json.read [filepath] ).to be nil
|
12
|
+
end
|
13
|
+
|
14
|
+
context "file with a string value in it" do
|
15
|
+
let(:value) {
|
16
|
+
"blah"
|
17
|
+
}
|
18
|
+
|
19
|
+
before(:each) {
|
20
|
+
File.open(filepath, 'w') {|f|
|
21
|
+
f.write JSON.dump(value)
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
it "should read the value" do
|
26
|
+
expect( json.read [filepath] ).to eq value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "file with a dict value in it" do
|
31
|
+
let(:value) {
|
32
|
+
{
|
33
|
+
'x' => 1,
|
34
|
+
'y' => 2,
|
35
|
+
'z' => {
|
36
|
+
'a' => 3,
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
before(:each) {
|
42
|
+
File.open(filepath, 'w') {|f|
|
43
|
+
f.write JSON.dump(value)
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
it "should read the root value" do
|
48
|
+
expect( json.read [filepath] ).to eq value
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should read a deeper value" do
|
52
|
+
expect( json.read "#{ filepath }:z:a" ).to eq 3
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|