holepunch 1.0.1 → 1.1.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: 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.