monkey_king 0.1.5 → 0.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: beb82abaa898573f203304c24f512196d2188232
4
- data.tar.gz: b303c14f5e6000d5287456d75db0ca2c39570be9
3
+ metadata.gz: c24e35b55ba57f034926c86a82c3a095b46addeb
4
+ data.tar.gz: 254ebb7b6fcff6acc074a2dbcd5b88ea49668d2f
5
5
  SHA512:
6
- metadata.gz: 4a5aabc89c3e9404a0df74936f165f37c3db1c5fcc8dc917a48e18969d6f1b8d163ea3ec249ba43349ed994123a05a3dfadfec84b340ebdd50b1d366d98b7588
7
- data.tar.gz: 20c4198faa50e4a02a4ec7949ff32f9f19570b1da17ab3fb24f8b17e83cba75014e5011a8e734539e38b1d5b2188084610cf2d04c076eea6403f644cfa350944
6
+ metadata.gz: 626b160499b2067a239196a0351b38fb63b4d377e0c85b54092d1ae466128a118800098ff860a7a10b7128b78e8ced7b9ff999dd700cc9f395f69dcc00c72516
7
+ data.tar.gz: d4a51f2ee54a8d9010397c49a61b05ec6bd3ad597291b54109f79cdbb15c860e2a80d7acb031ce5aeb5d14905031e3f3310846ecee73c5edc3058d8d4f49cac2
data/Gemfile CHANGED
@@ -8,4 +8,5 @@ gem 'climate_control'
8
8
  gem 'pry-byebug'
9
9
  gem 'mothership'
10
10
  gem 'highline'
11
+ gem 'sexpistol'
11
12
 
data/README.md CHANGED
@@ -2,30 +2,32 @@
2
2
 
3
3
  Monkey king is a tool which is initially designed for generating deployment manifests for a bosh deployment based on an existing deployment, and it could also be used for other purposes like performing functions on keys/values in yaml files based on yaml tags.
4
4
 
5
+ Here is some scenarios about how it works, some of these examples are in the `./fixtures/` directory:
5
6
 
6
- Here is some scenarios about how it works:
7
7
  ## Using secret generation
8
- The `!MK:secret` directive generates a random secret of the same length as the value it replaces.
9
- BEFORE
8
+ The `!MK:secret(<password_length>)` directive generates a random secret using [a-Z,0-9] with the length specified in the parameter.
9
+
10
+ Before:
11
+
10
12
  ```
11
13
  ---
12
14
  meta:
13
- secret: !MK:secret old_secret
14
- another_secret: !MK:secret old_secret
15
+ secret: !MK:secret(12) replace_me
16
+ another_secret: !MK:secret replace_me
15
17
  not_secret: not_secret
16
18
  ```
17
19
 
18
- AFTER
20
+ After:
19
21
  ```
20
22
  ---
21
23
  meta:
22
- secret: !MK:secret new_secret
23
- another_secret: !MK:secret new_secret
24
+ secret: !MK:secret(12) SomeRandomPass
25
+ another_secret: !MK:secret() AnotherRandomPass
24
26
  not_secret: not_secret
25
27
  ```
26
28
 
27
29
  ## Using environment variables
28
- The `!MK:env:<variable_name>` directive pulls values from environment variables and replaces the tagged keys/values
30
+ The `!MK:env(<variable_name>)` directive pulls values from environment variables and replaces the tagged keys/values
29
31
 
30
32
  Given:
31
33
 
@@ -40,10 +42,10 @@ Before:
40
42
  meta1:
41
43
  not_secret: not_secret
42
44
  layer1:
43
- - id1: !MK:env:id1 id1_before
45
+ - id1: !MK:env(id1) id1_before
44
46
  - layer2:
45
- - id2: !MK:env:id2 id2_before
46
- - !MK:env:id1 id3: !MK:env:id1 id1_before
47
+ - id2: !MK:env(id2) id2_before
48
+ - !MK:env(id1) id3: !MK:env:id1 id1_before
47
49
  ```
48
50
 
49
51
  After:
@@ -52,13 +54,110 @@ After:
52
54
  meta1:
53
55
  not_secret: not_secret
54
56
  layer1:
55
- - id1: !MK:env:id1 id1_from_env
57
+ - id1: !MK:env(id1) id1_from_env
56
58
  - layer2:
57
- - id2: !MK:env:id2 id2_from_env
58
- - !MK:env:id1 id1_from_env: !MK:env:id1 id1_from_env
59
+ - id2: !MK:env(id2) id2_from_env
60
+ - !MK:env(id1) id1_from_env: !MK:env:id1 id1_from_env
61
+ ```
62
+
63
+ ## Using Read and Write
64
+ The `!MK:read(<variable_name>)` and `!MK:write(<variable_name>,<value>)` directive is used to save the generated value and use it later.
65
+
66
+ **NOTE: at this time, reads must be ordered after writes in the YAML document until we implement a dependency graph.**
67
+
68
+ Before:
69
+
70
+ ```
71
+ ---
72
+ meta:
73
+ secret: !MK:write(nat_secret,secret(12)) replace_me
74
+ same_secret_again: !MK:read(nat_secret) replace_me
75
+ ```
76
+
77
+ After:
78
+
79
+ ```
80
+ ---
81
+ meta:
82
+ secret: !MK:write(nat_secret,secret(12)) SAME_PASSWORD_HERE
83
+ same_secret_again: !MK:read(nat_secret) SAME_PASSWORD_HERE
84
+ ```
85
+
86
+ # Using string format
87
+ The `!MK:format(<variable_1>,<variable_2>,...,<string>)` directive can be used to format the string given in the yaml field. This is usally used with `write_value` directive which store the template to a variable.
88
+
89
+ **NOTE: The string literal must be defined outside of the YAML tag, as there is a limited set of allowed characters in YAML tags. See usage of `nat_template` in example below**
90
+
91
+ Given:
92
+
93
+ ```
94
+ export NAT_HOST=10.10.0.6
95
+ ```
96
+
97
+ Before:
98
+
99
+ ```
100
+ ---
101
+ nat_template: write_value(TEMPLATE) https://%s
102
+ meta:
103
+ nat_url: !MK:format(env(NAT_HOST),read(TEMPLATE)) replaceme
104
+ ```
105
+
106
+ After:
107
+ ```
108
+ ---
109
+ nat_template: write_value(TEMPLATE) https://%s
110
+ meta:
111
+ nat_url: !MK:format(env(NAT_HOST),read(TEMPLATE)) https://10.10.0.6
112
+ ```
113
+
114
+ ## Combine them all
115
+ You can combine the directive in a LISP-like syntax to create more poweful usages:
116
+
117
+ ####Example:
118
+
119
+ Given:
120
+
121
+ ```
122
+ export NATS_USER=nats_user
123
+ export NATS_HOST=10.10.0.6
124
+ ```
125
+
126
+ Before:
127
+
128
+ ```
129
+ nat_template_1: !MK:write_value(NAT_TEMPLATE_1) https://%s:%s@%s
130
+ nat_template_2: !MK:write_value(NAT_TEMPLATE_2) '%s/info'
131
+
132
+ meta1:
133
+ not_secret: not_secret
134
+ layer1:
135
+ - nat_user: !MK:write(NATS_USER,env(NATS_USER)) replaceme
136
+ - nat_host: !MK:write(NATS_HOST,env(NATS_HOST)) replaceme
137
+ - nat_password: !MK:write(NATS_PASSWORD,secret(12)) replaceme
138
+ layer2:
139
+ - nat_connection: !MK:write(NATS_STRING,format(read(NATS_USER),read(NATS_PASSWORD),read(NATS_HOST),read(NAT_TEMPLATE_1)))
140
+ - info_endpoint: !MK:format(read(NATS_STRING),read(NAT_TEMPLATE_2))
59
141
  ```
60
142
 
61
143
 
144
+ After:
145
+
146
+ ```
147
+ ---
148
+ nat_template_1: !MK:write_value(NAT_TEMPLATE_1) https://%s:%s@%s
149
+ nat_template_2: !MK:write_value(NAT_TEMPLATE_2) '%s/info'
150
+ meta1:
151
+ not_secret: not_secret
152
+ layer1:
153
+ - nat_user: !MK:write(NATS_USER,env(NATS_USER)) nats_user
154
+ - nat_host: !MK:write(NATS_HOST,env(NATS_HOST)) 10.10.0.6
155
+ - nat_password: !MK:write(NATS_PASSWORD,secret(12)) WxhUE4RoJXYF
156
+ layer2:
157
+ - nat_connection: !MK:write(NATS_STRING,format(read(NATS_USER),read(NATS_PASSWORD),read(NATS_HOST),read(NAT_TEMPLATE_1))) https://nats_user:WxhUE4RoJXYF@10.10.0.6
158
+ - info_endpoint: !MK:format(read(NATS_STRING),read(NAT_TEMPLATE_2)) https://nats_user:WxhUE4RoJXYF@10.10.0.6/info
159
+ ```
160
+
62
161
  ## Installation
63
162
 
64
163
  Add this line to your application's Gemfile:
@@ -74,14 +173,35 @@ And then execute:
74
173
  Or install it yourself as:
75
174
 
76
175
  $ gem install monkey_king
176
+
177
+ **Or if you want the cutting edge**
178
+
179
+ ```
180
+ git clone https://github.com/pivotal-cloudops/monkey_king.git
181
+ cd monkey_king
182
+ bundle exec mk
183
+ ```
184
+
185
+ ## Try it Out:
186
+
187
+ You can create a yaml file (example: demo.yml in ~/tmp) with the 'MK' yaml tags as described earlier.
188
+
189
+ Then run:
190
+
191
+ ```
192
+ cd monkey_king
193
+ bundle exec mk demo ~/tmp/demo.yml
194
+ ```
195
+
77
196
 
78
- ## Usage
197
+ ## Full Usage
79
198
  ```
80
- $ mk help
199
+ $ mk
81
200
  Commands:
82
- help [COMMAND] Help!
83
- clone REPO DIR... Clone the repo and replace secret and env annotation
84
- replace GLOBS... Replace secret and env annotation for existing directory
201
+ help [COMMAND] Help!
202
+ clone REPO DIR use MK to clone github repo and transform
203
+ replace GLOBS... Do MK transform for existing directory(ies)
204
+ demo FILE Demo MK transform for one file```
85
205
  ```
86
206
  ```
87
207
  $ mk help clone
@@ -4,7 +4,7 @@ require 'highline/import'
4
4
  module MonkeyKing
5
5
  class CloneCommand < Mothership
6
6
 
7
- desc "Clone the repo and replace secret and env annotation"
7
+ desc "use MK to clone github repo and transform"
8
8
  input :repo, :argument => true
9
9
  input :dir, :argument => [:splat, true]
10
10
  def clone
@@ -23,6 +23,7 @@ module MonkeyKing
23
23
 
24
24
  deployment_yaml_files.each do |file|
25
25
  puts "Transforming #{file}..."
26
+ MonkeyKing.variables = {}
26
27
  transformed_content = parser.transform(file)
27
28
  File.open(file, "w") do |overwrite_file|
28
29
  overwrite_file.write transformed_content
@@ -31,7 +32,7 @@ module MonkeyKing
31
32
  puts "Done."
32
33
  end
33
34
 
34
- desc "Replace secret and env annotation for existing directory"
35
+ desc "Do MK transform for existing directory(ies)"
35
36
  input :globs, :argument => :splat
36
37
  def replace
37
38
  globs = input[:globs]
@@ -47,6 +48,7 @@ module MonkeyKing
47
48
  extension = file.split('.').last
48
49
  if extension == 'yml'
49
50
  puts "Transforming #{file}..."
51
+ MonkeyKing.variables = {}
50
52
  transformed_content = parser.transform(file)
51
53
  File.open(file, "w") do |overwrite_file|
52
54
  overwrite_file.write transformed_content
@@ -58,5 +60,13 @@ module MonkeyKing
58
60
  puts "Done."
59
61
  end
60
62
 
63
+ desc "Demo MK transform for one file"
64
+ input :file, :argument => true
65
+ def demo
66
+ file = input[:file]
67
+ parser = MonkeyKing::Parser.new
68
+ puts parser.transform(file)
69
+ end
70
+
61
71
  end
62
72
  end
@@ -1,84 +1,150 @@
1
1
  require 'yaml'
2
+ require 'securerandom'
3
+ require 'sexpistol'
4
+ require 'pry'
2
5
 
3
6
  module MonkeyKing
4
- class SecretTag
5
- yaml_tag '!MK:secret'
7
+ @@variables = {}
6
8
 
7
- attr_reader :secret
9
+ def self.variables
10
+ @@variables
11
+ end
12
+
13
+ def self.variables=(value)
14
+ @@variables = value
15
+ end
16
+
17
+ def self.set_variable(name, value)
18
+ @@variables[name] = value
19
+ return value
20
+ end
21
+
22
+ class FunctionTag
23
+ attr_accessor :scalar
8
24
 
9
- def initialize( *secret )
10
- self.secret = *secret
25
+ def register(tag)
26
+ self.class.send(:yaml_tag, tag)
11
27
  end
12
28
 
13
- def init_with( coder )
14
- case coder.type
15
- when :scalar
16
- self.secret = coder.scalar
17
- else
29
+ def init_with(coder)
30
+ unless coder.type == :scalar
18
31
  raise "Dunno how to handle #{coder.type} for #{coder.inspect}"
19
32
  end
33
+ self.scalar = coder.scalar
20
34
  end
21
35
 
22
- def encode_with( coder )
36
+ def encode_with(coder)
23
37
  coder.style = Psych::Nodes::Mapping::FLOW
24
- coder.scalar = gen_secret(@secret.length)
38
+ s_expression = coder.tag.sub(/^!MK:/, '')
39
+ s_expression.gsub!(/,/, ' ')
40
+ expression_tree = Sexpistol.new.parse_string(s_expression)
41
+ coder.scalar = expand(expression_tree).first
25
42
  end
26
43
 
27
- protected def secret=( str )
28
- @secret= str
29
- end
30
44
 
31
- def gen_secret(length)
32
- [*('a'..'z'),*('0'..'9'),*('A'..'Z')].shuffle[0,length].join
45
+ def argment_count_checking(params_count, legit_count, function_name)
46
+ if params_count > legit_count
47
+ raise "too many arguments for #{function_name} function (#{params_count} of #{legit_count})"
48
+ elsif params_count < legit_count
49
+ raise "not enough arguments for #{function_name} function (#{params_count} of #{legit_count})"
50
+ end
33
51
  end
34
- end
35
52
 
36
- class EnvTag
37
- attr_reader :env_tag
53
+ def expand(expression)
54
+ if expression.is_a? Array
55
+ function_array = []
56
+ expression.each do |ex|
57
+ function_array << expand(ex)
58
+ end
38
59
 
39
- def register(tag)
40
- if tag.split(':')[1] == 'env'
41
- self.class.send(:yaml_tag, tag)
60
+ process_array = []
61
+
62
+ while !function_array.empty? do
63
+ key_word = function_array.shift
64
+ case key_word
65
+ when :write
66
+ params = function_array.shift
67
+ argment_count_checking(params.size, 2, key_word)
68
+ key = params.first
69
+ value = params.last
70
+ raise "attempting to redefine immutable variable #{key}, exiting" unless MonkeyKing.variables[key].nil?
71
+ process_array << MonkeyKing.set_variable(key, value.to_s)
72
+ when :read
73
+ params = function_array.shift
74
+ argment_count_checking(params.size, 1, key_word)
75
+ key = params.first
76
+ raise "unresolved variables #{key}" if MonkeyKing.variables[key].nil?
77
+ process_array << MonkeyKing.variables[key]
78
+ when :secret
79
+ params = function_array.shift
80
+ argment_count_checking(params.size, 1, key_word)
81
+ raise "argument error for secret function: got #{params.first.class} instead of Fixnum" if !params.first.is_a? Fixnum
82
+ length = params.first
83
+ process_array << gen_secret(length)
84
+ when :env
85
+ params = function_array.shift
86
+ argment_count_checking(params.size, 1, key_word)
87
+ key = params.first.to_s
88
+ process_array << get_env(key)
89
+ when :write_value
90
+ params = function_array.shift
91
+ argment_count_checking(params.size, 1, key_word)
92
+ key = params.first
93
+ process_array << MonkeyKing.set_variable(key, self.scalar)
94
+ when :format
95
+ params = function_array.shift
96
+ formating_string = params.pop
97
+ raise('format template not found') if formating_string.nil?
98
+ replace_count = formating_string.scan(/%s/).count
99
+ argment_count_checking(params.size, replace_count, key_word)
100
+ params.each do |param|
101
+ formating_string = formating_string.sub(/%s/, param.to_s)
102
+ end
103
+ process_array << formating_string
104
+ else
105
+ process_array << key_word
106
+ end
107
+ end
108
+ return process_array
109
+
110
+ elsif expression.is_a? Symbol
111
+ return expression
112
+ elsif expression.is_a? Numeric
113
+ return expression
114
+ elsif expression.is_a? String
115
+ return expression
116
+ else
117
+ raise "unknown expression #{expression}"
42
118
  end
43
119
  end
44
120
 
45
- def init_with( coder )
46
- unless coder.type == :scalar
47
- raise "Dunno how to handle #{coder.type} for #{coder.inspect}"
48
- end
121
+ def get_env(key)
122
+ raise "#{key} not found in env" if ENV[key].nil?
123
+ return ENV[key]
49
124
  end
50
125
 
51
- def encode_with(coder)
52
- coder.style = Psych::Nodes::Mapping::FLOW
53
- tag=coder.tag.split(':')[2]
54
- if ENV[tag].nil?
55
- raise "#{tag} not found in env"
56
- end
57
- coder.scalar = ENV[tag]
126
+ def gen_secret(length)
127
+ return [*('a'..'z'),*('0'..'9'),*('A'..'Z')].shuffle[0,length].join
58
128
  end
59
129
  end
60
130
 
61
131
  class Parser
132
+
62
133
  def transform(yaml_file)
134
+ function_tag_instances = {}
63
135
  tags = get_tags(yaml_file)
64
- env_tag_instances={}
136
+
65
137
  tags.each do |tag|
66
- command = tag.split(':')[1]
67
- unless command.nil? or command != 'env'
68
- class_name = tag.split(':')[2]
69
- unless class_name.nil?
70
- tag_class = Class.new(EnvTag)
71
-
72
- # Hacky way to give each class a global uniq name
73
- random_string = [*('a'..'z')].shuffle[0,32].join
74
- Object.const_set("EnvTag#{class_name}#{random_string}", tag_class)
75
-
76
- tag_instance = tag_class.new
77
- tag_instance.register(tag)
78
- env_tag_instances[tag] = tag_instance
79
- end
138
+ if tag =~ /!MK:/
139
+ tag_class = Class.new(FunctionTag)
140
+ random_string = SecureRandom.uuid.gsub(/-/, '')
141
+ Object.const_set("FunctionTag#{random_string}", tag_class)
142
+ tag_instance = tag_class.new
143
+ tag_instance.register(tag)
144
+ function_tag_instances[tag] = tag_instance
80
145
  end
81
146
  end
147
+
82
148
  yaml = YAML.load_file(yaml_file)
83
149
  yaml.to_yaml
84
150
  end
@@ -97,5 +163,4 @@ module MonkeyKing
97
163
  tags.uniq
98
164
  end
99
165
  end
100
-
101
166
  end
@@ -1,3 +1,3 @@
1
1
  module MonkeyKing
2
- VERSION = "0.1.5"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: monkey_king
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - cloudops_hosted
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-15 00:00:00.000000000 Z
11
+ date: 2016-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mothership