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