state_mate 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|