holepunch 1.0.1 → 1.1.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: 2b279b2eeb6d71a22be15c89d5df8d0a5809f626
4
- data.tar.gz: b8f9c861cb294128f381b9e6742e76ca2c63c668
3
+ metadata.gz: ee112b10944d69873e6fcf50a0b1536a5adfb88c
4
+ data.tar.gz: f40ac7918db97e6fd64470b40358fe344d456380
5
5
  SHA512:
6
- metadata.gz: d82f17fcf4e7881ce29141b13801b527483a964e8f42cd45d82cbd5049f3a753449374b2a40d57b388f3c1ea0ef9c9f618296e52d68d6a71d84b1f418b429bb0
7
- data.tar.gz: 6e4ebfa45a20aa7f4fc5b034c7f8bfde3b45c56c5eb7e96dd6ea23042109faa8d476755a638c15c79aa73c11024aae7e5cda6ff103b6460701d66b43b7e82b69
6
+ metadata.gz: 4330a09b9e4e9fc291c3910395bb160bf97a94ba52404113eb6cee6386fe9a9ceadbfd60296c8f5e2efd83f389ae80978d1ec764afee411bc919447e3ae93764
7
+ data.tar.gz: 828ad00eb16a838923cd72be646348ac4745bee98bdadd19d4faf7c07d5a5322db96edba8cfd06881b40755c7346ca81b41bd458b5dc7718264b56fb24ba2cab
@@ -23,4 +23,4 @@ lib = File.expand_path('../../lib', __FILE__)
23
23
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
24
24
  require 'holepunch/cli'
25
25
 
26
- HolePunch::Cli.new.execute!(ARGV.dup)
26
+ HolePunch::CLI.start(ARGV.dup)
@@ -33,8 +33,28 @@ module HolePunch
33
33
  class GroupDoesNotExistError < HolePunchError; end
34
34
  class SecurityGroupsFileNotFoundError < HolePunchError; end
35
35
  class SecurityGroupsFileError < HolePunchError; end
36
+ class ServiceDoesNotExistError < HolePunchError; end
36
37
 
37
38
  class << self
39
+ # Examines the given SecurityGroups file for the given service and returns
40
+ # a list of the security groups that make up that service.
41
+ #
42
+ # @param filename [String] the path to the SecurityGroups file
43
+ # @param env [String, nil] the environment
44
+ # @param name [String] the name of the service to query
45
+ #
46
+ # @return [Array<String>] the list of security group names
47
+ def service_groups(filename, env, name)
48
+ definition = Definition.build(filename, env)
49
+ service = definition.services[name]
50
+ raise ServiceDoesNotExistError, "service '#{name}' not found" if service.nil?
51
+ service.groups
52
+ end
53
+
54
+ #
55
+ # private helpers
56
+ #
57
+
38
58
  def cidr?(value)
39
59
  value.to_s =~ /\d+\.\d+\.\d+\.\d+\/\d+/
40
60
  end
@@ -19,33 +19,41 @@
19
19
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
20
  #
21
21
  require 'holepunch'
22
- require 'optparse'
22
+ require 'thor'
23
23
 
24
24
  module HolePunch
25
- class Options < Struct.new(
26
- :aws_access_key_id,
27
- :aws_region,
28
- :aws_secret_access_key,
29
- :env,
30
- :filename,
31
- :verbose
32
- ); end
33
-
34
- class Cli
35
- def initialize
25
+ class CLI < Thor
26
+ def initialize(*args)
27
+ super
36
28
  Logger.output = LoggerOutputStdio.new
37
29
  end
38
30
 
39
- def execute!(args)
40
- opts = parse_opts(args)
41
- Logger.verbose = opts.verbose
31
+ default_task :apply
42
32
 
43
- definition = Definition.build(opts.filename, opts.env)
33
+ option :'aws-access-key', aliases: :A, type: :string, default: ENV['AWS_ACCESS_KEY_ID'], desc:
34
+ 'Your AWS Access Key ID'
35
+ option :'aws-secret-access-key', aliases: :k, type: :string, default: ENV['AWS_SECRET_ACCESS_KEY'], desc:
36
+ 'Your AWS API Secret Access Key'
37
+ option :'aws-region', aliases: :r, type: :string, default: ENV['AWS_REGION'], desc:
38
+ 'Your AWS region'
39
+ option :env, aliases: :e, type: :string, desc:
40
+ 'Set the environment'
41
+ option :file, aliases: :f, type: :string, default: "#{Dir.pwd}/SecurityGroups", desc:
42
+ 'The location of the SecurityGroups file to use'
43
+ option :verbose, aliases: :v, type: :boolean, desc:
44
+ 'Enable verbose output'
45
+ desc 'apply [OPTIONS]', 'apply the defined security groups to ec2'
46
+ def apply
47
+ Logger.fatal("AWS Access Key ID not defined. Use --aws-access-key or AWS_ACCESS_KEY_ID") if options[:'aws-access-key'].nil?
48
+ Logger.fatal("AWS Secret Access Key not defined. Use --aws-secret-access-key or AWS_SECRET_ACCESS_KEY") if options[:'aws-secret-access-key'].nil?
49
+ Logger.fatal("AWS Region not defined. Use --aws-region or AWS_REGION") if options[:'aws-region'].nil?
50
+ Logger.verbose = options[:verbose]
44
51
 
52
+ definition = Definition.build(options[:file], options[:env])
45
53
  ec2 = EC2.new({
46
- access_key_id: opts.aws_access_key_id,
47
- secret_access_key: opts.aws_secret_access_key,
48
- region: opts.aws_region,
54
+ access_key_id: options[:'aws-access-key'],
55
+ secret_access_key: options[:'aws-secret-access-key'],
56
+ region: options[:'aws-region'],
49
57
  })
50
58
  ec2.apply(definition)
51
59
 
@@ -55,55 +63,45 @@ module HolePunch
55
63
  Logger.fatal(e.message)
56
64
  end
57
65
 
58
- private
59
- def parse_opts(args)
60
- opts = Options.new
61
- opts.aws_access_key_id = ENV['AWS_ACCESS_KEY_ID']
62
- opts.aws_secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
63
- opts.aws_region = ENV['AWS_REGION']
64
- opts.env = nil
65
- opts.filename = "#{Dir.pwd}/SecurityGroups"
66
- opts.verbose = false
66
+ option :env, aliases: :e, type: :string, desc:
67
+ 'Set the environment'
68
+ option :file, aliases: :f, type: :string, default: "#{Dir.pwd}/SecurityGroups", desc:
69
+ 'The location of the SecurityGroups file to use'
70
+ option :list, type: :boolean, desc:
71
+ 'List all services instead'
72
+ option :verbose, aliases: :v, type: :boolean, desc:
73
+ 'Enable verbose output'
74
+ desc 'service NAME', 'output the list of security groups for a service'
75
+ def service(name = nil)
76
+ Logger.verbose = options[:verbose]
67
77
 
68
- OptionParser.new(<<-EOS.gsub(/^ {10}/, '')
69
- Usage: holepunch [options]
78
+ definition = Definition.build(options[:file], options[:env])
70
79
 
71
- Options:
72
- EOS
73
- ) do |parser|
74
- parser.on('-A', '--aws-access-key KEY', String, 'Your AWS Access Key ID') do |value|
75
- opts.aws_access_key_id = value
76
- end
77
- parser.on('-e', '--env ENV', String, 'Set the environment') do |value|
78
- opts.env = value
79
- end
80
- parser.on('-f', '--file FILENAME', String, 'The location of the SecurityGroups file to use') do |value|
81
- opts.filename = value
82
- end
83
- parser.on('-K', '--aws-secret-access-key SECRET', String, 'Your AWS API Secret Access Key') do |value|
84
- opts.aws_secret_access_key = value
85
- end
86
- parser.on('-r', '--aws-region REGION', String, 'Your AWS region') do |v|
87
- opts.aws_region = v
88
- end
89
- parser.on('-v', '--verbose', 'verbose output') do |v|
90
- opts.verbose = v
91
- end
92
- parser.on('-V', '--version', 'display the version and exit') do
93
- puts VERSION
94
- exit
95
- end
96
- parser.on_tail('-h', '--help', 'show this message') do
97
- puts parser
98
- exit
99
- end
100
- end.parse!(args)
80
+ if options[:list]
81
+ definition.services.keys.sort.each do |name|
82
+ puts name
83
+ end
84
+ else
85
+ service = definition.services[name]
86
+ Logger.fatal("service '#{name}' not found") if service.nil?
87
+ puts service.groups.sort.join(',')
88
+ end
101
89
 
102
- Logger.fatal("AWS Access Key ID not defined. Use --aws-access-key or AWS_ACCESS_KEY_ID") if opts.aws_access_key_id.nil?
103
- Logger.fatal("AWS Secret Access Key not defined. Use --aws-secret-access-key or AWS_SECRET_ACCESS_KEY") if opts.aws_secret_access_key.nil?
104
- Logger.fatal("AWS Region not defined. Use --aws-region or AWS_REGION") if opts.aws_region.nil?
90
+ rescue EnvNotDefinedError => e
91
+ Logger.fatal('You have security groups that use an environment, but you did not specify one. See --help')
92
+ rescue HolePunchError => e
93
+ Logger.fatal(e.message)
94
+ end
95
+
96
+ desc 'version', 'display the version and exit'
97
+ def version
98
+ puts VERSION
99
+ end
100
+ map %w(-V --version) => :version
105
101
 
106
- opts
102
+ protected
103
+ def exit_on_failure?
104
+ true
107
105
  end
108
106
  end
109
107
  end
@@ -19,6 +19,15 @@
19
19
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
20
  #
21
21
  module HolePunch
22
+ class Service
23
+ attr_accessor :id, :groups
24
+
25
+ def initialize(id)
26
+ @id = id
27
+ @groups = []
28
+ end
29
+ end
30
+
22
31
  class SecurityGroup
23
32
  attr_accessor :id, :desc, :dependency, :ingresses
24
33
 
@@ -59,6 +68,7 @@ module HolePunch
59
68
  class Definition
60
69
  attr_reader :env
61
70
  attr_reader :groups
71
+ attr_reader :services
62
72
 
63
73
  class << self
64
74
  def build(file, env)
@@ -74,6 +84,7 @@ module HolePunch
74
84
  def initialize(env = nil)
75
85
  @env = env
76
86
  @groups = {}
87
+ @services = {}
77
88
  end
78
89
 
79
90
  def add_group(group)
@@ -93,6 +104,15 @@ module HolePunch
93
104
  end
94
105
  end
95
106
  end
107
+
108
+ # verify service group references are defined
109
+ services.each do |name, service|
110
+ service.groups.each do |group|
111
+ unless groups.include?(group)
112
+ raise GroupDoesNotExistError, "group '#{group}' referenced by service '#{name}' does not exist"
113
+ end
114
+ end
115
+ end
96
116
  end
97
117
  end
98
118
  end
@@ -21,79 +21,104 @@
21
21
  require 'pathname'
22
22
 
23
23
  module HolePunch
24
- class DSL
25
- attr_reader :groups
24
+ class BaseDSL
25
+ def initialize(env, model)
26
+ @env = env
27
+ @model = model
28
+ end
26
29
 
27
- def self.evaluate(filename, env = nil)
28
- DSL.new(env).eval_dsl(filename)
30
+ def eval_dsl(filename = nil, &block)
31
+ if !filename.nil?
32
+ instance_eval(HolePunch.read_file(filename.to_s), filename.to_s, 1)
33
+ else
34
+ instance_eval(&block) if block_given?
35
+ end
36
+ @model
29
37
  end
30
38
 
31
- def initialize(env)
32
- @definition = Definition.new(env)
33
- @group = nil
34
- @groups = {}
39
+ def env
40
+ raise EnvNotDefinedError, 'env not defined' if @env.nil?
41
+ @env
35
42
  end
43
+ end
36
44
 
37
- def eval_dsl(filename)
38
- instance_eval(HolePunch.read_file(filename.to_s), filename.to_s, 1)
39
- @definition.validate!
40
- @definition
41
- rescue SyntaxError => e
42
- raise SecurityGroupsFileError, "SecurityGroups syntax error #{e.message.gsub("#{filename.to_s}:", 'on line ')}"
45
+ class ServiceDSL < BaseDSL
46
+ def self.evaluate(env, *args, &block)
47
+ new(env, *args).eval_dsl(&block)
43
48
  end
44
49
 
45
- def env
46
- raise EnvNotDefinedError, 'env not defined' if @definition.env.nil?
47
- @definition.env
50
+ def initialize(env, id)
51
+ super(env, Service.new(id))
48
52
  end
49
53
 
50
- def depends(id)
51
- id = id.to_s
52
- raise GroupError, "duplicate group id #{id}" if @definition.groups.include?(id)
53
- raise HolePunchSyntaxError, "dependency group #{id} cannot have a block" if block_given?
54
- @group = SecurityGroup.new(id, dependency: true)
55
- @definition.add_group(@group)
56
- yield if block_given?
57
- ensure
58
- @group = nil
54
+ def groups(*ids)
55
+ @model.groups.concat(ids.flatten)
59
56
  end
57
+ end
60
58
 
61
- def group(id, &block)
62
- id = id.to_s
63
- raise GroupError, "duplicate group id #{id}" if @definition.groups.include?(id)
64
- @group = SecurityGroup.new(id, dependency: false)
65
- @definition.add_group(@group)
66
- yield if block_given?
67
- ensure
68
- @group = nil
59
+ class GroupDSL < BaseDSL
60
+ def self.evaluate(env, *args, &block)
61
+ new(env, *args).eval_dsl(&block)
62
+ end
63
+
64
+ def initialize(env, id)
65
+ super(env, SecurityGroup.new(id, dependency: false))
69
66
  end
70
67
 
71
68
  def desc(str)
72
- raise HolePunchSyntaxError, 'desc must be used inside a group' if @group.nil?
73
- raise HolePunchSyntaxError, 'desc cannot be used in a dependency group (the group is expected to be already defined elsewhere)' if @group.dependency
74
- @group.desc = str
69
+ @model.desc = str
75
70
  end
76
71
 
77
72
  def icmp(*sources)
78
- raise HolePunchSyntaxError, 'ping/icmp must be used inside a group' if @group.nil?
79
- raise HolePunchSyntaxError, 'ping/icmp cannot be used in a dependency group (the group is expected to be already defined elsewhere)' if @group.dependency
80
73
  sources << '0.0.0.0/0' if sources.empty?
81
- @group.ingresses << Permission.new(:icmp, nil, sources.flatten)
74
+ @model.ingresses << Permission.new(:icmp, nil, sources.flatten)
82
75
  end
83
76
  alias_method :ping, :icmp
84
77
 
85
78
  def tcp(ports, *sources)
86
- raise HolePunchSyntaxError, 'tcp must be used inside a group' if @group.nil?
87
- raise HolePunchSyntaxError, 'tcp cannot be used in a dependency group (the group is expected to be already defined elsewhere)' if @group.dependency
88
79
  sources << '0.0.0.0/0' if sources.empty?
89
- @group.ingresses << Permission.new(:tcp, ports, sources.flatten)
80
+ @model.ingresses << Permission.new(:tcp, ports, sources.flatten)
90
81
  end
91
82
 
92
83
  def udp(ports, *sources)
93
- raise HolePunchSyntaxError, 'udp must be used inside a group' if @group.nil?
94
- raise HolePunchSyntaxError, 'udp cannot be used in a dependency group (the group is expected to be already defined elsewhere)' if @group.dependency
95
84
  sources << '0.0.0.0/0' if sources.empty?
96
- @group.ingresses << Permission.new(:udp, ports, sources.flatten)
85
+ @model.ingresses << Permission.new(:udp, ports, sources.flatten)
86
+ end
87
+ end
88
+
89
+ class DSL < BaseDSL
90
+ def self.evaluate(filename, env)
91
+ DSL.new(env).eval_dsl(filename)
92
+ end
93
+
94
+ def initialize(env)
95
+ super(env, Definition.new(env))
96
+ end
97
+
98
+ def eval_dsl(filename)
99
+ super(filename)
100
+ @model.validate!
101
+ @model
102
+ rescue SyntaxError => e
103
+ raise SecurityGroupsFileError, "SecurityGroups syntax error #{e.message.gsub("#{filename.to_s}:", 'on line ')}"
104
+ end
105
+
106
+ def depends(id)
107
+ id = id.to_s
108
+ raise GroupError, "duplicate group id #{id}" if @model.groups.include?(id)
109
+ raise HolePunchSyntaxError, "dependency group #{id} cannot have a block" if block_given?
110
+ @model.add_group(SecurityGroup.new(id, dependency: true))
111
+ end
112
+
113
+ def group(id, &block)
114
+ id = id.to_s
115
+ raise GroupError, "duplicate group id #{id}" if @model.groups.include?(id)
116
+ @model.add_group(GroupDSL.evaluate(@env, id, &block))
117
+ end
118
+
119
+ def service(id, &block)
120
+ id = id.to_s
121
+ @model.services[id] = ServiceDSL.evaluate(@env, id, &block)
97
122
  end
98
123
  end
99
124
  end
@@ -19,5 +19,5 @@
19
19
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
20
  #
21
21
  module HolePunch
22
- VERSION = '1.0.1'
22
+ VERSION = '1.1.0'
23
23
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: holepunch
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Scott
@@ -9,8 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-06-16 00:00:00.000000000 Z
12
+ date: 2014-07-01 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: thor
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0.19'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '0.19'
14
28
  - !ruby/object:Gem::Dependency
15
29
  name: aws-sdk
16
30
  requirement: !ruby/object:Gem::Requirement
@@ -119,9 +133,9 @@ description: |
119
133
  This allows you to have custom security groups per server environment.
120
134
 
121
135
  ```ruby
122
- group "web-#{env}"
123
- group "db-#{env}" do
124
- tcp 5432, "web-#{env}"
136
+ group "#{env}-web"
137
+ group "#{env}-db" do
138
+ tcp 5432, "#{env}-web"
125
139
  end
126
140
  ```
127
141
 
@@ -143,6 +157,26 @@ description: |
143
157
  end
144
158
  ```
145
159
 
160
+ You can specify ping/icmp rules with `icmp` (alias: `ping`).
161
+
162
+ ```ruby
163
+ group 'my-service' do
164
+ ping '10.0.0.0/16'
165
+ end
166
+
167
+ It can be useful to describe groups of security groups you plan to launch
168
+ instances with by using the `service` declaration.
169
+
170
+ ```ruby
171
+ service "#{env}-web" do
172
+ groups %W(
173
+ admin
174
+ #{env}-log-producer
175
+ #{env}-web
176
+ )
177
+ end
178
+ ```
179
+
146
180
  ## Usage
147
181
 
148
182
  Simply navigate to the directory containing your `SecurityGroups` file and run `holepunch`.
@@ -157,6 +191,19 @@ description: |
157
191
  $ holepunch -e live
158
192
  ```
159
193
 
194
+ You can get a list of security groups for a service using the `service` subcommand.
195
+
196
+ ```
197
+ $ holepunch service -e prod prod-web
198
+ admin,prod-log-producer,prod-web
199
+ ```
200
+
201
+ You can also get a list of all defined services.
202
+
203
+ ```
204
+ $ holepunch service --list
205
+ ```
206
+
160
207
  ## Testing
161
208
 
162
209
  You can run the unit tests by simply running rspec.