gclouder 0.1.1

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.
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