class MU::Cloud::Google::Group

A group as configured in {MU::Config::BasketofKittens::groups}

Public Class Methods

canLiveIn() click to toggle source

Return the list of “container” resource types in which this resource can reside. The list will include an explicit nil if this resource can exist outside of any container. @return [Array<Symbol,nil>]

# File modules/mu/providers/google/group.rb, line 129
def self.canLiveIn
  [nil]
end
cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, credentials: nil, flags: {}) click to toggle source

Remove all groups associated with the currently loaded deployment. @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/group.rb, line 143
def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, credentials: nil, flags: {})
  MU::Cloud::Google.getDomains(credentials)
  my_org = MU::Cloud::Google.getOrg(credentials)

  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 Group artifacts do not support labels, so ignoremaster cleanup flag has no effect", MU::DEBUG, details: filter

  if my_org
    groups = MU::Cloud::Google.admin_directory(credentials: credentials).list_groups(customer: MU::Cloud::Google.customerID(credentials)).groups
    if groups
      groups.each { |group|
        if group.description == MU.deploy_id
          MU.log "Deleting group #{group.name} from #{my_org.display_name}", details: group
          if !noop
            MU::Cloud::Google.admin_directory(credentials: credentials).delete_group(group.id)
          end
        end
      }
    end
  end

  if flags['known']
    flags['known'].each { |group|
      MU::Cloud.resourceClass("Google", "Role").removeBindings("group", group, credentials: credentials, noop: noop)
    }
  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/group.rb, line 182
        def self.find(**args)
          found = {}

          # The API treats the email address field as its main identifier, so
          # we'll go ahead and respect that.
          if args[:cloud_id]
            begin
              resp = MU::Cloud::Google.admin_directory(credentials: args[:credentials]).get_group(args[:cloud_id])
              found[resp.email] = resp if resp
            rescue ::Google::Apis::ClientError => e
              raise e if !e.message.match(/forbidden: /)
            end
          else
            resp = MU::Cloud::Google.admin_directory(credentials: args[:credentials]).list_groups(customer: MU::Cloud::Google.customerID(args[:credentials]))
            if resp and resp.groups
              found = Hash[resp.groups.map { |g| [g.email, g] }]
            end
          end
# XXX what about Google Groups groups and other external groups? Where do we fish for those? Do we even need to?
          found
        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/group.rb, line 121
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/group.rb, line 23
def initialize(**args)
  super

  @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/group.rb, line 135
def self.quality
  MU::Cloud::BETA
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/group.rb, line 237
        def self.schema(_config)
          toplevel_required = []
          schema = {
            "name" => {
              "type" => "string",
              "description" => "This can include an optional @domain component (<tt>foo@example.com</tt>).

If the domain portion is not specified, and we manage exactly one GSuite or Cloud Identity domain, we will attempt to create the group in that domain.

If we do not manage any domains, and none are specified, we will assume <tt>@googlegroups.com</tt> for the domain and attempt to bind an existing external Google Group to roles under our jurisdiction.

If the domain portion is specified, and our credentials can manage that domain via GSuite or Cloud Identity, we will attempt to create the group in that domain.

If it is a domain we do not manage, we will attempt to bind an existing external group from that domain to roles under our jurisdiction.

If we are binding (rather than creating) a group and no roles are specified, we will default to +roles/viewer+ at the organization scope. If our credentials do not manage an organization, we will grant this role in our default project.

"
            },
            "domain" => {
              "type" => "string",
              "description" => "The domain from which the group originates or in which it should be created. This can instead be embedded in the {name} field: +foo@example.com+."
            },
            "external" => {
              "type" => "boolean",
              "description" => "Explicitly flag this group as originating from an external domain. This should always autodetect correctly."
            },

            "roles" => {
              "type" => "array",
              "items" => MU::Cloud.resourceClass("Google", "Role").ref_schema
            }
          }
          [toplevel_required, schema]
        end
validateConfig(group, configurator) click to toggle source

Cloud-specific pre-processing of {MU::Config::BasketofKittens::groups}, bare and unvalidated. @param group [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/group.rb, line 277
def self.validateConfig(group, configurator)
  ok = true

  my_domains = MU::Cloud::Google.getDomains(group['credentials'])
  my_org = MU::Cloud::Google.getOrg(group['credentials'])

  if group['name'].match(/@(.*+)$/)
    domain = Regexp.last_match[1].downcase
    if domain and group['domain'] and domain != group['domain'].downcase
      MU.log "Group #{group['name']} had a domain component, but the domain field was also specified (#{group['domain']}) and they don't match."
      ok = false
    end
    group['domain'] = domain

    if !my_domains or !my_domains.include?(domain)
      group['external'] = true

      if !["googlegroups.com", "google.com"].include?(domain)
        MU.log "#{group['name']} appears to be a member of a domain that our credentials (#{group['credentials']}) do not manage; attempts to grant access for this group may fail!", MU::WARN
      end

      if !group['roles'] or group['roles'].empty?
        group['roles'] = [
          {
            "role" => {
              "id" => "roles/viewer"
            }
          }
        ]
        if my_org
          group['roles'][0]["organizations"] = [my_org.name]
        else
          group['roles'][0]["projects"] = {
            "id" => group["project"]
          }
        end
        MU.log "External Google group specified with no role binding, will grant 'viewer' in #{my_org ? "organization #{my_org.display_name}" : "project #{group['project']}"}", MU::WARN
      end
    end
  else
    if !group['domain']
      if my_domains.size == 1
        group['domain'] = my_domains.first
      elsif my_domains.size > 1
        MU.log "Google interactive User #{group['name']} did not specify a domain, and we have multiple defaults available. Must specify exactly one.", MU::ERR, details: my_domains
        ok = false
      else
        group['domain'] = "googlegroups.com"
      end
    end
  end


  MU::Cloud::Google.credConfig(group['credentials'])

  if group['external'] and group['members']
    MU.log "Cannot manage memberships for external group #{group['name']}", MU::ERR
    if group['domain'] == "googlegroups.com"
      MU.log "Visit https://groups.google.com to manage Google Groups.", MU::ERR
    end
    ok = false
  end

  if group['members']
    group['members'].each { |m|
      if configurator.haveLitterMate?(m, "users")
        MU::Config.addDependency(group, m, "user")
      end
    }
  end

  if group['roles']
    group['roles'].each { |r|
      if r['role'] and r['role']['name'] and
         (!r['role']['deploy_id'] and !r['role']['id'])
        MU::Config.addDependency(group, r['role']['name'], "role")
      end
    }
  end

  ok
end

Public Instance Methods

create() click to toggle source

Called automatically by {MU::Deploy#createResources}

# File modules/mu/providers/google/group.rb, line 30
def create
  if !@config['external']
    if !@config['email']
      domains = MU::Cloud::Google.admin_directory(credentials: @credentials).list_domains(@customer)
      @config['email'] = @mu_name.downcase+"@"+domains.domains.first.domain_name
    end
    group_obj = MU::Cloud::Google.admin_directory(:Group).new(
      name: @mu_name,
      email: @config['email'],
      description: @deploy.deploy_id
    )

    MU.log "Creating group #{@mu_name}", details: group_obj

    resp = MU::Cloud::Google.admin_directory(credentials: @credentials).insert_group(group_obj)
    @cloud_id = resp.email

    MU::Cloud.resourceClass("Google", "Role").bindFromConfig("group", @cloud_id, @config['roles'], credentials: @config['credentials'])
  else
    @cloud_id = @config['name'].sub(/@.*/, "")+"@"+@config['domain']
  end
end
groom() click to toggle source

Called automatically by {MU::Deploy#createResources}

# File modules/mu/providers/google/group.rb, line 54
def groom
  MU::Cloud.resourceClass("Google", "Role").bindFromConfig("group", @cloud_id, @config['roles'], credentials: @config['credentials'], debug: true)

  if @config['members']
    resolved_desired = []
    @config['members'].each { |m|
      sibling_user = @deploy.findLitterMate(name: m, type: "users")
      usermail = if sibling_user
        sibling_user.cloud_id
      elsif !m.match(/@/)
        domains = MU::Cloud::Google.admin_directory(credentials: @credentials).list_domains(@customer)
        m+"@"+domains.domains.first.domain_name
      else
        m
      end
      resolved_desired << usermail
      next if members.include?(usermail)
      MU.log "Adding user #{usermail} to group #{@mu_name}"
      MU::Cloud::Google.admin_directory(credentials: @credentials).insert_member(
        @cloud_id,
        MU::Cloud::Google.admin_directory(:Member).new(
          email: usermail
        )
      )
    }

    deletia = members - resolved_desired
    deletia.each { |m|
      MU.log "Removing user #{m} from group #{@mu_name}", MU::NOTICE
      MU::Cloud::Google.admin_directory(credentials: @credentials).delete_member(@cloud_id, m)
    }

    # Theoretically there can be a delay
    begin
      if members.sort != resolved_desired.sort
        sleep 3
      end
    end while members.sort != resolved_desired.sort
  end

end
members() click to toggle source

Retrieve a list of users (by cloud id) of this group

# File modules/mu/providers/google/group.rb, line 97
        def members
          resp = MU::Cloud::Google.admin_directory(credentials: @credentials).list_members(@cloud_id)
          members = []
          if resp and resp.members
            members = resp.members.map { |m| m.email }
# XXX reject status != "ACTIVE" ?
          end
          members
        end
notify() click to toggle source

Return the metadata for this group configuration @return [Hash]

# File modules/mu/providers/google/group.rb, line 109
def notify
  if !@config['external']
    base = MU.structToHash(cloud_desc)
  end
  base ||= {}

  base
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/group.rb, line 207
        def toKitten(**_args)

          bok = {
            "cloud" => "Google",
            "credentials" => @config['credentials']
          }

          bok['name'] = cloud_desc.name
          bok['cloud_id'] = cloud_desc.email
          bok['members'] = members.dup
#          bok['members'] = members.map { |m|
#            MU::Config::Ref.get(
#              id: m,
#              cloud: "Google",
#              credentials: @config['credentials'],
#              type: "users"
#            )
#          }
          group_roles = MU::Cloud.resourceClass("Google", "Role").getAllBindings(@config['credentials'])["by_entity"]
          if group_roles["group"] and group_roles["group"][bok['cloud_id']] and
             group_roles["group"][bok['cloud_id']].size > 0
            bok['roles'] = MU::Cloud.resourceClass("Google", "Role").entityBindingsToSchema(group_roles["group"][bok['cloud_id']], credentials: @config['credentials'])
          end

          bok
       end