gclouder_undefined_resources 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 53f02cbd3cea848631504944efa920e5ced3574e
4
+ data.tar.gz: 98d661239064f19b793a6f5992cec119e37c1a7a
5
+ SHA512:
6
+ metadata.gz: e4719ab0e87f18ed531b3bcdc18b7948ae0d3e1a2e1edd37dfa7d1cd756056d42ae33eeefd8de4a6ef407bf98f41c2ef55d4431062467f0a0cd5c800142fa23b
7
+ data.tar.gz: d29e31aef7a439ec592be10c0691942ff8e71a55d6f8a03096480967dd2eca81948006671cb43cd76257d09de7683654c0f70f48952d43df280b3558b36e429c
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "../lib"))
4
+
5
+ require "gclouder_undefined_resources"
6
+
7
+ GClouderUndefinedResources.run
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouderUndefinedResources
4
+ module CLIArgs
5
+ def self.cli_args
6
+ @cli_args ||= { debug: false }
7
+ end
8
+
9
+ def cli_args
10
+ CLIArgs.cli_args
11
+ end
12
+
13
+ def self.included(klass)
14
+ klass.extend CLIArgs
15
+ end
16
+
17
+ def self.load
18
+ option_parser = Trollop::Parser.new do
19
+ banner "\n undefined gcp resources\n "
20
+
21
+ opt :local_resources, "file containing local resource definitions", type: :string, required: true
22
+ #opt :remote_resources, "file containing remote resource definitions\n ", type: :string, required: true
23
+ opt :group_by, "how to format output. valid values include: location, type", type: :string, default: "location"
24
+ opt :filter_config, "file containing filter config", type: :string
25
+ opt :show_filtered, "show filter matching output\n ", type: :bool
26
+ opt :verbose, "verbose\n ", type: :bool
27
+ end
28
+
29
+ @cli_args = Trollop.with_standard_exception_handling(option_parser) do
30
+ raise Trollop::HelpNeeded if ARGV.empty?
31
+ option_parser.parse ARGV
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouderUndefinedResources
4
+ class LocalResources
5
+ extend Forwardable
6
+
7
+ def initialize(file)
8
+ @resources = yaml_load(file)
9
+ end
10
+
11
+ def_delegator :@resources, :[]
12
+
13
+ def yaml_load(file)
14
+ @resources = YAML.load_file(file)
15
+ rescue => e
16
+ raise StandardError, "failed to load local resource file: #{e}"
17
+ end
18
+
19
+ def global?(key, name)
20
+ #ap @resources
21
+ #puts "looking up: #{key}, name: #{name}"
22
+ match = @resources.dig(*key)
23
+ return false if match.nil?
24
+ found_resources = match.select { |hash| hash["name"] == name }
25
+ !found_resources.empty?
26
+ rescue TypeError, "type table lookup failure"
27
+ end
28
+
29
+ def regional?(region, key, name)
30
+ #ap @resources
31
+ #puts "looking up: #{key}, name: #{name}"
32
+ match = @resources.dig("regions", region, *key)
33
+ return false if match.nil?
34
+ found_resources = match.select { |hash| hash["name"] == name }
35
+ !found_resources.empty?
36
+ rescue TypeError, "type table lookup failure"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouderUndefinedResources
4
+ class RemoteResources
5
+ module Remote
6
+ module GCloud
7
+ def self.fetch(project_id)
8
+ modules.map { |m| m.send(:fetch, project_id) }.flatten
9
+ end
10
+
11
+ def self.modules
12
+ [
13
+ ServiceAccounts,
14
+ ResourcesList
15
+ ]
16
+ end
17
+
18
+ module ServiceAccounts
19
+ def self.fetch(project_id)
20
+ json = JSON.parse(Shell.run("gcloud iam service-accounts list --format json"))
21
+
22
+ json.map do |resource|
23
+ resource["@type"] = "serviceAccount"
24
+ Resource.new(resource["email"], resource)
25
+ end
26
+ end
27
+ end
28
+
29
+ module ResourcesList
30
+ def self.fetch(project_id)
31
+ json = JSON.parse(Shell.run("gcloud alpha resources list --format json"))
32
+
33
+ json.select do |resource|
34
+ result = true
35
+
36
+ result = nil unless resource["selfLink"] =~ /projects\/#{project_id}\//
37
+
38
+ if resource["@type"] == "type.googleapis.com/google.cloud.dataproc.v1.Job"
39
+ puts "skipping data proc resource: #{resource['reference']}, #{resource['placement']}"
40
+ result = nil
41
+ end
42
+
43
+ result
44
+ end.map do |resource|
45
+ Resource.new(resource["name"], resource)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouderUndefinedResources
4
+ class RemoteResources
5
+ class Resource
6
+ extend Forwardable
7
+
8
+ attr_accessor :name, :data
9
+
10
+ def initialize(name, data)
11
+ if name.nil?
12
+ ap data
13
+ raise StandardError, "can't find name for resource"
14
+ end
15
+
16
+ @name = name
17
+ @data = data
18
+ end
19
+
20
+ def_delegator :@data, :[]
21
+
22
+ def region
23
+ return unless @data.key?("selfLink")
24
+ match = @data["selfLink"].match(/.*\/regions\/([^\/]+)/)
25
+ return match[1] if match
26
+ zone[0..-3] if zone
27
+ end
28
+
29
+ def zone
30
+ return unless @data.key?("selfLink")
31
+ match = @data["selfLink"].match(/.*\/zones\/([^\/]+)/)
32
+ match[1] if match
33
+ end
34
+
35
+ def global?
36
+ return true if zone.nil? && region.nil?
37
+ return unless @data.key?("selfLink")
38
+ !@data["selfLink"].match(/.*\/projects\/[^\/]+\/global/).nil?
39
+ end
40
+
41
+ # type is the Remote version of Local path.., use this to check against local data structure
42
+ def type
43
+ @type ||= get_type
44
+ end
45
+
46
+ # check for local regional and local global definitions
47
+ def defined?
48
+ local_resources = GClouderUndefinedResources.local_resources
49
+
50
+ if global?
51
+ local_resources.global?(type, name)
52
+ else
53
+ local_resources.regional?(region, type, name)
54
+ end
55
+ end
56
+
57
+ # check to see if this resource has been filtered
58
+ def filtered?
59
+ unless CLIArgs.cli_args[:filter_config_given]
60
+ puts "no filter config given" if CLIArgs.cli_args["verbose"]
61
+ return false
62
+ end
63
+
64
+ unless filters.key?("filters")
65
+ raise StandardError, "no filters key found in filter config"
66
+ end
67
+
68
+ original_type = @data["@type"]
69
+
70
+ filters["filters"].each do |filter|
71
+ if filter["region"] != region && filter["region"] != "*"
72
+ next
73
+ end
74
+
75
+ if filter["zone"] != zone && filter["zone"] != "*"
76
+ next
77
+ end
78
+
79
+ if filter["type"] != original_type && filter["type"] != "*"
80
+ next
81
+ end
82
+
83
+ if (name !~ Regexp.new(filter["name"]) ? true : false) && filter["name"] != "*"
84
+ next
85
+ end
86
+
87
+ if CLIArgs.cli_args[:show_filtered]
88
+ puts "filter: "
89
+ puts " description: #{filter['description']}"
90
+ puts " region: #{filter['region']}"
91
+ puts " zone: #{filter['zone']}"
92
+ puts " type: #{filter['type']}"
93
+ puts " name: #{filter['name']}"
94
+ puts "match: "
95
+ puts " region: #{region}"
96
+ puts " zone: #{zone}"
97
+ puts " type: #{original_type}"
98
+ puts " name: #{name}"
99
+ puts
100
+ end
101
+
102
+ return true
103
+ end
104
+
105
+ false
106
+ end
107
+
108
+ private
109
+
110
+ def filters
111
+ @filters ||= YAML.load_file(CLIArgs.cli_args[:filter_config])
112
+ rescue => error
113
+ puts "failed to load filter config: #{error}"
114
+ exit 1
115
+ end
116
+
117
+ # FIXME - add more types
118
+ def get_type
119
+ case @data["@type"]
120
+ when "serviceAccount"
121
+ [ "service_accounts" ]
122
+ #when "type.googleapis.com/cloud.bigstore.api.Bucket"
123
+
124
+ when "type.googleapis.com/cloud.dns.api.ManagedZone"
125
+ [ "dns", "zones" ]
126
+
127
+ #when "type.googleapis.com/cloud.resourcemanager.Organization"
128
+
129
+ when "type.googleapis.com/compute.BackendBucket"
130
+ [ "compute", "backend-buckets" ]
131
+
132
+ when "type.googleapis.com/compute.Disk"
133
+ [ "compute", "disks" ]
134
+
135
+ when "type.googleapis.com/compute.Firewall"
136
+ [ "firewall", "rules" ]
137
+
138
+ when "type.googleapis.com/compute.HealthCheck"
139
+ [ "compute", "health_check" ]
140
+
141
+ when "type.googleapis.com/compute.HttpHealthCheck"
142
+ [ "compute", "http_health_check" ]
143
+
144
+ when "type.googleapis.com/compute.HttpsHealthCheck"
145
+ [ "compute", "https_health_check" ]
146
+
147
+ when "type.googleapis.com/compute.Image"
148
+ ["compute", "images"]
149
+
150
+ when "type.googleapis.com/compute.Instance"
151
+ ["compute", "instances"]
152
+
153
+ when "type.googleapis.com/compute.InstanceTemplate"
154
+ ["compute", "instance_templates"]
155
+
156
+ #when "type.googleapis.com/compute.InstanceTemplate"
157
+
158
+ when "type.googleapis.com/compute.Network"
159
+ [ "networks" ]
160
+
161
+ when "type.googleapis.com/compute.Router"
162
+ [ "compute", "router" ]
163
+
164
+ #when "type.googleapis.com/compute.SslCertificate"
165
+
166
+ when "type.googleapis.com/compute.Subnetwork"
167
+ [ "subnets" ]
168
+
169
+ #when "type.googleapis.com/compute.TargetHttpProxy"
170
+ #when "type.googleapis.com/compute.TargetHttpsProxy"
171
+
172
+ when "type.googleapis.com/compute.UrlMap"
173
+ [ "compute", "url-maps" ]
174
+
175
+ #when "type.googleapis.com/google.api.servicemanagement.v1.ManagedService"
176
+ #when "type.googleapis.com/google.appengine.v1.Application"
177
+ #when "type.googleapis.com/google.appengine.v1.Service"
178
+ #when "type.googleapis.com/google.appengine.v1.Version"
179
+ #when "type.googleapis.com/google.cloud.billing.v1.BillingAccount"
180
+ #when "type.googleapis.com/google.cloud.dataproc.v1.Cluster"
181
+ #when "type.googleapis.com/google.cloudresourcemanager.projects.v1beta1.Project"
182
+ else
183
+ puts "error: unknown or no '@type' property found for data:"
184
+ ap @data
185
+ exit 1
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouderUndefinedResources
4
+ class RemoteResources
5
+ module Symbols
6
+ def self.tick
7
+ "✓".green
8
+ end
9
+
10
+ def self.x
11
+ "✗".red
12
+ end
13
+
14
+ def self.tick_filtered
15
+ "✓".yellow
16
+ end
17
+
18
+ def self.x_filtered
19
+ "✗".yellow
20
+ end
21
+ end
22
+
23
+ def initialize(project_id)
24
+ @project_id = project_id
25
+ @resources = []
26
+ end
27
+
28
+ def collect
29
+ add Remote::GCloud.fetch(@project_id)
30
+ end
31
+
32
+ def add(resources)
33
+ @resources += resources
34
+ end
35
+
36
+ # display: type, region, zone, name
37
+ def display_by_type
38
+ types = @resources.group_by { |resource| resource.type }
39
+
40
+ types.each do |type, resources_by_type|
41
+ regions = resources_by_type.group_by { |resource| resource.region }
42
+
43
+ next if regions.empty?
44
+
45
+ puts
46
+ puts
47
+ puts " #{type.join(' / ')}"
48
+
49
+ regions.each do |region, resources_by_region|
50
+ zones = resources_by_region.group_by { |resource| resource.zone }
51
+
52
+ next if zones.empty?
53
+
54
+ region ||= "global"
55
+
56
+ puts
57
+ puts " #{region}"
58
+
59
+ zones.each do |zone, resources_by_zone|
60
+ next if resources_by_zone.empty?
61
+
62
+ zone ||= "none"
63
+
64
+ puts
65
+ puts " #{zone}"
66
+ puts
67
+
68
+ resources_by_region.each do |resource|
69
+ if resource.filtered?
70
+ status = resource.defined? ? Symbols.tick_filtered : Symbols.x_filtered
71
+ else
72
+ status = resource.defined? ? Symbols.tick : Symbols.x
73
+ end
74
+
75
+ # FIXME: do the skipping stuff here, or display status of the resource name..
76
+ # FIXME: to_s in resource could include x or tick or whatever
77
+ puts " #{status} #{resource.name}"
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ def display_by_location
85
+ # display: region, zone, type, name
86
+ regions = @resources.group_by { |resource| resource.region }
87
+
88
+ regions.each do |region, resources_by_region|
89
+ region ||= "global"
90
+
91
+ puts
92
+ puts
93
+ puts " #{region}"
94
+
95
+ zones = resources_by_region.group_by { |resource| resource.zone }
96
+
97
+ zones.each do |zone, resources_by_zone|
98
+ zone ||= "none"
99
+
100
+ puts
101
+ puts " #{zone}"
102
+
103
+ types = resources_by_zone.group_by { |resource| resource.type }
104
+
105
+ types.each do |type, resources_by_type|
106
+ puts
107
+ puts " #{type.join(' / ')}"
108
+ puts
109
+
110
+ resources_by_type.each do |resource|
111
+ if resource.filtered?
112
+ status = resource.defined? ? Symbols.tick_filtered : Symbols.x_filtered
113
+ else
114
+ status = resource.defined? ? Symbols.tick : Symbols.x
115
+ end
116
+
117
+ # FIXME: do the skipping stuff here, or display status of the resource name..
118
+ # FIXME: to_s in resource could include x or tick or whatever
119
+ puts " #{status} #{resource.name}"
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouderUndefinedResources
4
+ module Resource
5
+ def self.local=(file)
6
+ @local_resources = YAML.load_file(file)
7
+ rescue => e
8
+ raise StandardError, "failed to load local resource file: #{e}"
9
+ end
10
+
11
+ def self.local
12
+ @local_resources
13
+ end
14
+
15
+ def self.global?(key, name)
16
+ resources = local.dig(*key)
17
+ return false if resources.nil?
18
+ found_resources = resources.select { |hash| hash["name"] == name }
19
+ !found_resources.empty?
20
+ end
21
+
22
+ def self.regional?(region, key, name)
23
+ resources = local.dig("regions", region, *key)
24
+ return false if resources.nil?
25
+ found_resources = resources.select { |hash| hash["name"] == name }
26
+ !found_resources.empty?
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouderUndefinedResources
4
+ module SelfLink
5
+ def self.region(resource)
6
+ r = resource["selfLink"].match(/.*\/regions\/([^\/]+)/)
7
+ r.nil? ? nil : r[1]
8
+ end
9
+
10
+ def self.zone(resource)
11
+ r = resource["selfLink"].match(/.*\/zones\/([^\/]+)/)
12
+ r.nil? ? nil : r[1]
13
+ end
14
+
15
+ def self.global(resource)
16
+ !resource["selfLink"].match(/.*\/projects\/[^\/]+\/global/).nil?
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouderUndefinedResources
4
+ module Shell
5
+ def self.run(command)
6
+ puts "running command: #{command}" if CLIArgs.cli_args[:verbose]
7
+ stdout, stderr, status = Open3.capture3(command)
8
+
9
+ if status.to_i.nonzero?
10
+ raise StandardError, "command failed: #{command}\n#{stderr}\n#{stdout}"
11
+ end
12
+
13
+ stdout
14
+ rescue => error
15
+ puts error
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module GClouderUndefinedResources
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "yaml"
4
+ require "json"
5
+ require "trollop"
6
+ require "open3"
7
+ require "awesome_print"
8
+ require "colorize"
9
+ require "forwardable"
10
+ require "gclouder_undefined_resources/cli_args"
11
+ require "gclouder_undefined_resources/shell"
12
+ require "gclouder_undefined_resources/local_resources"
13
+ require "gclouder_undefined_resources/remote_resources"
14
+ require "gclouder_undefined_resources/remote_resources/remote"
15
+ require "gclouder_undefined_resources/remote_resources/resource"
16
+
17
+ module GClouderUndefinedResources
18
+ def self.run
19
+ undefined
20
+ end
21
+
22
+ def self.local_resources
23
+ @local_resources ||= LocalResources.new(CLIArgs.cli_args[:local_resources])
24
+ end
25
+
26
+ def self.undefined
27
+ CLIArgs.load
28
+
29
+ remote_resources = RemoteResources.new(local_resources["project_id"])
30
+ remote_resources.collect
31
+
32
+ case CLIArgs.cli_args[:group_by].to_sym
33
+ when :location
34
+ remote_resources.display_by_location
35
+ when :type
36
+ remote_resources.display_by_type
37
+ else
38
+ puts "unknown group-by type"
39
+ end
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gclouder_undefined_resources
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rob Wilson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-01-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: trollop
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: awesome_print
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: colorize
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
+ description: A tool to compare deployed resources to resources defined in gclouder(1)
56
+ manifests
57
+ email: roobert@gmail.com
58
+ executables:
59
+ - gclouder_undefined_resources
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - bin/gclouder_undefined_resources
64
+ - lib/gclouder_undefined_resources.rb
65
+ - lib/gclouder_undefined_resources/cli_args.rb
66
+ - lib/gclouder_undefined_resources/local_resources.rb
67
+ - lib/gclouder_undefined_resources/remote_resources.rb
68
+ - lib/gclouder_undefined_resources/remote_resources/remote.rb
69
+ - lib/gclouder_undefined_resources/remote_resources/resource.rb
70
+ - lib/gclouder_undefined_resources/resource.rb
71
+ - lib/gclouder_undefined_resources/self_link.rb
72
+ - lib/gclouder_undefined_resources/shell.rb
73
+ - lib/gclouder_undefined_resources/version.rb
74
+ homepage: https://github.com/roobert/gclouder_undefined_resources
75
+ licenses:
76
+ - MIT
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 2.6.8
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: GClouder undefined resource detection
98
+ test_files: []