monkey_king 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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