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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0eb4fecbee428b1cdc7c390f0503ccdc4448cbff
4
+ data.tar.gz: 3f552005ae332e6ef91fd32cf207f827e4f2c8b6
5
+ SHA512:
6
+ metadata.gz: 4ee77003e814011b94d28ab3f438973ad3e49dea34893e2f6ea20f86d132bbe5e113b08a8c7d4affb90dc1d710f0edc6d4a92430cd5ecbab87f8164485248c1b
7
+ data.tar.gz: 0307d6e85947977a12c5a30a5d054b0c738a71341b99691351b2e1ae1ff51eb103982821a3532fcee33a4d06c968f2f6bcd9c66f6a22cc0ea3591122339619d0
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ ## GClouder
2
+
3
+ Google Cloud Resource Deployer
4
+
5
+ ### Usage
6
+
7
+ #### Authentication
8
+
9
+ Authenticate against the GCP API:
10
+ ```
11
+ gcloud auth login
12
+ gcloud auth application-default login
13
+ ```
14
+
15
+ #### Examples
16
+
17
+ ```
18
+ # output validation then exit
19
+ ./bin/gclouder -c conf/<config>.yaml --validate-only
20
+
21
+ # only execute non-state-changing commands (e.g: queries)
22
+ ./bin/gclouder -c conf/<config>.yaml --dry-run
23
+
24
+ # apply the config
25
+ ./bin/gclouder -c conf/<config>.yaml
26
+ ```
27
+
28
+ #### Dependencies
29
+
30
+ ##### Google Cloud SDK
31
+
32
+ Please see: https://cloud.google.com/sdk/downloads
33
+
34
+ ##### Ruby
35
+
36
+ Requires a modern version of Ruby (>= 2.4), you can use rbenv or brew to install one, e.g:
37
+
38
+ ```
39
+ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
40
+ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
41
+ echo 'eval "$(rbenv init -)"' >> ~/.bashrc
42
+ . ~/.bashrc
43
+ rbenv install 2.4.0
44
+ rbenv global 2.4.0 # or `rbenv local 2.4.0` in gclouder dir
45
+ ```
46
+
47
+ ##### Gems
48
+
49
+ Install `gclouder` gem dependencies:
50
+ ```
51
+ cd gclouder
52
+ gem install bundler
53
+ bundle install
54
+ ```
55
+
56
+ ### Testing
57
+
58
+
59
+ Test coverage is currently limited to libraries which are peripheral to the core functionality of this app, i.e: tests only exist for methods which can be independently tested of the GCP API.
60
+
61
+ There are plans to add integration tests at a later date.
62
+
63
+ To run the tests use one of the following:
64
+
65
+ Run once:
66
+ ```
67
+ rake
68
+ ```
69
+
70
+ To monitor changes to project files during development:
71
+ ```
72
+ rake guard
73
+ ```
74
+
75
+ ### Notes
76
+
77
+ Each resource is designed to do the following:
78
+
79
+ * validation of local configuration by:
80
+ * check parameters are valid arguments
81
+ * check required parameters are set
82
+ * check types are correct
83
+
84
+ * create remote instances which are defined locally
85
+
86
+ * check remote instance dont differs from local instance definitions
87
+
88
+ * remove instances which are no longer defined locally yet exist remotely
89
+
90
+ ### Why?
91
+ * Google Deployment Manager is unable to manipulate resources not managed by itself
92
+ * It's not possible to resize things like clusters without using gcloud(1)
93
+ * Not all resources are supported by Google Deployment Manager
94
+ * Greater control over the magic that happens between our resource definitions and the remote resources
95
+ * Inter-project resources
96
+
97
+ ### Gem
98
+
99
+ To build and install a gem run:
100
+
101
+ ```
102
+ rake build
103
+ gem install pkg/gclouder-<version>.gem
104
+ ```
data/bin/gclouder ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "../lib"))
4
+
5
+ require "gclouder"
6
+
7
+ GClouder.run
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "yaml"
4
+
5
+ module GClouder
6
+ module Config
7
+ module Arguments
8
+ include GClouder::Logging
9
+
10
+ def self.arguments
11
+ GClouder::ConfigLoader.load("../../assets/arguments")
12
+ end
13
+
14
+ def self.load
15
+ arguments
16
+ end
17
+
18
+ def arguments
19
+ Arguments.arguments
20
+ end
21
+
22
+ def self.included(klass)
23
+ klass.extend Arguments
24
+ end
25
+
26
+ def self.permitted(section)
27
+ GClouder::ConfigSection.find(section, arguments)
28
+ end
29
+
30
+ def self.required(section)
31
+ GClouder::ConfigSection.find(section, arguments).delete_if { |key, values| ! values["required"] }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouder
4
+ module Config
5
+ module CLIArgs
6
+ def self.cli_args
7
+ @cli_args ||= { debug: false }
8
+ end
9
+
10
+ def cli_args
11
+ CLIArgs.cli_args
12
+ end
13
+
14
+ def self.included(klass)
15
+ klass.extend CLIArgs
16
+ end
17
+
18
+ def self.valid_resources
19
+ GClouder.resources.map { |resource| resource[:name] }
20
+ end
21
+
22
+ def self.load
23
+ option_parser = Trollop::Parser.new do
24
+ banner GClouder::Header.display + "\n \n "
25
+
26
+ # required
27
+ opt :config, "path to config file\n ", type: :string, required: true
28
+
29
+ # level of operation
30
+ opt :dry_run, "passive mode"
31
+ opt :purge, "remove unmanaged resources (destructive!)\n "
32
+
33
+ # authentication / for automation
34
+ opt :activate_service_accounts, "activate service account(s) (for use when running using service accounts, i.e: with CI)"
35
+ opt :keys_dir, "path to directory with service account key files (for use with --activate-service-accounts)\n ", type: :string
36
+
37
+ # which resources / actions
38
+ # FIXME: integrate checks for required permissions into Project module
39
+ opt :bootstrap, "create project (requires being run as non-service account)"
40
+ # this should be type: proc and validate that the params match one of: validate, ensure, clean
41
+ opt :stages, "which stages to run (validate,ensure,clean) [csv]", type: :string
42
+ opt :resources, "which resources to update [csv]", type: :string
43
+ opt :skip_cross_project_resources, "skip resources which don't reside in main project\n "
44
+
45
+ # output
46
+ opt :debug, "print commands to be executed, and stack traces"
47
+ opt :trace, "print stack traces"
48
+ opt :no_color, "disable color\n \n "
49
+ end
50
+
51
+ @cli_args = Trollop.with_standard_exception_handling(option_parser) do
52
+ raise Trollop::HelpNeeded if ARGV.empty?
53
+ option_parser.parse ARGV
54
+ end
55
+
56
+ String.disable_colorization = @cli_args[:no_color]
57
+
58
+ if @cli_args[:resources]
59
+ @cli_args[:resources].split(',').each do |resource|
60
+ unless valid_resources.include?(resource)
61
+ puts "valid resources: #{valid_resources.join(', ')}"
62
+ puts "invalid resource for --resources flag: #{resource}"
63
+ exit 1
64
+ end
65
+ end
66
+ end
67
+
68
+ check
69
+ end
70
+
71
+ def self.check
72
+ raise ArgumentError, "config file not specified" unless cli_args[:config_given]
73
+ raise ArgumentError, "config file not readable: #{cli_args[:config]}" unless File.readable?(cli_args[:config])
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouder
4
+ module Config
5
+ class Cluster < DeepMergeHash
6
+ def context(context)
7
+ dup = self.dup
8
+
9
+ permitted_keys = case context
10
+ when :create_cluster
11
+ [
12
+ "additional_zones",
13
+ "async",
14
+ "cluster_ipv4_cidr",
15
+ "disable_addons",
16
+ "disk_size",
17
+ "no_enable_cloud_endpoints",
18
+ "no_enable_cloud_logging",
19
+ "no_enable_cloud_monitoring",
20
+ "image_type",
21
+ "machine_type",
22
+ "max_nodes_per_pool",
23
+ "network",
24
+ "num_nodes",
25
+ "password",
26
+ "scopes",
27
+ "subnetwork",
28
+ "username",
29
+ "wait",
30
+ "zone",
31
+ ]
32
+ when :create_nodepool
33
+ # flip value due to differing key name..
34
+ if self.key?("no_enable_cloud_endpoints")
35
+ dup[:enable_cloud_endpoints] = self[:no_enable_cloud_endpoints] ? false : true
36
+ end
37
+
38
+ [
39
+ "disk_size",
40
+ "enable_cloud_endpoints",
41
+ "image_type",
42
+ "machine_type",
43
+ "num_nodes",
44
+ "scopes",
45
+ "zone",
46
+ "cluster",
47
+ ]
48
+ when :resize_cluster
49
+ dup["size"] = self["num_nodes"] if self.key?("num_nodes")
50
+
51
+ [
52
+ "async",
53
+ "size",
54
+ "wait",
55
+ "zone",
56
+ "node_pool",
57
+ ]
58
+ else
59
+ raise StandardError, "invalid context supplied when querying config object: #{context}"
60
+ end
61
+
62
+ dup.delete_if { |k, _| !permitted_keys.include?(k) }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "yaml"
4
+
5
+ module GClouder
6
+ module Config
7
+ module Defaults
8
+ include GClouder::Logging
9
+
10
+ def self.defaults
11
+ GClouder::ConfigLoader.load("../../assets/defaults")
12
+ end
13
+
14
+ def self.load
15
+ defaults
16
+ end
17
+
18
+ def defaults
19
+ Defaults.defaults
20
+ end
21
+
22
+ def self.included(klass)
23
+ klass.extend Defaults
24
+ end
25
+
26
+ def self.section(section)
27
+ GClouder::ConfigSection.find(section, defaults)
28
+ end
29
+
30
+ def self.section?(section)
31
+ GClouder::ConfigSection.find(section, defaults, silent: true)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouder
4
+ module Config
5
+ module Files
6
+ module Project
7
+ include GClouder::Config::CLIArgs
8
+ include GClouder::Helpers
9
+
10
+ def self.included(klass)
11
+ klass.extend project
12
+ end
13
+
14
+ def project
15
+ Project.project
16
+ end
17
+
18
+ def self.project
19
+ to_deep_merge_hash(YAML.load_file(cli_args[:config]))
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouder
4
+ module Config
5
+ module Project
6
+ include GClouder::Logging
7
+ include GClouder::Helpers
8
+
9
+ def self.project
10
+ @project
11
+ end
12
+
13
+ def project
14
+ Project.project
15
+ end
16
+
17
+ def self.load
18
+ @project = GClouder.resources.each_with_object(GClouder::Config::Files::Project.project) do |resource, config|
19
+ next unless module_exists? "#{resource[:module]}::Config"
20
+
21
+ config = resource[:module]::Config.merged(config)
22
+ end
23
+
24
+ fatal "no project_id found in config" unless project.key?("project_id")
25
+ end
26
+
27
+ private
28
+
29
+ def self.included(klass)
30
+ klass.extend Project
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "yaml"
4
+
5
+ module GClouder
6
+ module Config
7
+ module ResourceRepresentations
8
+ include GClouder::Logging
9
+
10
+ def self.properties
11
+ @properties ||= GClouder::ConfigLoader.load("../../assets/resource_representations")
12
+ end
13
+
14
+ def self.load
15
+ properties
16
+ end
17
+
18
+ def properties
19
+ ResourceRepresentations.properties
20
+ end
21
+
22
+ def self.included(klass)
23
+ klass.extend ResourceRepresentations
24
+ end
25
+
26
+ def self.section(section)
27
+ GClouder::ConfigSection.find(section, properties)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouder
4
+ module ConfigLoader
5
+ def self.load(path)
6
+ data = DeepMergeHash.new
7
+ file = File.join(File.dirname(__FILE__), path, "**/*.yml")
8
+
9
+ configs = Dir.glob(file)
10
+
11
+ configs.each do |config|
12
+ yaml = YAML.load_file config
13
+ section_path = to_path(config, path)
14
+ hash = section_path.reverse.inject(yaml) { |value, key| { key => value } }
15
+ data = data.deep_merge hash
16
+ end
17
+
18
+ data
19
+ end
20
+
21
+ def self.to_path(config, path)
22
+ config.gsub(/.*#{path}\//, "").gsub!(/\.yml$/, "").split("/")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouder
4
+ module ConfigSection
5
+ include GClouder::Logging
6
+
7
+ def self.included(klass)
8
+ klass.extend ConfigSection
9
+ end
10
+
11
+ def self.find(path, data, silent: false)
12
+ raise StandardError, "find: path argument must be an array: #{path.inspect}" unless path.is_a?(Array)
13
+ raise StandardError, "find: data argument must be an hash: #{path.inspect}" unless data.is_a?(Hash)
14
+
15
+ section = data.dig(*path)
16
+
17
+ if section
18
+ return silent ? true : section
19
+ end
20
+
21
+ return false if silent
22
+
23
+ fatal "can't find key in data: #{path}"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouder
4
+ module Dependencies
5
+ def self.check
6
+ %w(jq gcloud gsutil).each do |command|
7
+ (puts "missing dependency: #{command}"; exit 1) unless system("which #{command} > /dev/null 2>&1")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouder
4
+ module GCloud
5
+ include GClouder::Logging
6
+ include GClouder::Shell
7
+ include GClouder::Helpers
8
+ include GClouder::Config::Project
9
+ include GClouder::Config::CLIArgs
10
+
11
+ def self.included(klass)
12
+ klass.extend GCloud
13
+ end
14
+
15
+ def gcloud(command, force: false, failure: true, silent: false, project_id: nil)
16
+ project_id = verify(project_id)
17
+
18
+ GClouder::Project::ID.switch(project_id)
19
+
20
+ if cli_args[:dry_run] && !force
21
+ debug "# gcloud --quiet --format json --project=#{project_id} #{command}" if cli_args[:debug]
22
+ GClouder::Project::ID.reset
23
+ return
24
+ end
25
+
26
+ result = shell("gcloud --quiet --format json --project=#{project_id} #{command}", failure: failure, silent: silent)
27
+
28
+ GClouder::Project::ID.reset
29
+
30
+ valid_json?(result) ? JSON.parse(result.to_s) : result
31
+ end
32
+
33
+ def verify(project_id)
34
+ project_id ||= project["project_id"]
35
+ return project_id if project_id
36
+ raise StandardError, "project_id not detected"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouder
4
+ module GSUtil
5
+ include GClouder::Shell
6
+ include GClouder::Config::CLIArgs
7
+ include GClouder::Config::Project
8
+
9
+ def self.included(klass)
10
+ klass.extend GSUtil
11
+ end
12
+
13
+ def gsutil(command, args, force: false)
14
+ info "# gsutil #{command} -p #{project['project_id']} #{args}" if cli_args[:debug]
15
+
16
+ return if cli_args[:dry_run] && !force
17
+
18
+ gsutil_exec(command, args)
19
+ end
20
+
21
+ def gsutil_exec(command, args)
22
+ shell("gsutil #{command} -p #{project['project_id']} #{args}")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouder
4
+ module Header
5
+ def self.display
6
+ "\n\n #{'g'.blue}#{'o'.red}#{'o'.yellow}#{'g'.blue}#{'l'.green}#{'e'.red} cloud platform resource deployer"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouder
4
+ module Helpers
5
+ def self.included(klass)
6
+ klass.extend Helpers
7
+ end
8
+
9
+ def hash_to_args(hash)
10
+ raise StandardError, "hash_to_args: input not a hash: #{hash}" unless hash.is_a?(Hash)
11
+ hash.map { |param, value|
12
+ next if param == "name"
13
+ to_arg(param, value)
14
+ }.join(" ")
15
+ end
16
+
17
+ def to_arg(param, value)
18
+ param = param.tr("_", "-")
19
+
20
+ value = case value
21
+ when Boolean
22
+ return value ? "--#{param}" : "--no-#{param}"
23
+ when Array
24
+ value.join(",")
25
+ else
26
+ value
27
+ end
28
+
29
+ "--#{param}='#{value}'"
30
+ end
31
+
32
+ def valid_json?(object)
33
+ JSON.parse(object.to_s)
34
+ return true
35
+ rescue JSON::ParserError
36
+ return false
37
+ end
38
+
39
+ def to_deep_merge_hash(hash, hash_type = DeepMergeHash)
40
+ raise StandardError, "to_deep_merge_hash: argument must be a hash" unless hash.is_a?(Hash)
41
+
42
+ hash.each do |k,v|
43
+ case v
44
+ when Hash
45
+ hash_to_deep_merge_hash(hash, k, v, hash_type)
46
+ when Array
47
+ array_to_deep_merge_hash(v, hash_type)
48
+ end
49
+ end
50
+
51
+ hash_type.new(hash)
52
+ end
53
+
54
+ def module_exists?(name, base = self.class)
55
+ raise StandardError, "module name must be a string" unless name.is_a?(String)
56
+ base.const_defined?(name) && base.const_get(name).instance_of?(::Module)
57
+ end
58
+
59
+ private
60
+
61
+ def hash_to_deep_merge_hash(obj, index, hash, hash_type)
62
+ obj[index] = hash_type.new(hash)
63
+ to_deep_merge_hash(obj[index], hash_type)
64
+ end
65
+
66
+ def array_to_deep_merge_hash(array, hash_type)
67
+ array.each_with_index do |e,i|
68
+ case e
69
+ when Hash
70
+ hash_to_deep_merge_hash(array, i, e, hash_type)
71
+ when Array
72
+ array_to_deep_merge_hash(array, hash_type)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end