simnos 0.1.0.beta1
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 +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +119 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/simnos +5 -0
- data/lib/simnos/cli.rb +79 -0
- data/lib/simnos/client.rb +100 -0
- data/lib/simnos/client_wrapper.rb +49 -0
- data/lib/simnos/converter.rb +20 -0
- data/lib/simnos/dsl/sns.rb +33 -0
- data/lib/simnos/dsl/topic.rb +171 -0
- data/lib/simnos/dsl.rb +54 -0
- data/lib/simnos/filterable.rb +20 -0
- data/lib/simnos/output_topic.erb +18 -0
- data/lib/simnos/template_helper.rb +20 -0
- data/lib/simnos/utils.rb +48 -0
- data/lib/simnos/version.rb +3 -0
- data/lib/simnos.rb +14 -0
- data/simnos.gemspec +34 -0
- metadata +195 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6c2ee190f061194db5326da7059b8a52b77e1a8c
|
4
|
+
data.tar.gz: a28cb166a667a8aa6261577ec575446fe8142746
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a9a4042a549f22438ca66fa6c7bd5a13d9ac3f5b68a453865115b4ec72d531176cfa9d0e0e8f0695f8a4efc3804b9a461f80eb067ef4decc334570eda156a6f8
|
7
|
+
data.tar.gz: aa3f79e4d68983a057e0f40de5df2c5368d016dd51daf9f9c71344e6c579b66c7c8d240851775811789e3c89a97187e862dec519d3f2094ed711211620f2bd63
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 shinya-watanabe
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
# Simnos
|
2
|
+
|
3
|
+
Simons is a tool to manage AWS SNS topic.
|
4
|
+
It defines the state of SNS topic using DSL, and updates SNS topic according to DSL.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'simnos'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install simnos
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
```
|
25
|
+
export AWS_ACCESS_KEY_ID='...'
|
26
|
+
export AWS_SECRET_ACCESS_KEY='...'
|
27
|
+
export AWS_REGION='ap-northeast-1'
|
28
|
+
simnos -e -o SNSfile # export SNS topic
|
29
|
+
vi SNSfile
|
30
|
+
simnos -a --dry-run
|
31
|
+
simnos -a # apply `SNSfile` to SNS
|
32
|
+
```
|
33
|
+
|
34
|
+
## Help
|
35
|
+
|
36
|
+
```
|
37
|
+
Usage: simnos [options]
|
38
|
+
-h, --help Show help
|
39
|
+
-v, --debug Show debug log
|
40
|
+
-a, --apply apply DSL
|
41
|
+
-e, --export export to DSL
|
42
|
+
-n, --dry-run dry run
|
43
|
+
-f, --file FILE use selected DSL file
|
44
|
+
-s, --split split export DSL file to 1 per topic
|
45
|
+
--no-color
|
46
|
+
no color
|
47
|
+
-i, --include-names NAMES include SNS names
|
48
|
+
-x, --exclude-names NAMES exclude SNS names by regex
|
49
|
+
```
|
50
|
+
|
51
|
+
## SNSfile
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
sns "ap-northeast-1" do
|
55
|
+
topic "test-topic" do
|
56
|
+
display_name "test topic"
|
57
|
+
|
58
|
+
effective_delivery_policy do
|
59
|
+
{"http"=>
|
60
|
+
{"defaultHealthyRetryPolicy"=>
|
61
|
+
{"minDelayTarget"=>20,
|
62
|
+
"maxDelayTarget"=>20,
|
63
|
+
"numRetries"=>2,
|
64
|
+
"numMaxDelayRetries"=>0,
|
65
|
+
"numNoDelayRetries"=>0,
|
66
|
+
"numMinDelayRetries"=>0,
|
67
|
+
"backoffFunction"=>"linear"},
|
68
|
+
"disableSubscriptionOverrides"=>false}}
|
69
|
+
end
|
70
|
+
|
71
|
+
policy do
|
72
|
+
{"Version"=>"2008-10-17",
|
73
|
+
"Id"=>"__default_policy_ID",
|
74
|
+
"Statement"=>
|
75
|
+
[{"Sid"=>"__default_statement_ID",
|
76
|
+
"Effect"=>"Allow",
|
77
|
+
"Principal"=>{"AWS"=>"*"},
|
78
|
+
"Action"=>"SNS:Subscribe",
|
79
|
+
"Resource"=>"arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:test-topic",
|
80
|
+
"Condition"=>{"StringEquals"=>{"AWS:SourceOwner"=>"XXXXXXXXXXXX"}}}]}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
## Use template
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
template "default_policy" do
|
90
|
+
policy do
|
91
|
+
{"Version"=>"2008-10-17",
|
92
|
+
"Id"=>"__default_policy_ID",
|
93
|
+
"Statement"=>
|
94
|
+
[{"Sid"=>"__default_statement_ID",
|
95
|
+
"Effect"=>"Allow",
|
96
|
+
"Principal"=>{"AWS"=>"*"},
|
97
|
+
"Action"=>"SNS:Subscribe",
|
98
|
+
"Resource"=>"arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:#{context.topic_name}",
|
99
|
+
"Condition"=>{"StringEquals"=>{"AWS:SourceOwner"=>"XXXXXXXXXXXX"}}}]}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
sns "ap-northeast-1" do
|
104
|
+
include_template "default_policy", topic_name: "test-topic"
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
## Similar tools
|
109
|
+
|
110
|
+
* [Codenize.tools](http://codenize.tools/)
|
111
|
+
|
112
|
+
## Contributing
|
113
|
+
|
114
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/codenize-tools/simnos.
|
115
|
+
|
116
|
+
## License
|
117
|
+
|
118
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
119
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "simnos"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/simnos
ADDED
data/lib/simnos/cli.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'simnos'
|
2
|
+
require 'optparse'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module Simnos
|
6
|
+
class CLI
|
7
|
+
def self.start(argv)
|
8
|
+
new(argv).run
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(argv)
|
12
|
+
@argv = argv.dup
|
13
|
+
@help = argv.empty?
|
14
|
+
@filepath = 'SNSfile'
|
15
|
+
@options = {
|
16
|
+
color: true,
|
17
|
+
includes: [],
|
18
|
+
excludes: [],
|
19
|
+
}
|
20
|
+
parser.order!(@argv)
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
if @help
|
25
|
+
puts parser.help
|
26
|
+
elsif @apply
|
27
|
+
Apply.new(@filepath, @options).run
|
28
|
+
elsif @export
|
29
|
+
Export.new(@filepath, @options).run
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def parser
|
36
|
+
@parser ||= OptionParser.new do |opts|
|
37
|
+
opts.version = VERSION
|
38
|
+
opts.on('-h', '--help', 'Show help') { @help = true }
|
39
|
+
opts.on('-v', '--debug', 'Show debug log') { Simnos.logger.level = Logger::DEBUG }
|
40
|
+
opts.on('-a', '--apply', 'apply DSL') { @apply = true }
|
41
|
+
opts.on('-e', '--export', 'export to DSL') { @export = true }
|
42
|
+
opts.on('-n', '--dry-run', 'dry run') { @options[:dry_run] = true }
|
43
|
+
opts.on('-f', '--file FILE', 'use selected DSL file') { |v| @filepath = v }
|
44
|
+
opts.on('-s', '--split', 'split export DSL file to 1 per topic') { @options[:split] = true }
|
45
|
+
opts.on('', '--no-color', 'no color') { @options[:color] = false }
|
46
|
+
opts.on('-i', '--include-names NAMES', 'include SNS names', Array) { |v| @options[:includes] = v }
|
47
|
+
opts.on('-x', '--exclude-names NAMES', 'exclude SNS names by regex', Array) do |v|
|
48
|
+
@options[:excludes] = v.map! do |name|
|
49
|
+
name =~ /\A\/(.*)\/\z/ ? Regexp.new($1) : Regexp.new("\A#{Regexp.escape(name)}\z")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Apply
|
56
|
+
def initialize(filepath, options)
|
57
|
+
@filepath = filepath
|
58
|
+
@options = options
|
59
|
+
end
|
60
|
+
|
61
|
+
def run
|
62
|
+
require 'simnos/client'
|
63
|
+
result = Client.new(@filepath, @options).apply
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Export
|
68
|
+
def initialize(filepath, options)
|
69
|
+
@filepath = filepath
|
70
|
+
@options = options
|
71
|
+
end
|
72
|
+
|
73
|
+
def run
|
74
|
+
require 'simnos/client'
|
75
|
+
result = Client.new(@filepath, @options).export
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'awesome_print'
|
3
|
+
require 'simnos/client_wrapper'
|
4
|
+
require 'simnos/converter'
|
5
|
+
require 'simnos/dsl'
|
6
|
+
require 'simnos/filterable'
|
7
|
+
|
8
|
+
module Simnos
|
9
|
+
class Client
|
10
|
+
include Filterable
|
11
|
+
MAGIC_COMMENT = <<-EOS
|
12
|
+
# -*- mode: ruby -*-
|
13
|
+
# vi: set ft=ruby :
|
14
|
+
EOS
|
15
|
+
|
16
|
+
def initialize(filepath, options = {})
|
17
|
+
@filepath = filepath
|
18
|
+
@options = options
|
19
|
+
end
|
20
|
+
|
21
|
+
def apply
|
22
|
+
Simnos.logger.info("Applying...#{@options[:dry_run] ? ' [dry-run]' : ''}")
|
23
|
+
dsl = load_file(@filepath)
|
24
|
+
|
25
|
+
dsl.snss.each do |region, sns|
|
26
|
+
@options[:region] = region
|
27
|
+
Simnos.logger.info("region: #{region}")
|
28
|
+
aws_topics_by_name = client.topics
|
29
|
+
traverse_topics(sns.topics, aws_topics_by_name)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def export
|
34
|
+
Simnos.logger.info("Exporting...#{@options[:dry_run] ? ' [dry-run]' : ''}")
|
35
|
+
|
36
|
+
topics_by_name = client.topics
|
37
|
+
|
38
|
+
path = Pathname.new(@filepath)
|
39
|
+
base_dir = path.parent
|
40
|
+
if @options[:split]
|
41
|
+
FileUtils.mkdir_p(base_dir)
|
42
|
+
topics_by_name.each do |name, aws|
|
43
|
+
Converter.new({name => aws}).convert do |dsl|
|
44
|
+
sns_file = base_dir.join("#{name}.sns")
|
45
|
+
Simnos.logger.info("Export #{sns_file}")
|
46
|
+
open(sns_file, 'wb') do |f|
|
47
|
+
f.puts MAGIC_COMMENT
|
48
|
+
f.puts dsl
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
else
|
53
|
+
Converter.new(topics_by_name).convert do |dsl|
|
54
|
+
FileUtils.mkdir_p(base_dir)
|
55
|
+
Simnos.logger.info("Export #{path}")
|
56
|
+
open(path, 'wb') do |f|
|
57
|
+
f.puts MAGIC_COMMENT
|
58
|
+
f.puts dsl
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def traverse_topics(dsl_topics_all, aws_topics_by_name)
|
67
|
+
dsl_topics = dsl_topics_all.select { |t| target?(t.name) }
|
68
|
+
# create
|
69
|
+
dsl_topics.reject { |t| aws_topics_by_name[t.name] }.each do |dsl_topic|
|
70
|
+
aws_topics_by_name[dsl_topic.name] = dsl_topic.create
|
71
|
+
end
|
72
|
+
|
73
|
+
# modify
|
74
|
+
dsl_topics.each do |dsl_topic|
|
75
|
+
next unless aws_topic = aws_topics_by_name.delete(dsl_topic.name)
|
76
|
+
|
77
|
+
dsl_topic.aws(aws_topic).modify
|
78
|
+
end
|
79
|
+
|
80
|
+
# delete
|
81
|
+
aws_topics_by_name.each do |name, aws_topic|
|
82
|
+
Simnos.logger.info("Delete Topic #{name}")
|
83
|
+
next if @options[:dry_run]
|
84
|
+
|
85
|
+
client.delete_topic(topic_arn: aws_topic[:topic].topic_arn)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def load_file(file)
|
90
|
+
open(file) do |f|
|
91
|
+
DSL.define(f.read, file, @options).result
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def client
|
96
|
+
@client_by_region ||= {}
|
97
|
+
@client_by_region[@options[:region]] ||= ClientWrapper.new(@options)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'aws-sdk'
|
3
|
+
require 'simnos/filterable'
|
4
|
+
|
5
|
+
module Simnos
|
6
|
+
class ClientWrapper
|
7
|
+
extend Forwardable
|
8
|
+
include Filterable
|
9
|
+
|
10
|
+
def_delegators :@client, *%i/delete_topic get_topic_attributes create_topic set_topic_attributes set_subscription_attributes/
|
11
|
+
|
12
|
+
def initialize(options)
|
13
|
+
@options = options
|
14
|
+
if options[:region]
|
15
|
+
@client = Aws::SNS::Client.new(region: @options[:region])
|
16
|
+
else
|
17
|
+
@client = Aws::SNS::Client.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def topic_attrs(topic_arn: )
|
22
|
+
@client.get_topic_attributes(topic_arn: topic_arn)
|
23
|
+
end
|
24
|
+
|
25
|
+
def topics
|
26
|
+
results = {}
|
27
|
+
next_token = nil
|
28
|
+
begin
|
29
|
+
resp = @client.list_topics(marker: next_token)
|
30
|
+
resp.topics.each do |t|
|
31
|
+
name = t.topic_arn.split(':').last
|
32
|
+
next unless target?(name)
|
33
|
+
results[name] = {
|
34
|
+
topic: t,
|
35
|
+
attrs: topic_attrs(topic_arn: t.topic_arn),
|
36
|
+
}
|
37
|
+
end
|
38
|
+
next_token = resp.next_token
|
39
|
+
end while next_token
|
40
|
+
results
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def topic_name(topic)
|
46
|
+
topic.topic_arn.split(':').last
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Simnos
|
4
|
+
class Converter
|
5
|
+
def initialize(topics_by_name)
|
6
|
+
@topics_by_name = topics_by_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def convert
|
10
|
+
yield output_topic(@topics_by_name)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def output_topic(topics_by_name)
|
16
|
+
path = Pathname.new(File.expand_path('../', __FILE__)).join('output_topic.erb')
|
17
|
+
ERB.new(path.read, nil, '-').result(binding)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'simnos/dsl/topic'
|
3
|
+
|
4
|
+
module Simnos
|
5
|
+
class DSL
|
6
|
+
class SNS
|
7
|
+
include Simnos::TemplateHelper
|
8
|
+
|
9
|
+
attr_reader :result
|
10
|
+
|
11
|
+
def initialize(context, topics, &block)
|
12
|
+
@context = context
|
13
|
+
|
14
|
+
@result = OpenStruct.new(
|
15
|
+
topics: topics
|
16
|
+
)
|
17
|
+
@names = topics.map(&:name)
|
18
|
+
instance_eval(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def topic(name, &block)
|
24
|
+
if @names.include?(name)
|
25
|
+
raise "#{name} is already defined"
|
26
|
+
end
|
27
|
+
|
28
|
+
@result.topics << Topic.new(@context, name, &block).result
|
29
|
+
@names << name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'simnos/utils'
|
2
|
+
|
3
|
+
module Simnos
|
4
|
+
class DSL
|
5
|
+
class Topic
|
6
|
+
include Simnos::TemplateHelper
|
7
|
+
|
8
|
+
class Result
|
9
|
+
ATTRIBUTES = %i/name display_name subscriptions_pending subscriptions_confirmed subscriptions_deleted effective_delivery_policy policy/
|
10
|
+
attr_accessor *ATTRIBUTES
|
11
|
+
|
12
|
+
def initialize(context)
|
13
|
+
@context = context
|
14
|
+
@options = context.options
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_h
|
18
|
+
Hash[ATTRIBUTES.sort.map { |name| [name, public_send(name)] }]
|
19
|
+
end
|
20
|
+
|
21
|
+
CREATE_KEYS = %i/name/
|
22
|
+
def create_option
|
23
|
+
to_h.select { |k, _| CREATE_KEYS.include?(k) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def create
|
27
|
+
Simnos.logger.info("Create Topic #{name}")
|
28
|
+
return if @options[:dry_run]
|
29
|
+
|
30
|
+
resp = client.create_topic(name: name)
|
31
|
+
{
|
32
|
+
topic: resp,
|
33
|
+
attrs: client.topic_attrs(topic_arn: resp.topic_arn)
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def aws(aws_topic)
|
38
|
+
@aws_topic = aws_topic
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def attrs_updated?
|
43
|
+
to_h
|
44
|
+
end
|
45
|
+
|
46
|
+
def modify
|
47
|
+
modify_attrs
|
48
|
+
modify_attrs_hash
|
49
|
+
end
|
50
|
+
|
51
|
+
MODIFY_ATTRS = {
|
52
|
+
display_name: 'DisplayName',
|
53
|
+
#subscriptions_pending: 'SubscriptionsPending',
|
54
|
+
#subscriptions_confirmed: 'SubscriptionsConfirmed',
|
55
|
+
#subscriptions_deleted: 'SubscriptionsDeleted',
|
56
|
+
}
|
57
|
+
def modify_attrs
|
58
|
+
MODIFY_ATTRS.each do |prop_name, attr_name|
|
59
|
+
modify_attr(send(prop_name), attr_name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def modify_attr(dsl_val, attr_name)
|
64
|
+
aws_val = @aws_topic[:attrs].attributes[attr_name]
|
65
|
+
return if dsl_val == aws_val
|
66
|
+
|
67
|
+
Simnos.logger.debug('--- aws ---')
|
68
|
+
Simnos.logger.debug(@aws_topic[:attrs].attributes.pretty_inspect)
|
69
|
+
Simnos.logger.debug('--- dsl ---')
|
70
|
+
Simnos.logger.debug(dsl_val)
|
71
|
+
Simnos.logger.info("Modify Topic `#{name}` #{attr_name} attributes")
|
72
|
+
dsl_attrs = {
|
73
|
+
attribute_name: attr_name,
|
74
|
+
attribute_value: dsl_val,
|
75
|
+
}
|
76
|
+
diff = Simnos::Utils.diff({
|
77
|
+
attribute_name: attr_name,
|
78
|
+
attribute_value: aws_val,
|
79
|
+
}, dsl_attrs,
|
80
|
+
color: @options[:color],
|
81
|
+
)
|
82
|
+
Simnos.logger.info("<diff>\n#{diff}")
|
83
|
+
return if @options[:dry_run]
|
84
|
+
|
85
|
+
client.set_topic_attributes(dsl_attrs.merge(topic_arn: @aws_topic[:topic].topic_arn))
|
86
|
+
end
|
87
|
+
|
88
|
+
def modify_attr_hash(dsl_val, attr_name)
|
89
|
+
aws_val = JSON.parse(@aws_topic[:attrs].attributes[attr_name])
|
90
|
+
return if dsl_val == aws_val
|
91
|
+
Simnos.logger.info("Modify Topic `#{name}` #{attr_name} attributes")
|
92
|
+
dsl_attrs = {
|
93
|
+
attribute_name: attr_name,
|
94
|
+
attribute_value: dsl_val,
|
95
|
+
}
|
96
|
+
diff = Simnos::Utils.diff({
|
97
|
+
attribute_name: attr_name,
|
98
|
+
attribute_value: aws_val,
|
99
|
+
}, dsl_attrs,
|
100
|
+
color: @options[:color],
|
101
|
+
)
|
102
|
+
Simnos.logger.info("<diff>\n#{diff}")
|
103
|
+
return if @options[:dry_run]
|
104
|
+
|
105
|
+
dsl_attrs.merge!(topic_arn: @aws_topic[:topic].topic_arn)
|
106
|
+
dsl_attrs[:attribute_value] = dsl_attrs[:attribute_value].to_json
|
107
|
+
if attr_name == 'Policy'
|
108
|
+
client.set_topic_attributes(dsl_attrs)
|
109
|
+
elsif attr_name == 'EffectiveDeliveryPolicy'
|
110
|
+
dsl_attrs[:attribute_name] = 'DeliveryPolicy'
|
111
|
+
client.set_topic_attributes(dsl_attrs)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
MODIFY_ATTRS_HASH = {
|
116
|
+
effective_delivery_policy: 'EffectiveDeliveryPolicy',
|
117
|
+
policy: 'Policy',
|
118
|
+
}
|
119
|
+
def modify_attrs_hash
|
120
|
+
MODIFY_ATTRS_HASH.each do |prop_name, attr_name|
|
121
|
+
modify_attr_hash(send(prop_name), attr_name)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def client
|
126
|
+
@client ||= Simnos::ClientWrapper.new(@context)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def initialize(context, name, &block)
|
131
|
+
@name = name
|
132
|
+
@context = context.merge(name: name)
|
133
|
+
|
134
|
+
@result = Result.new(@context)
|
135
|
+
@result.name = name
|
136
|
+
|
137
|
+
instance_eval(&block)
|
138
|
+
end
|
139
|
+
|
140
|
+
def result
|
141
|
+
@result
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def display_name(display_name)
|
147
|
+
@result.display_name = display_name
|
148
|
+
end
|
149
|
+
|
150
|
+
def subscriptions_pending(subscriptions_pending)
|
151
|
+
@result.subscriptions_pending = subscriptions_pending
|
152
|
+
end
|
153
|
+
|
154
|
+
def subscriptions_confirmed(subscriptions_confirmed)
|
155
|
+
@result.subscriptions_confirmed = subscriptions_confirmed
|
156
|
+
end
|
157
|
+
|
158
|
+
def subscriptions_deleted(subscriptions_deleted)
|
159
|
+
@result.subscriptions_deleted = subscriptions_deleted
|
160
|
+
end
|
161
|
+
|
162
|
+
def effective_delivery_policy
|
163
|
+
@result.effective_delivery_policy = yield
|
164
|
+
end
|
165
|
+
|
166
|
+
def policy
|
167
|
+
@result.policy = yield
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
data/lib/simnos/dsl.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
require 'simnos/template_helper'
|
3
|
+
require 'simnos/dsl/sns'
|
4
|
+
|
5
|
+
module Simnos
|
6
|
+
class DSL
|
7
|
+
include Simnos::TemplateHelper
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def define(source, filepath, options)
|
11
|
+
self.new(filepath, options) do
|
12
|
+
eval(source, binding, filepath)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :result
|
18
|
+
|
19
|
+
def initialize(filepath, options, &block)
|
20
|
+
@filepath = filepath
|
21
|
+
@result = OpenStruct.new(snss: Hashie::Mash.new)
|
22
|
+
|
23
|
+
@context = Hashie::Mash.new(
|
24
|
+
filepath: filepath,
|
25
|
+
templates: {},
|
26
|
+
options: options,
|
27
|
+
)
|
28
|
+
|
29
|
+
instance_eval(&block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def require(file)
|
33
|
+
albfile = (file =~ %r|\A/|) ? file : File.expand_path(File.join(File.dirname(@filepath), file))
|
34
|
+
|
35
|
+
if File.exist?(albfile)
|
36
|
+
instance_eval(File.read(albfile), albfile)
|
37
|
+
elsif File.exist?("#{albfile}.rb")
|
38
|
+
instance_eval(File.read("#{albfile}.rb"), "#{albfile}.rb")
|
39
|
+
else
|
40
|
+
Kernel.require(file)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def template(name, &block)
|
45
|
+
@context.templates[name.to_s] = block
|
46
|
+
end
|
47
|
+
|
48
|
+
def sns(region = nil, &block)
|
49
|
+
current_region = @context[:region] = region || ENV['AWS_DEFAULT_REGION'] || ENV.fetch('AWS_REGION')
|
50
|
+
topics = @result.snss[current_region] || []
|
51
|
+
@result.snss[current_region] = SNS.new(@context, topics, &block).result
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Simnos
|
2
|
+
module Filterable
|
3
|
+
def target?(topic_name)
|
4
|
+
unless @options[:includes].empty?
|
5
|
+
unless @options[:includes].include?(topic_name)
|
6
|
+
Simnos.logger.debug("skip topic(with include-names option) #{topic_name}")
|
7
|
+
return false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
unless @options[:excludes].empty?
|
12
|
+
if @options[:excludes].any? { |regex| topic_name =~ regex }
|
13
|
+
Simnos.logger.debug("skip topic(with exclude-names option) #{topic_name}")
|
14
|
+
return false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
sns do
|
2
|
+
<%- topics_by_name.each do |name, topic| -%>
|
3
|
+
topic <%= name.inspect %> do
|
4
|
+
display_name <%= topic[:attrs].attributes['DisplayName'].inspect %>
|
5
|
+
|
6
|
+
effective_delivery_policy do
|
7
|
+
<%= JSON.parse(topic[:attrs].attributes['EffectiveDeliveryPolicy']).pretty_inspect.gsub(/^/, ' ') -%>
|
8
|
+
end
|
9
|
+
|
10
|
+
policy do
|
11
|
+
<%= JSON.parse(topic[:attrs].attributes['Policy']).pretty_inspect.gsub(/^/, ' ') -%>
|
12
|
+
end
|
13
|
+
end
|
14
|
+
<%- if topics_by_name.keys.last != name -%>
|
15
|
+
|
16
|
+
<%- end -%>
|
17
|
+
<%- end -%>
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Simnos
|
2
|
+
module TemplateHelper
|
3
|
+
def include_template(template_name, context = {})
|
4
|
+
template = @context.templates[template_name.to_s]
|
5
|
+
|
6
|
+
unless template
|
7
|
+
raise "Template `#{template_name}' is not defined"
|
8
|
+
end
|
9
|
+
|
10
|
+
context_org = @context
|
11
|
+
@context = @context.merge(context)
|
12
|
+
instance_eval(&template)
|
13
|
+
@context = context_org
|
14
|
+
end
|
15
|
+
|
16
|
+
def context
|
17
|
+
@context
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/simnos/utils.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'diffy'
|
2
|
+
|
3
|
+
module Simnos
|
4
|
+
class Utils
|
5
|
+
class << self
|
6
|
+
def normalize_hash(hash)
|
7
|
+
hash.dup.each do |k, v|
|
8
|
+
if v.kind_of?(Array)
|
9
|
+
if v.first.kind_of?(Hash)
|
10
|
+
hash[k] = v.map { |o| normalize_hash(o) }
|
11
|
+
elsif v.first.respond_to?(:to_h)
|
12
|
+
hash[k] = v.map { |o| normalize_hash(o.to_h) }
|
13
|
+
end
|
14
|
+
elsif v.respond_to?(:to_h)
|
15
|
+
hash[k] = normalize_hash(v.to_h)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
sort_keys(hash)
|
19
|
+
end
|
20
|
+
|
21
|
+
def sort_keys(hash)
|
22
|
+
hash = Hash[hash.sort]
|
23
|
+
hash.each do |k, v|
|
24
|
+
if v.kind_of?(Array)
|
25
|
+
if v.first.kind_of?(Hash)
|
26
|
+
hash[k] = v.map { |h| sort_keys(h) }
|
27
|
+
end
|
28
|
+
elsif v.kind_of?(Hash)
|
29
|
+
hash[k] = sort_keys(v)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
hash
|
33
|
+
end
|
34
|
+
|
35
|
+
def diff(obj1, obj2, options = {})
|
36
|
+
diffy = Diffy::Diff.new(
|
37
|
+
obj1.pretty_inspect,
|
38
|
+
obj2.pretty_inspect,
|
39
|
+
:diff => '-u'
|
40
|
+
)
|
41
|
+
|
42
|
+
out = diffy.to_s(options[:color] ? :color : :text).gsub(/\s+\z/m, '')
|
43
|
+
out.gsub!(/^/, options[:indent]) if options[:indent]
|
44
|
+
out
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/simnos.rb
ADDED
data/simnos.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'simnos/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "simnos"
|
8
|
+
spec.version = Simnos::VERSION
|
9
|
+
spec.authors = ["wata"]
|
10
|
+
spec.email = ["wata.gm@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Codenize AWS SNS}
|
13
|
+
spec.description = %q{Manage AWS SNS by DSL}
|
14
|
+
spec.homepage = "https://github.com/codenize-tools/simnos"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_dependency 'aws-sdk', '~> 2'
|
25
|
+
spec.add_dependency 'hashie'
|
26
|
+
spec.add_dependency 'diffy'
|
27
|
+
spec.add_dependency 'term-ansicolor'
|
28
|
+
|
29
|
+
spec.add_development_dependency 'bundler', '~> 1.14'
|
30
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
31
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
32
|
+
spec.add_development_dependency 'pry-byebug'
|
33
|
+
spec.add_development_dependency 'awesome_print'
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simnos
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.beta1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- wata
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aws-sdk
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: hashie
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: diffy
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: term-ansicolor
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.14'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.14'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '10.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '10.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: pry-byebug
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: awesome_print
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: Manage AWS SNS by DSL
|
140
|
+
email:
|
141
|
+
- wata.gm@gmail.com
|
142
|
+
executables:
|
143
|
+
- simnos
|
144
|
+
extensions: []
|
145
|
+
extra_rdoc_files: []
|
146
|
+
files:
|
147
|
+
- ".gitignore"
|
148
|
+
- ".rspec"
|
149
|
+
- ".travis.yml"
|
150
|
+
- Gemfile
|
151
|
+
- LICENSE.txt
|
152
|
+
- README.md
|
153
|
+
- Rakefile
|
154
|
+
- bin/console
|
155
|
+
- bin/setup
|
156
|
+
- exe/simnos
|
157
|
+
- lib/simnos.rb
|
158
|
+
- lib/simnos/cli.rb
|
159
|
+
- lib/simnos/client.rb
|
160
|
+
- lib/simnos/client_wrapper.rb
|
161
|
+
- lib/simnos/converter.rb
|
162
|
+
- lib/simnos/dsl.rb
|
163
|
+
- lib/simnos/dsl/sns.rb
|
164
|
+
- lib/simnos/dsl/topic.rb
|
165
|
+
- lib/simnos/filterable.rb
|
166
|
+
- lib/simnos/output_topic.erb
|
167
|
+
- lib/simnos/template_helper.rb
|
168
|
+
- lib/simnos/utils.rb
|
169
|
+
- lib/simnos/version.rb
|
170
|
+
- simnos.gemspec
|
171
|
+
homepage: https://github.com/codenize-tools/simnos
|
172
|
+
licenses:
|
173
|
+
- MIT
|
174
|
+
metadata: {}
|
175
|
+
post_install_message:
|
176
|
+
rdoc_options: []
|
177
|
+
require_paths:
|
178
|
+
- lib
|
179
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - ">="
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '0'
|
184
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
185
|
+
requirements:
|
186
|
+
- - ">"
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
version: 1.3.1
|
189
|
+
requirements: []
|
190
|
+
rubyforge_project:
|
191
|
+
rubygems_version: 2.6.11
|
192
|
+
signing_key:
|
193
|
+
specification_version: 4
|
194
|
+
summary: Codenize AWS SNS
|
195
|
+
test_files: []
|