class MU::Cloud::Google::Folder
Creates a Google
folder as configured in {MU::Config::BasketofKittens::folders}
Public Class Methods
Retrieve the IAM bindings for this folder (associates between IAM roles and groups/users) @param folder [String]: @param credentials [String]:
# File modules/mu/providers/google/folder.rb, line 83 def self.bindings(folder, credentials: nil) MU::Cloud::Google.folder(credentials: credentials).get_folder_iam_policy(folder).bindings end
Remove all Google
projects associated with the currently loaded deployment. Try to, anyway. @param noop [Boolean]: If true, will only print what would be done @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server @return [void]
# File modules/mu/providers/google/folder.rb, line 165 def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, credentials: nil, flags: {}) filter = %Q{(labels.mu-id = "#{MU.deploy_id.downcase}")} if !ignoremaster and MU.mu_public_ip filter += %Q{ AND (labels.mu-master-ip = "#{MU.mu_public_ip.gsub(/\./, "_")}")} end MU.log "Placeholder: Google Folder artifacts do not support labels, so ignoremaster cleanup flag has no effect", MU::DEBUG, details: filter # We can't label GCP folders, and their names are too short to encode # Mu deploy IDs, so all we can do is rely on flags['known'] passed in # from cleanup, which relies on our metadata to know what's ours. #noop = true if flags and flags['known'] threads = [] flags['known'].each { |cloud_id| threads << Thread.new { found = self.find(cloud_id: cloud_id, credentials: credentials) if found.size > 0 and found.values.first.lifecycle_state == "ACTIVE" MU.log "Deleting folder #{found.values.first.display_name} (#{found.keys.first})" if !noop max_retries = 10 retries = 0 success = false begin MU::Cloud::Google.folder(credentials: credentials).delete_folder( "folders/"+found.keys.first ) found = self.find(cloud_id: cloud_id, credentials: credentials) if found and found.size > 0 and found.values.first.lifecycle_state != "DELETE_REQUESTED" if retries < max_retries sleep 30 retries += 1 puts retries else MU.log "Folder #{cloud_id} still exists after #{max_retries.to_s} attempts to delete", MU::ERR break end else success = true end rescue ::Google::Apis::ClientError => e # XXX maybe see if the folder has disappeared already? # XXX look for child folders that haven't been deleted, that's what this tends # to mean if e.message.match(/failedPrecondition/) and retries < max_retries sleep 30 retries += 1 retry else MU.log "Got 'failedPrecondition' a bunch while trying to delete #{found.values.first.display_name} (#{found.keys.first})", MU::ERR break end end while !success end end } } threads.each { |t| t.join } end end
Locate and return cloud provider descriptors of this resource type which match the provided parameters, or all visible resources if no filters are specified. At minimum, implementations of find
must honor credentials
and cloud_id
arguments. We may optionally support other search methods, such as tag_key
and tag_value
, or cloud-specific arguments like project
. See also {MU::MommaCat.findStray}. @param args [Hash]: Hash
of named arguments passed via Ruby's double-splat @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching resources
# File modules/mu/providers/google/folder.rb, line 237 def self.find(**args) found = {} # Recursively search a GCP folder hierarchy for a folder matching our # supplied name or identifier. def self.find_matching_folder(parent, name: nil, id: nil, credentials: nil) resp = MU::Cloud::Google.folder(credentials: credentials).list_folders(parent: parent) if resp and resp.folders resp.folders.each { |f| if name and name.downcase == f.display_name.downcase return f elsif id and "folders/"+id== f.name return f else found = self.find_matching_folder(f.name, name: name, id: id, credentials: credentials) return found if found end } end nil end parent = if args[:flags] and args[:flags]['parent_id'] args[:flags]['parent_id'] else my_org = MU::Cloud::Google.getOrg(args[:credentials]) my_org.name end if args[:cloud_id] raw_id = args[:cloud_id].sub(/^folders\//, "") begin resp = MU::Cloud::Google.folder(credentials: args[:credentials]).get_folder("folders/"+raw_id) found[resp.name] = resp if resp rescue ::Google::Apis::ClientError => e raise e if e.message !~ /forbidden: / end elsif args[:flags] and args[:flags]['display_name'] if parent resp = self.find_matching_folder(parent, name: args[:flags]['display_name'], credentials: args[:credentials]) if resp found[resp.name] = resp end end else resp = MU::Cloud::Google.folder(credentials: args[:credentials]).list_folders(parent: parent) if resp and resp.folders resp.folders.each { |folder| next if folder.lifecycle_state == "DELETE_REQUESTED" found[folder.name] = folder # recurse so that we'll pick up child folders children = self.find( credentials: args[:credentials], flags: { 'parent_id' => folder.name } ) if !children.nil? and !children.empty? found.merge!(children) end } end end found end
Recursively search a GCP folder hierarchy for a folder matching our supplied name or identifier.
# File modules/mu/providers/google/folder.rb, line 241 def self.find_matching_folder(parent, name: nil, id: nil, credentials: nil) resp = MU::Cloud::Google.folder(credentials: credentials).list_folders(parent: parent) if resp and resp.folders resp.folders.each { |f| if name and name.downcase == f.display_name.downcase return f elsif id and "folders/"+id== f.name return f else found = self.find_matching_folder(f.name, name: name, id: id, credentials: credentials) return found if found end } end nil end
Does this resource type exist as a global (cloud-wide) artifact, or is it localized to a region/zone? @return [Boolean]
# File modules/mu/providers/google/folder.rb, line 151 def self.isGlobal? true end
Initialize this cloud resource object. Calling super
will invoke the initializer defined under {MU::Cloud}, which should set the attribtues listed in {MU::Cloud::PUBLIC_ATTRS} as well as applicable dependency shortcuts, like @vpc
, for us. @param args [Hash]: Hash
of named arguments passed via Ruby's double-splat
# File modules/mu/providers/google/folder.rb, line 23 def initialize(**args) super cloud_desc if @cloud_id # XXX this maybe isn't my job @mu_name ||= @deploy.getResourceName(@config['name']) end
Denote whether this resource implementation is experiment, ready for testing, or ready for production use.
# File modules/mu/providers/google/folder.rb, line 157 def self.quality MU::Cloud::RELEASE end
Given a {MU::Config::Folder.reference} configuration block, resolve to a GCP resource id and type suitable for use in API calls to manage projects and folders. @param parentblock [Hash] @return [String]
# File modules/mu/providers/google/folder.rb, line 92 def self.resolveParent(parentblock, credentials: nil) my_org = MU::Cloud::Google.getOrg(credentials) if my_org and (!parentblock or parentblock['id'] == my_org.name or parentblock['name'] == my_org.display_name or (parentblock['id'] and "organizations/"+parentblock['id'] == my_org.name)) return my_org.name end if parentblock['name'] sib_folder = MU::MommaCat.findStray( "Google", "folders", deploy_id: parentblock['deploy_id'], credentials: credentials, name: parentblock['name'] ).first if sib_folder return sib_folder.cloud_desc.name end end begin found = MU::Cloud::Google::Folder.find(cloud_id: parentblock['id'], credentials: credentials, flags: { 'display_name' => parentblock['name'] }) rescue ::Google::Apis::ClientError => e if !e.message.match(/Invalid request status_code: 404/) raise e end end if found and found.size > 0 return found.values.first.name end nil end
Cloud-specific configuration properties. @param _config [MU::Config]: The calling MU::Config
object @return [Array<Array,Hash>]: List of required fields, and json-schema Hash
of cloud-specific configuration parameters for this resource
# File modules/mu/providers/google/folder.rb, line 338 def self.schema(_config) toplevel_required = [] schema = { "display_name" => { "type" => "string", "description" => "The +display_name+ field of this folder, specified only if we want it to be something other than the automatically-generated string derived from the +name+ field.", } } [toplevel_required, schema] end
Cloud-specific pre-processing of {MU::Config::BasketofKittens::folders}, bare and unvalidated. @param folder [Hash]: The resource to process and validate @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member @return [Boolean]: True if validation succeeded, False otherwise
# File modules/mu/providers/google/folder.rb, line 353 def self.validateConfig(folder, configurator) ok = true if !MU::Cloud::Google.getOrg(folder['credentials']) MU.log "Cannot manage Google Cloud folders in environments without an organization", MU::ERR ok = false end if folder['parent'] and folder['parent']['name'] and !folder['parent']['deploy_id'] and configurator.haveLitterMate?(folder['parent']['name'], "folders") MU::Config.addDependency(folder, folder['parent']['name'], "folder") end ok end
Public Instance Methods
Retrieve the IAM bindings for this folder (associates between IAM roles and groups/users)
# File modules/mu/providers/google/folder.rb, line 76 def bindings MU::Cloud::Google::Folder.bindings(@cloud_id, credentials: @config['credentials']) end
Return the cloud descriptor for the Folder
@return [Google::Apis::Core::Hashable]
# File modules/mu/providers/google/folder.rb, line 131 def cloud_desc(use_cache: true) return @cached_cloud_desc if @cached_cloud_desc and use_cache @cached_cloud_desc = MU::Cloud::Google::Folder.find(cloud_id: @cloud_id, credentials: @config['credentials']).values.first @habitat_id ||= @cached_cloud_desc.parent.sub(/^(folders|organizations)\//, "") @cached_cloud_desc end
Called automatically by {MU::Deploy#createResources}
# File modules/mu/providers/google/folder.rb, line 31 def create name_string = if @config['scrub_mu_isms'] @config["name"] else @deploy.getResourceName(@config["name"], max_length: 30).downcase end params = { display_name: name_string } if @config['parent']['name'] and !@config['parent']['id'] @config['parent']['deploy_id'] = @deploy.deploy_id end parent = MU::Cloud::Google::Folder.resolveParent(@config['parent'], credentials: @config['credentials']) folder_obj = MU::Cloud::Google.folder(:Folder).new(params) MU.log "Creating folder #{name_string} under #{parent}", details: folder_obj MU::Cloud::Google.folder(credentials: @config['credentials']).create_folder(folder_obj, parent: parent) # Wait for list_folders output to be consistent (for the folder we # just created to show up) retries = 0 begin found = MU::Cloud::Google::Folder.find(credentials: credentials, flags: { 'display_name' => name_string, 'parent_id' => parent }) if found.size > 0 @cloud_id = found.keys.first @parent = found.values.first.parent MU.log "Folder #{name_string} has identifier #{@cloud_id}" else if retries > 0 and (retries % 3) == 0 MU.log "Waiting for Google Cloud folder #{name_string} to appear in list_folder results...", MU::NOTICE end retries += 1 sleep 15 end end while found.size == 0 @habitat = parent end
Return the metadata for this folders's configuration @return [Hash]
# File modules/mu/providers/google/folder.rb, line 140 def notify desc = MU.structToHash(cloud_desc) desc["mu_name"] = @mu_name desc["parent"] = @parent desc["cloud_id"] = @cloud_id desc end
Reverse-map our cloud description into a runnable config hash. We assume that any values we have in +@config+ are placeholders, and calculate our own accordingly based on what's live in the cloud.
# File modules/mu/providers/google/folder.rb, line 308 def toKitten(**args) bok = { "cloud" => "Google", "credentials" => @config['credentials'] } bok['display_name'] = cloud_desc.display_name bok['cloud_id'] = cloud_desc.name bok['name'] = cloud_desc.display_name#+bok['cloud_id'] # only way to guarantee uniqueness if cloud_desc.parent.match(/^folders\/(.*)/) bok['parent'] = MU::Config::Ref.get( id: cloud_desc.parent, cloud: "Google", credentials: @config['credentials'], type: "folders" ) elsif args[:rootparent] bok['parent'] = { 'id' => args[:rootparent].is_a?(String) ? args[:rootparent] : args[:rootparent].cloud_desc.name } else bok['parent'] = { 'id' => cloud_desc.parent } end bok end