gclouder 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +104 -0
  3. data/bin/gclouder +7 -0
  4. data/lib/gclouder/config/arguments.rb +35 -0
  5. data/lib/gclouder/config/cli_args.rb +77 -0
  6. data/lib/gclouder/config/cluster.rb +66 -0
  7. data/lib/gclouder/config/defaults.rb +35 -0
  8. data/lib/gclouder/config/files/project.rb +24 -0
  9. data/lib/gclouder/config/project.rb +34 -0
  10. data/lib/gclouder/config/resource_representations.rb +31 -0
  11. data/lib/gclouder/config_loader.rb +25 -0
  12. data/lib/gclouder/config_section.rb +26 -0
  13. data/lib/gclouder/dependencies.rb +11 -0
  14. data/lib/gclouder/gcloud.rb +39 -0
  15. data/lib/gclouder/gsutil.rb +25 -0
  16. data/lib/gclouder/header.rb +9 -0
  17. data/lib/gclouder/helpers.rb +77 -0
  18. data/lib/gclouder/logging.rb +181 -0
  19. data/lib/gclouder/mappings/argument.rb +31 -0
  20. data/lib/gclouder/mappings/file.rb +31 -0
  21. data/lib/gclouder/mappings/property.rb +31 -0
  22. data/lib/gclouder/mappings/resource_representation.rb +31 -0
  23. data/lib/gclouder/monkey_patches/array.rb +19 -0
  24. data/lib/gclouder/monkey_patches/boolean.rb +12 -0
  25. data/lib/gclouder/monkey_patches/hash.rb +44 -0
  26. data/lib/gclouder/monkey_patches/ipaddr.rb +10 -0
  27. data/lib/gclouder/monkey_patches/string.rb +30 -0
  28. data/lib/gclouder/resource.rb +63 -0
  29. data/lib/gclouder/resource_cleaner.rb +58 -0
  30. data/lib/gclouder/resources/compute/addresses.rb +108 -0
  31. data/lib/gclouder/resources/compute/bgp-vpns.rb +220 -0
  32. data/lib/gclouder/resources/compute/disks.rb +99 -0
  33. data/lib/gclouder/resources/compute/firewall_rules.rb +82 -0
  34. data/lib/gclouder/resources/compute/instances.rb +147 -0
  35. data/lib/gclouder/resources/compute/networks/subnets.rb +104 -0
  36. data/lib/gclouder/resources/compute/networks.rb +110 -0
  37. data/lib/gclouder/resources/compute/project_info/ssh_keys.rb +171 -0
  38. data/lib/gclouder/resources/compute/routers.rb +83 -0
  39. data/lib/gclouder/resources/compute/vpns.rb +199 -0
  40. data/lib/gclouder/resources/container/clusters.rb +257 -0
  41. data/lib/gclouder/resources/container/node_pools.rb +193 -0
  42. data/lib/gclouder/resources/dns.rb +390 -0
  43. data/lib/gclouder/resources/logging/sinks.rb +98 -0
  44. data/lib/gclouder/resources/project/iam_policy_binding.rb +293 -0
  45. data/lib/gclouder/resources/project.rb +85 -0
  46. data/lib/gclouder/resources/project_id.rb +71 -0
  47. data/lib/gclouder/resources/pubsub/subscriptions.rb +100 -0
  48. data/lib/gclouder/resources/pubsub/topics.rb +95 -0
  49. data/lib/gclouder/resources/storagebuckets.rb +103 -0
  50. data/lib/gclouder/resources/validate/global.rb +27 -0
  51. data/lib/gclouder/resources/validate/local.rb +68 -0
  52. data/lib/gclouder/resources/validate/region.rb +28 -0
  53. data/lib/gclouder/resources/validate/remote.rb +78 -0
  54. data/lib/gclouder/resources.rb +148 -0
  55. data/lib/gclouder/shell.rb +71 -0
  56. data/lib/gclouder/version.rb +5 -0
  57. data/lib/gclouder.rb +278 -0
  58. metadata +102 -0
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "stringio"
4
+ require "logger"
5
+
6
+ module GClouder
7
+ module Logging
8
+ class << self
9
+ attr_accessor :appenders
10
+ end
11
+
12
+ @@good ||= 0
13
+ @@bad ||= 0
14
+ @@add ||= 0
15
+ @@change ||= 0
16
+ @@remove ||= 0
17
+ @@warning ||= 0
18
+
19
+ def self.loggers
20
+ @loggers ||= setup
21
+ end
22
+
23
+ def self.included(klass)
24
+ klass.extend Logging
25
+ end
26
+
27
+ def self.setup
28
+ appenders.map do |appender|
29
+ appender[:appender].formatter = appender[:format]
30
+ appender[:appender].level = Logger::DEBUG
31
+ appender[:appender]
32
+ end
33
+ end
34
+
35
+ def self.log(message, level: :info, indent: 0, heading: false, title: false)
36
+ loggers.each do |log|
37
+ message = case message
38
+ when "" || nil
39
+ [ "" ]
40
+ when String
41
+ message.split("\n")
42
+ when Array
43
+ message
44
+ else
45
+ fatal "unknown message type: #{message.class}"
46
+ end
47
+
48
+ message = [ "", "" ] + message if title
49
+ message = [ "" ] + message if heading
50
+
51
+ if title
52
+ prefix = " " * 1
53
+ else
54
+ prefix = " " * indent
55
+ end
56
+
57
+ message.each { |line| log.send(level, prefix + line) }
58
+ end
59
+ end
60
+
61
+ def self.appenders
62
+ @appenders ||= [Appenders.stdout, Appenders.file]
63
+ end
64
+
65
+ module Appenders
66
+ def self.stdout
67
+ {
68
+ appender: ::Logger.new(STDOUT),
69
+ format: proc { |_, _, _, message| "#{message}\n" }
70
+ }
71
+ end
72
+
73
+ def self.file
74
+ {
75
+ appender: ::Logger.new(File.join(File.dirname(__FILE__), "../../log.txt")),
76
+ format: proc { |severity, datetime, _, message| "#{severity} - #{datetime}: #{message}\n" }
77
+ }
78
+ end
79
+
80
+ def self.stringio(obj = StringIO.new)
81
+ {
82
+ appender: ::Logger.new(StringIO.new),
83
+ format: proc { |severity, datetime, _, message| "#{severity} - #{datetime}: #{message}\n" }
84
+ }
85
+ end
86
+ end
87
+
88
+ def debug(message = "")
89
+ Logging.log message, level: :debug
90
+ end
91
+
92
+ def info(message = "", indent: 0, heading: false, title: false)
93
+ Logging.log message, level: :info, indent: indent, heading: heading, title: title
94
+ end
95
+
96
+ def warn(message = "", heading: false)
97
+ Logging.log message, level: :warn, heading: heading
98
+ end
99
+
100
+ def error(message = "", heading: false)
101
+ Logging.log message, level: :error, heading: heading
102
+ end
103
+
104
+ def fatal(message = "", status: 1, heading: false)
105
+ Logging.log "\n#{message}", level: :fatal, heading: heading
106
+ exit status
107
+ end
108
+
109
+ def resource_state(message, indent: 0, heading: false, level: :info)
110
+ Logging.log "#{' ' * indent}#{message}", level: level, heading: heading
111
+ end
112
+
113
+ def good(message, indent: 3, heading: false)
114
+ @@good += 1
115
+ resource_state("#{Symbols.tick} #{message}", indent: indent, heading: heading)
116
+ end
117
+
118
+ def bad(message, indent: 3, heading: false)
119
+ @@bad += 1
120
+ resource_state("#{Symbols.x} #{message}", indent: indent, heading: heading)
121
+ end
122
+
123
+ def add(message, indent: 3, heading: false)
124
+ @@add += 1
125
+ resource_state("#{Symbols.plus} #{message}", indent: indent, heading: heading)
126
+ end
127
+
128
+ def change(message, indent: 3, heading: false)
129
+ @@change += 1
130
+ resource_state("#{Symbols.o} #{message}", indent: indent, heading: heading)
131
+ end
132
+
133
+ def remove(message, indent: 3, heading: false)
134
+ @@remove += 1
135
+ resource_state("#{Symbols.minus} #{message}", indent: indent, heading: heading)
136
+ end
137
+
138
+ def warning(message, indent: 3, heading: false)
139
+ @@warning += 1
140
+ resource_state("#{Symbols.bang} #{message}", indent: indent, heading: heading)
141
+ end
142
+
143
+ module Symbols
144
+ def self.tick
145
+ "✓".green
146
+ end
147
+
148
+ def self.x
149
+ "✗".red
150
+ end
151
+
152
+ def self.plus
153
+ "+".blue
154
+ end
155
+
156
+ def self.o
157
+ "o".yellow
158
+ end
159
+
160
+ def self.minus
161
+ "-".red
162
+ end
163
+
164
+ def self.bang
165
+ "!".yellow
166
+ end
167
+ end
168
+
169
+ def self.report
170
+ Logging.log "\n\n [report]"
171
+ Logging.log " "
172
+ Logging.log " #{Symbols.tick} - #{@@good}"
173
+ Logging.log " #{Symbols.bang} - #{@@warning}"
174
+ Logging.log " #{Symbols.x} - #{@@bad}"
175
+ Logging.log " "
176
+ Logging.log " #{Symbols.plus} - #{@@add}"
177
+ Logging.log " #{Symbols.o} - #{@@change}"
178
+ Logging.log " #{Symbols.minus} - #{@@remove}"
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "yaml"
4
+
5
+ module GClouder
6
+ module Mappings
7
+ module Argument
8
+ include GClouder::Logging
9
+
10
+ def self.mappings
11
+ GClouder::ConfigLoader.load("../../assets/mappings/argument")
12
+ end
13
+
14
+ def self.load
15
+ mappings
16
+ end
17
+
18
+ def mappings
19
+ Argument.mappings
20
+ end
21
+
22
+ def self.included(klass)
23
+ klass.extend Argument
24
+ end
25
+
26
+ def self.section(section)
27
+ GClouder::ConfigSection.find(section, mappings)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "yaml"
4
+
5
+ module GClouder
6
+ module Mappings
7
+ module File
8
+ include GClouder::Logging
9
+
10
+ def self.mappings
11
+ GClouder::ConfigLoader.load("../../assets/mappings/file")
12
+ end
13
+
14
+ def self.load
15
+ mappings
16
+ end
17
+
18
+ def file
19
+ File.mappings
20
+ end
21
+
22
+ def self.included(klass)
23
+ klass.extend File
24
+ end
25
+
26
+ def self.section(section)
27
+ GClouder::ConfigSection.find(section, mappings)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "yaml"
4
+
5
+ module GClouder
6
+ module Mappings
7
+ module Property
8
+ include GClouder::Logging
9
+
10
+ def self.mappings
11
+ YAML.load_file(::File.join(::File.dirname(__FILE__), "../../../assets/mappings/property.yml"))
12
+ end
13
+
14
+ def self.load
15
+ mappings
16
+ end
17
+
18
+ def mappings
19
+ Property.mappings
20
+ end
21
+
22
+ def self.included(klass)
23
+ klass.extend Property
24
+ end
25
+
26
+ def self.section(section)
27
+ GClouder::ConfigSection.find(section, mappings)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "yaml"
4
+
5
+ module GClouder
6
+ module Mappings
7
+ module ResourceRepresentation
8
+ include GClouder::Logging
9
+
10
+ def self.mappings
11
+ GClouder::ConfigLoader.load("../../assets/resource_representations")
12
+ end
13
+
14
+ def self.load
15
+ mappings
16
+ end
17
+
18
+ def resource_representation
19
+ ResourceRepresentation.mappings
20
+ end
21
+
22
+ def self.included(klass)
23
+ klass.extend ResourceRepresentation
24
+ end
25
+
26
+ def self.section(section)
27
+ GClouder::ConfigSection.find(section, mappings)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ class Array
4
+ def clean
5
+ uniq.delete_if(&:nil?)
6
+ end
7
+
8
+ def to_snake_keys
9
+ each_with_index do |v, i|
10
+ self[i] = v.to_snake_keys if v.is_a?(Hash)
11
+ end
12
+ self
13
+ end
14
+
15
+ def fetch_with_default(key, name, default)
16
+ results = find { |e| e[key] == name }
17
+ results || default
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Boolean
4
+ end
5
+
6
+ class TrueClass
7
+ include Boolean
8
+ end
9
+
10
+ class FalseClass
11
+ include Boolean
12
+ end
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ class Hash
4
+ # sourced from: https://github.com/futurechimp/plissken/blob/master/lib/plissken/ext/hash/to_snake_keys.rb
5
+ def to_snake_keys(value = self)
6
+ case value
7
+ when Array
8
+ value.map { |v| to_snake_keys(v) }
9
+ when Hash
10
+ snake_hash(value)
11
+ else
12
+ value
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def snake_hash(value)
19
+ Hash[value.map { |k, v| [underscore_key(k), to_snake_keys(v)] }]
20
+ end
21
+
22
+ def underscore_key(k)
23
+ if k.is_a? Symbol
24
+ underscore(k.to_s)
25
+ elsif k.is_a? String
26
+ underscore(k)
27
+ else
28
+ k
29
+ end
30
+ end
31
+
32
+ def underscore(string)
33
+ string.gsub(/::/, '/')
34
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
35
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
36
+ .tr('-', '_')
37
+ .downcase
38
+ end
39
+ end
40
+
41
+ class DeepMergeHash < Hash
42
+ include Hashie::Extensions::MergeInitializer
43
+ include Hashie::Extensions::DeepMerge
44
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ class IPAddr
4
+ def self.valid?(obj)
5
+ IPAddr.new(obj)
6
+ true
7
+ rescue IPAddr::InvalidAddressError
8
+ false
9
+ end
10
+ end
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ class String
4
+ include GClouder::Shell
5
+
6
+ def snakecase
7
+ self.gsub(/::/, '/').
8
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
9
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
10
+ tr("-", "_").
11
+ downcase
12
+ end
13
+
14
+ def mixedcase
15
+ self.split('_').collect(&:capitalize).join.sub(/^[A-Z]/, &:downcase)
16
+ end
17
+
18
+ def truncate(length = 32)
19
+ raise 'Pleasant: Length should be greater than 3' unless length > 3
20
+
21
+ truncated_string = self.to_s
22
+
23
+ if truncated_string.length > length
24
+ truncated_string = truncated_string[0...(length - 3)]
25
+ truncated_string += "..."
26
+ end
27
+
28
+ truncated_string
29
+ end
30
+ end
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouder
4
+ module Resource
5
+ include GClouder::GCloud
6
+ include GClouder::Config::CLIArgs
7
+ include GClouder::Logging
8
+
9
+ def self.feedback(action, resource, name, extra_info: nil, indent: 3, silent: false)
10
+ return if silent
11
+ send action, "#{name} #{extra_info}", indent: indent
12
+ end
13
+
14
+ def self.resource?(resource, name, args = nil, filter_key: "name", filter: "#{filter_key} ~ ^#{name}$", project_id: nil, silent: false, extra_info: nil, indent: 3)
15
+ exists = \
16
+ gcloud("#{resource} list --filter '#{filter}' #{args} | jq '. | length'", force: true, silent: silent, project_id: project_id)
17
+
18
+ if exists
19
+ feedback("good", resource, name, extra_info: extra_info, indent: indent, silent: silent)
20
+ return true
21
+ end
22
+
23
+ false
24
+ end
25
+
26
+ def self.describe(resource, name, args = nil, force: true, failure: true, silent: false, project_id: nil)
27
+ return if resource?(resource, name, silent: silent, project_id: project_id)
28
+ gcloud "#{resource} describe #{name} #{args}", force: force, failure: failure, silent: silent, project_id: project_id
29
+ end
30
+
31
+ def self.ensure(resource, name, args = nil, project_id: nil, extra_info: nil, silent: false, indent: 3, filter_key: "name")
32
+ return if resource?(resource, name, project_id: project_id, extra_info: extra_info, silent: silent, indent: indent, filter_key: filter_key)
33
+ feedback("add", resource, name, extra_info: extra_info, indent: indent, silent: silent)
34
+ gcloud "#{resource} create #{name} #{args}", project_id: project_id, silent: silent
35
+ end
36
+
37
+ def self.purge(resource, name, args = nil, project_id: nil, silent: false, indent: 3)
38
+ return unless resource?(resource, name, project_id: project_id, silent: true, indent: indent)
39
+ extra_info = "[skipped] (--purge not specified)" unless cli_args[:purge]
40
+ feedback("remove", resource, name, extra_info: extra_info, indent: indent, silent: silent)
41
+ gcloud "#{resource} delete #{name} #{args}", project_id: project_id if cli_args[:purge]
42
+ end
43
+
44
+ def self.list(resource, args = nil, snake_case: false, project_id: nil)
45
+ list = gcloud "#{resource} list #{args}", force: true, project_id: project_id
46
+ snake_case ? list.to_snake_keys : list
47
+ end
48
+
49
+ module Find
50
+ include GClouder::GCloud
51
+
52
+ def self.zone(resource, name, region, project_id: nil)
53
+ zones = %w(b c d).map { |zone| region + "-" + zone }
54
+
55
+ zones.each do |zone|
56
+ return zone if gcloud "#{resource} describe #{name} --zone=#{zone}", force: true, failure: false, silent: true, project_id: project_id
57
+ end
58
+
59
+ false
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouder
4
+ module Resource
5
+ module Cleaner
6
+ def self.included(klass)
7
+ klass.extend Cleaner
8
+ end
9
+
10
+ def clean
11
+ return if undefined.empty?
12
+
13
+ header :clean
14
+
15
+ undefined.each do |namespace, resources|
16
+ info namespace, indent: 2, heading: true
17
+ info
18
+ resources.each do |resource|
19
+ message = resource['name']
20
+ message += " (not defined locally)"
21
+ warning message
22
+ # FIXME: enable purge on --purge flag..
23
+ #Resource.symlink.send(:purge, namespace, resource)
24
+ end
25
+ end
26
+ end
27
+
28
+
29
+ module Default
30
+ def self.cleaner
31
+ Proc.new do |resources, resource|
32
+ resources.map do |r|
33
+ r['name'] == resource
34
+ end.include?(true)
35
+ end
36
+ end
37
+ end
38
+
39
+ def cleaner
40
+ (self.const_defined?(:Cleaner) && self::Cleaner.respond_to?(:custom)) ? self::Cleaner.custom : Default.cleaner
41
+ end
42
+
43
+ def undefined
44
+ self::Remote.list.each_with_object({}) do |(namespace, resources), collection|
45
+ resources.each do |resource|
46
+ namespace_resources = self::Local.list[namespace]
47
+
48
+ # accept PROC for custom matching of undefined resources
49
+ next if namespace_resources && cleaner.call(namespace_resources, resource["name"])
50
+
51
+ collection[namespace] ||= []
52
+ collection[namespace] << resource
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouder
4
+ module Resources
5
+ module Compute
6
+ module Addresses
7
+ include GClouder::GCloud
8
+ include GClouder::Logging
9
+ include GClouder::Resource::Cleaner
10
+
11
+ def self.header(stage = :ensure)
12
+ info "[#{stage}] compute / addresses", title: true
13
+ end
14
+
15
+ def self.ensure
16
+ return if Local.list.empty?
17
+ header
18
+
19
+ if Local.list.key?("global")
20
+ info "global", indent: 2, heading: true
21
+ info
22
+ Local.list["global"].each { |address| Address.ensure(address["name"], "--global") }
23
+ end
24
+
25
+ Local.list.each do |region, addresses|
26
+ next if region == "global"
27
+ next unless addresses
28
+
29
+ info region, indent: 2, heading: true
30
+ info
31
+ addresses.each do |address|
32
+ Address.ensure(address["name"], "--region #{region}")
33
+ end
34
+ end
35
+ end
36
+
37
+ def self.check
38
+ return if Remote.list.empty?
39
+ return if Local.list.empty?
40
+ header :check
41
+ Resources::Validate::Remote.instances(Local.list, Remote.list)
42
+ end
43
+
44
+ def self.validate
45
+ return if Local.list.empty?
46
+ header :validate
47
+ Local.validate
48
+ end
49
+
50
+ module Local
51
+ include GClouder::Config::Project
52
+
53
+ def self.list
54
+ addresses = global.merge regional
55
+ addresses.delete_if { |_k, v| v.empty? }
56
+ end
57
+
58
+ def self.global
59
+ Resources::Global.instances(path: ["addresses"])
60
+ end
61
+
62
+ def self.section
63
+ %w(compute addresses)
64
+ end
65
+
66
+ def self.validate
67
+ Resources::Validate::Region.instances(
68
+ list,
69
+ required_keys: GClouder::Config::Arguments.required(section),
70
+ permitted_keys: GClouder::Config::Arguments.permitted(section)
71
+ )
72
+ end
73
+
74
+ def self.regional
75
+ resources = Resources::Region.instances(path: ["addresses"])
76
+
77
+ # get_instances_from_region assumes all keys have configs.. this normalizes
78
+ # the data to match self.global
79
+ resources.each { |k, v| resources[k] = v.to_a.flatten.delete_if(&:nil?) }
80
+ end
81
+ end
82
+
83
+ module Remote
84
+ def self.list
85
+ Resources::Remote.instances(
86
+ path: ["compute", "addresses"]
87
+ )
88
+ end
89
+ end
90
+
91
+ module Address
92
+ def self.ensure(name, args)
93
+ GClouder::Resource.ensure :"compute addresses", name, args
94
+ end
95
+
96
+ def self.purge(namespace, name, args = nil)
97
+ end
98
+ end
99
+
100
+ module Resource
101
+ def self.symlink
102
+ Module.const_get "#{self.name.delete(/::Resource$/)}::Address"
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end