class MU::Cloud::Google::Folder

Creates a Google folder as configured in {MU::Config::BasketofKittens::folders}

Public Class Methods

bindings(folder, credentials: nil) click to toggle source

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
cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, credentials: nil, flags: {}) click to toggle source

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
find(**args) click to toggle source

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
find_matching_folder(parent, name: nil, id: nil, credentials: nil) click to toggle source

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
isGlobal?() click to toggle source

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
new(**args) click to toggle source

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

Calls superclass method
# 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
quality() click to toggle source

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
resolveParent(parentblock, credentials: nil) click to toggle source

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
schema(_config) click to toggle source

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
validateConfig(folder, configurator) click to toggle source

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

bindings() click to toggle source

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
cloud_desc(use_cache: true) click to toggle source

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
create() click to toggle source

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
notify() click to toggle source

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
toKitten(**args) click to toggle source

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