gclouder_undefined_resources 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []