state_mate 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +7 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +29 -0
  7. data/Rakefile +6 -0
  8. data/ansible/library/state +59 -0
  9. data/lib/state_mate.rb +366 -0
  10. data/lib/state_mate/adapters/defaults.rb +330 -0
  11. data/lib/state_mate/adapters/git_config.rb +35 -0
  12. data/lib/state_mate/adapters/json.rb +66 -0
  13. data/lib/state_mate/adapters/launchd.rb +117 -0
  14. data/lib/state_mate/adapters/nvram.rb +44 -0
  15. data/lib/state_mate/adapters/time_machine.rb +60 -0
  16. data/lib/state_mate/version.rb +3 -0
  17. data/notes/state-set-steps.md +26 -0
  18. data/spec/spec_helper.rb +44 -0
  19. data/spec/state_mate/adapters/defaults/hardware_uuid_spec.rb +15 -0
  20. data/spec/state_mate/adapters/defaults/hash_deep_write_spec.rb +33 -0
  21. data/spec/state_mate/adapters/defaults/read_defaults_spec.rb +57 -0
  22. data/spec/state_mate/adapters/defaults/read_spec.rb +32 -0
  23. data/spec/state_mate/adapters/defaults/read_type_spec.rb +27 -0
  24. data/spec/state_mate/adapters/defaults/write_spec.rb +29 -0
  25. data/spec/state_mate/adapters/git_config/read_spec.rb +36 -0
  26. data/spec/state_mate/adapters/git_config/write_spec.rb +17 -0
  27. data/spec/state_mate/adapters/json/read_spec.rb +56 -0
  28. data/spec/state_mate/adapters/json/write_spec.rb +54 -0
  29. data/spec/state_mate_spec.rb +7 -0
  30. data/state_mate.gemspec +25 -0
  31. data/test/ansible/ansible.cfg +5 -0
  32. data/test/ansible/clear +2 -0
  33. data/test/ansible/hosts +1 -0
  34. data/test/ansible/play +2 -0
  35. data/test/ansible/playbook.yml +38 -0
  36. data/test/ansible/read +2 -0
  37. 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,3 @@
1
+ module StateMate
2
+ VERSION = "0.0.1"
3
+ end
@@ -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.
@@ -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