class MU::Cloud::Google::User
A user as configured in {MU::Config::BasketofKittens::users}
Public Class Methods
We can either refer to a service account, which is scoped to a project (a Habitat
in Mu parlance), or a “real” user, which comes from an external directory like GMail, GSuite, or Cloud
Identity.
# File modules/mu/providers/google/user.rb, line 407 def self.canLiveIn [:Habitat, nil] end
Try to determine whether the given string looks like a pre-configured GCP service account, as distinct from one we might create or manage
# File modules/mu/providers/google/user.rb, line 381 def self.cannedServiceAcctName?(name) return false if !name name.match(/\b\d+\-compute@developer\.gserviceaccount\.com$/) or name.match(/\bproject-\d+@storage-transfer-service\.iam\.gserviceaccount\.com$/) or name.match(/\b\d+@cloudbuild\.gserviceaccount\.com$/) or name.match(/\b\d+@cloudservices\.gserviceaccount\.com$/) or name.match(/\bservice-\d+@containerregistry\.iam\.gserviceaccount\.com$/) or name.match(/\bservice-\d+@container-analysis\.iam\.gserviceaccount\.com$/) or name.match(/\bservice-\d+@compute-system\.iam\.gserviceaccount\.com$/) or name.match(/\bservice-\d+@container-engine-robot\.iam\.gserviceaccount\.com$/) or name.match(/\bservice-\d+@gc[pf]-admin-robot\.iam\.gserviceaccount\.com$/) or name.match(/\bservice-\d+@dataflow-service-producer-prod\.iam\.gserviceaccount\.com$/) or name.match(/\bservice-\d+@dataproc-accounts\.iam\.gserviceaccount\.com$/) or name.match(/\bservice-\d+@endpoints-portal\.iam\.gserviceaccount\.com$/) or name.match(/\bservice-\d+@cloud-filer\.iam\.gserviceaccount\.com$/) or name.match(/\bservice-\d+@cloud-redis\.iam\.gserviceaccount\.com$/) or name.match(/\bservice-\d+@firebase-rules\.iam\.gserviceaccount\.com$/) or name.match(/\bservice-\d+@cloud-tpu\.iam\.gserviceaccount\.com$/) or name.match(/\bservice-\d+@sourcerepo-service-accounts\.iam\.gserviceaccount\.com$/) or name.match(/\bservice-\d+@gcp-sa-[^\.]+\.iam\.gserviceaccount\.com$/) or name.match(/\bp\d+\-\d+@gcp-sa-logging\.iam\.gserviceaccount\.com$/) end
Remove all users 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/user.rb, line 257 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 User artifacts do not support labels, so ignoremaster cleanup flag has no effect", MU::DEBUG, details: filter # We don't have a good way of tagging directory users, so we rely # on the known parameter, which is pulled from deployment metadata if flags['known'] and my_org dir_users = MU::Cloud::Google.admin_directory(credentials: credentials).list_users(customer: MU::Cloud::Google.customerID(credentials)).users if dir_users dir_users.each { |user| if flags['known'].include?(user.primary_email) MU.log "Deleting user #{user.primary_email} from #{my_org.display_name}", details: user if !noop MU::Cloud::Google.admin_directory(credentials: credentials).delete_user(user.id) end end } flags['known'].each { |user_email| next if user_email.nil? next if !user_email.match(/^[^\/]+@[^\/]+$/) MU::Cloud.resourceClass("Google", "Role").removeBindings("user", user_email, credentials: credentials, noop: noop) } end end flags["habitat"] ||= MU::Cloud::Google.defaultProject(credentials) resp = MU::Cloud::Google.iam(credentials: credentials).list_project_service_accounts( "projects/"+flags["habitat"] ) if resp and resp.accounts and MU.deploy_id resp.accounts.each { |sa| if (sa.description and sa.description == MU.deploy_id) or (sa.display_name and sa.display_name.match(/^#{Regexp.quote(MU.deploy_id)}-/i)) begin MU.log "Deleting service account #{sa.name}", details: sa if !noop MU::Cloud::Google.iam(credentials: credentials).delete_project_service_account(sa.name) end rescue ::Google::Apis::ClientError => e raise e if !e.message.match(/^notFound: /) end end } 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/user.rb, line 321 def self.find(**args) cred_cfg = MU::Cloud::Google.credConfig(args[:credentials]) args[:project] ||= args[:habitat] found = {} if args[:cloud_id] and args[:flags] and args[:flags]["skip_provider_owned"] and MU::Cloud::Google::User.cannedServiceAcctName?(args[:cloud_id]) return found end # If the project id is embedded in the cloud_id, honor it if args[:cloud_id] if args[:cloud_id].match(/projects\/(.+?)\//) args[:project] = Regexp.last_match[1] elsif args[:cloud_id].match(/@([^\.]+)\.iam\.gserviceaccount\.com$/) args[:project] = Regexp.last_match[1] end end if args[:project] # project-local service accounts resp = begin MU::Cloud::Google.iam(credentials: args[:credentials]).list_project_service_accounts( "projects/"+args[:project] ) rescue ::Google::Apis::ClientError MU.log "Do not have permissions to retrieve service accounts for project #{args[:project]}", MU::WARN end if resp and resp.accounts resp.accounts.each { |sa| if args[:flags] and args[:flags]["skip_provider_owned"] and MU::Cloud::Google::User.cannedServiceAcctName?(sa.name) next end if !args[:cloud_id] or (sa.display_name and sa.display_name == args[:cloud_id]) or (sa.name and sa.name == args[:cloud_id]) or (sa.email and sa.email == args[:cloud_id]) found[sa.name] = sa end } end else if cred_cfg['masquerade_as'] resp = MU::Cloud::Google.admin_directory(credentials: args[:credentials]).list_users(customer: MU::Cloud::Google.customerID(args[:credentials]), show_deleted: false) # XXX this ain't exactly performant, do some caching or something if resp and resp.users resp.users.each { |u| next if args[:cloud_id] and !args[:cloud_id] != u.primary_email found[u.primary_email] = u } end end end found end
Create and inject a service account on behalf of the parent resource. Return the modified parent configuration hash with references to the new addition. @param parent [Hash] @param configurator [MU::Config] @return [Hash]
# File modules/mu/providers/google/user.rb, line 663 def self.genericServiceAccount(parent, configurator) user = { "name" => parent['name'], "cloud" => "Google", "project" => parent["project"], "credentials" => parent["credentials"], "type" => "service" } if user["name"].length < 6 user["name"] += Password.pronounceable(6) end if parent['roles'] user['roles'] = parent['roles'].dup end configurator.insertKitten(user, "users", true) parent['service_account'] = MU::Config::Ref.get( type: "users", cloud: "Google", name: user["name"], project: user["project"], credentials: user["credentials"] ) MU::Config.addDependency(parent, user['name'], "user") parent 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/user.rb, line 243 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/user.rb, line 23 def initialize(**args) super # If we're being reverse-engineered from a cloud descriptor, use that # to determine what sort of account we are. if args[:from_cloud_desc] @cloud_desc_cache = args[:from_cloud_desc] MU::Cloud::Google.admin_directory MU::Cloud::Google.iam if args[:from_cloud_desc].class == ::Google::Apis::AdminDirectoryV1::User @config['type'] = "interactive" @cloud_id = args[:from_cloud_desc].primary_email elsif args[:from_cloud_desc].class == ::Google::Apis::IamV1::ServiceAccount @config['type'] = "service" @config['name'] = args[:from_cloud_desc].display_name if @config['name'].nil? or @config['name'].empty? @config['name'] = args[:from_cloud_desc].name.sub(/.*?\/([^\/@]+)(?:@[^\/]*)?$/, '\1') end @cloud_id = args[:from_cloud_desc].name else raise MuError, "Google::User got from_cloud_desc arg of class #{args[:from_cloud_desc].class.name}, but doesn't know what to do with it" end end @mu_name ||= if (@config['unique_name'] or @config['type'] == "service") and !@config['scrub_mu_isms'] @deploy.getResourceName(@config["name"]) else @config['name'] end if @config['type'] == "interactive" and @config['email'] @cloud_id ||= @config['email'] end end
Denote whether this resource implementation is experiment, ready for testing, or ready for production use.
# File modules/mu/providers/google/user.rb, line 249 def self.quality MU::Cloud::RELEASE 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/user.rb, line 479 def self.schema(_config) toplevel_required = [] schema = { "name" => { "type" => "string", "description" => "If the +type+ of this account is not +service+, this can include an optional @domain component (<tt>foo@example.com</tt>), which is equivalent to the +domain+ configuration option. The following rules apply to +directory+ (non-<tt>service</tt>) accounts only: If the domain portion is not specified, and we manage exactly one GSuite or Cloud Identity domain, we will attempt to create the user in that domain. If we do not manage any domains, and none are specified, we will assume <tt>@gmail.com</tt> for the domain and attempt to bind an existing external GMail user 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 user in that domain. If it is a domain we do not manage, we will attempt to bind an existing external user from that domain to roles under our jurisdiction. If we are binding (rather than creating) a user 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" => "If creating or binding an +interactive+ user, this is the domain of which the user should be a member. This can instead be embedded in the {name} field: +foo@example.com+." }, "given_name" => { "type" => "string", "description" => "Optionally set the +given_name+ field of a +directory+ account. Ignored for +service+ accounts." }, "first_name" => { "type" => "string", "description" => "Alias for +given_name+" }, "family_name" => { "type" => "string", "description" => "Optionally set the +family_name+ field of a +directory+ account. Ignored for +service+ accounts." }, "last_name" => { "type" => "string", "description" => "Alias for +family_name+" }, "description" => { "type" => "string", "description" => "Comment field for service accounts, which we normally use to store the originating deploy's deploy id, since GCP service accounts do not have labels. This field is only honored if +scrub_mu_isms+ is set." }, "email" => { "type" => "string", "description" => "Canonical email address for a +directory+ user. If not specified, will be set to +name@domain+." }, "external" => { "type" => "boolean", "description" => "Explicitly flag this user as originating from an external domain. This should always autodetect correctly." }, "admin" => { "type" => "boolean", "description" => "If the user is +interactive+ and resides in a domain we manage, set their +is_admin+ flag.", "default" => false }, "suspend" => { "type" => "boolean", "description" => "If the user is +interactive+ and resides in a domain we manage, this can be used to lock their account.", "default" => false }, "type" => { "type" => "string", "description" => "'interactive' will either attempt to bind an existing user to a role under our jurisdiction, or create a new directory user, depending on the domain of the user specified and whether we manage any directories; 'service' will create a service account and generate API keys.", "enum" => ["interactive", "service"], "default" => "interactive" }, "roles" => { "type" => "array", "description" => "One or more Google IAM roles to associate with this user.", "items" => MU::Cloud.resourceClass("Google", "Role").ref_schema } } [toplevel_required, schema] end
Cloud-specific pre-processing of {MU::Config::BasketofKittens::users}, bare and unvalidated. @param user [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/user.rb, line 559 def self.validateConfig(user, _configurator) ok = true my_domains = MU::Cloud::Google.getDomains(user['credentials']) my_org = MU::Cloud::Google.getOrg(user['credentials']) # Deal with these name alias fields, here for the convenience of your # easily confused english-centric type of person user['given_name'] ||= user['first_name'] if user['first_name'] user['family_name'] ||= user['last_name'] if user['last_name'] user.delete("first_name") user.delete("last_name") if user['name'].match(/@(.*+)$/) domain = Regexp.last_match[1].downcase if domain and user['domain'] and domain != user['domain'].downcase MU.log "User #{user['name']} had a domain component, but the domain field was also specified (#{user['domain']}) and they don't match." ok = false end user['domain'] = domain if user['type'] == "service" MU.log "Username #{user['name']} appears to be a directory or external username, cannot use with 'service'", MU::ERR ok = false else user['type'] = "interactive" if !my_domains or !my_domains.include?(domain) user['external'] = true if !["gmail.com", "google.com"].include?(domain) MU.log "#{user['name']} appears to be a member of a domain that our credentials (#{user['credentials']}) do not manage; attempts to grant access for this user may fail!", MU::WARN end if !user['roles'] or user['roles'].empty? user['roles'] = [ { "role" => { "id" => "roles/viewer" } } ] MU.log "External Google user specified with no role binding, will grant 'viewer' in #{my_org ? "organization #{my_org.display_name}" : "project #{user['project']}"}", MU::WARN end else # this is actually targeting a domain we manage! yay! end end elsif user['type'] != "service" if !user['domain'] if my_domains.size == 1 user['domain'] = my_domains.first elsif my_domains.size > 1 MU.log "Google interactive User #{user['name']} did not specify a domain, and we have multiple defaults available. Must specify exactly one.", MU::ERR, details: my_domains ok = false else user['domain'] = "gmail.com" end end end if user['domain'] user['email'] ||= user['name'].gsub(/@.*/, "")+"@"+user['domain'] end if user['groups'] and user['groups'].size > 0 and my_org.nil? MU.log "Cannot change Google group memberships with credentials that do not manage GSuite or Cloud Identity.\nVisit https://groups.google.com to manage groups.", MU::ERR ok = false end if user['type'] == "service" user['project'] ||= MU::Cloud::Google.defaultProject(user['credentials']) end if user['type'] != "service" and user["create_api_key"] MU.log "Only service accounts can have API keys in Google Cloud", MU::ERR ok = false end if user['roles'] user['roles'].each { |r| if r['role'] and r['role']['name'] and (!r['role']['deploy_id'] and !r['role']['id']) MU::Config.addDependency(user, r['role']['name'], "role") end if !r["projects"] and !r["organizations"] and !r["folders"] if my_org r["organizations"] = [my_org.name] else r["projects"] = [ "id" => user["project"] ] end end } end ok end
Public Instance Methods
Retrieve the cloud descriptor for this resource. @return [Google::Apis::Core::Hashable]
# File modules/mu/providers/google/user.rb, line 199 def cloud_desc(use_cache: true) return @cloud_desc_cache if @cloud_desc_cache and use_cache if @config['type'] == "interactive" or !@config['type'] @config['type'] ||= "interactive" if !@config['external'] @cloud_id ||= @config['email'] @cloud_desc_cache = MU::Cloud::Google.admin_directory(credentials: @config['credentials']).get_user(@cloud_id) else return nil end else @config['type'] ||= "service" # this often fails even when it succeeded earlier, so try to be # resilient on GCP's behalf retries = 0 begin @cloud_desc_cache = MU::Cloud::Google.iam(credentials: @config['credentials']).get_project_service_account(@cloud_id) rescue ::Google::Apis::ClientError => e if e.message.match(/notFound:/) and retries < 10 sleep 3 retries += 1 retry end end end @cloud_desc_cache end
Called automatically by {MU::Deploy#createResources}
# File modules/mu/providers/google/user.rb, line 60 def create if @config['type'] == "service" acct_id = @config['scrub_mu_isms'] ? @config['name'] : @deploy.getResourceName(@config["name"], max_length: 30).downcase req_obj = MU::Cloud::Google.iam(:CreateServiceAccountRequest).new( account_id: acct_id, service_account: MU::Cloud::Google.iam(:ServiceAccount).new( display_name: @mu_name, description: @config['scrub_mu_isms'] ? @config['description'] : @deploy.deploy_id ) ) if @config['use_if_exists'] # XXX maybe just set @cloud_id to projects/#{@project_id}/serviceAccounts/#{@mu_name}@#{@project_id}.iam.gserviceaccount.com and see if cloud_desc returns something found = MU::Cloud::Google::User.find(project: @project_id, cloud_id: @mu_name) if found.size == 1 @cloud_id = found.keys.first MU.log "Service account #{@cloud_id} already existed, using it" end end if !@cloud_id MU.log "Creating service account #{@mu_name}" resp = MU::Cloud::Google.iam(credentials: @config['credentials']).create_service_account( "projects/"+@config['project'], req_obj ) @cloud_id = resp.name end # make sure we've been created before moving on begin cloud_desc rescue ::Google::Apis::ClientError => e if e.message.match(/notFound:/) sleep 3 retry end end elsif @config['external'] @cloud_id = @config['email'] MU::Cloud.resourceClass("Google", "Role").bindFromConfig("user", @cloud_id, @config['roles'], credentials: @config['credentials']) else if !@config['email'] domains = MU::Cloud::Google.admin_directory(credentials: @credentials).list_domains(@customer) @config['email'] = @mu_name.gsub(/@.*/, "")+"@"+domains.domains.first.domain_name end username_obj = MU::Cloud::Google.admin_directory(:UserName).new( given_name: (@config['given_name'] || @config['name']), family_name: (@config['family_name'] || @deploy.deploy_id), full_name: @mu_name ) user_obj = MU::Cloud::Google.admin_directory(:User).new( name: username_obj, primary_email: @config['email'], suspended: @config['suspend'], is_admin: @config['admin'], password: MU.generateWindowsPassword, change_password_at_next_login: (@config.has_key?('force_password_change') ? @config['force_password_change'] : true) ) MU.log "Creating user #{@mu_name}", details: user_obj resp = MU::Cloud::Google.admin_directory(credentials: @credentials).insert_user(user_obj) @cloud_id = resp.primary_email end end
Called automatically by {MU::Deploy#createResources}
# File modules/mu/providers/google/user.rb, line 129 def groom if @config['external'] MU::Cloud.resourceClass("Google", "Role").bindFromConfig("user", @cloud_id, @config['roles'], credentials: @config['credentials']) elsif @config['type'] == "interactive" need_update = false MU::Cloud.resourceClass("Google", "Role").bindFromConfig("user", @cloud_id, @config['roles'], credentials: @config['credentials']) if @config['force_password_change'] and !cloud_desc.change_password_at_next_login MU.log "Forcing #{@mu_name} to change their password at next login", MU::NOTICE need_update = true elsif @config.has_key?("force_password_change") and !@config['force_password_change'] and cloud_desc.change_password_at_next_login MU.log "No longer forcing #{@mu_name} to change their password at next login", MU::NOTICE need_update = true end if @config['admin'] != cloud_desc.is_admin MU.log "Setting 'is_admin' flag to #{@config['admin'].to_s} for directory user #{@mu_name}", MU::NOTICE MU::Cloud::Google.admin_directory(credentials: @credentials).make_user_admin(@cloud_id, MU::Cloud::Google.admin_directory(:UserMakeAdmin).new(status: @config['admin'])) end if @config['suspend'] != cloud_desc.suspended need_update = true end if cloud_desc.name.given_name != (@config['given_name'] || @config['name']) or cloud_desc.name.family_name != (@config['family_name'] || @deploy.deploy_id) or cloud_desc.primary_email != @config['email'] need_update = true end if need_update username_obj = MU::Cloud::Google.admin_directory(:UserName).new( given_name: (@config['given_name'] || @config['name']), family_name: (@config['family_name'] || @deploy.deploy_id), full_name: @mu_name ) user_obj = MU::Cloud::Google.admin_directory(:User).new( name: username_obj, primary_email: @config['email'], suspended: @config['suspend'], change_password_at_next_login: (@config.has_key?('force_password_change') ? @config['force_password_change'] : true) ) MU.log "Updating directory user #{@mu_name}", MU::NOTICE, details: user_obj resp = MU::Cloud::Google.admin_directory(credentials: @credentials).update_user(@cloud_id, user_obj) @cloud_id = resp.primary_email end else MU::Cloud.resourceClass("Google", "Role").bindFromConfig("serviceAccount", @cloud_id.gsub(/.*?\/([^\/]+)$/, '\1'), @config['roles'], credentials: @config['credentials']) if @config['create_api_key'] resp = MU::Cloud::Google.iam(credentials: @config['credentials']).list_project_service_account_keys( cloud_desc.name ) if resp.keys.size == 0 MU.log "Generating API keys for service account #{@mu_name}" resp = MU::Cloud::Google.iam(credentials: @config['credentials']).create_service_account_key( cloud_desc.name ) scratchitem = MU::Master.storeScratchPadSecret("Google Cloud Service Account credentials for #{@mu_name}:\n<pre style='text-align:left;'>#{resp.private_key_data}</pre>") MU.log "User #{@mu_name}'s Google Cloud Service Account credentials can be retrieved from: https://#{$MU_CFG['public_address']}/scratchpad/#{scratchitem}", MU::SUMMARY end end end end
Return the metadata for this user configuration @return [Hash]
# File modules/mu/providers/google/user.rb, line 230 def notify description = if !@config['external'] MU.structToHash(cloud_desc) else {} end description.delete(:etag) if description description 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/user.rb, line 414 def toKitten(**_args) if MU::Cloud::Google::User.cannedServiceAcctName?(@cloud_id) return nil end bok = { "cloud" => "Google", "credentials" => @config['credentials'] } if cloud_desc.nil? MU.log @config['name']+" couldn't fetch its cloud descriptor", MU::WARN, details: @cloud_id return nil end user_roles = MU::Cloud.resourceClass("Google", "Role").getAllBindings(@config['credentials'])["by_entity"] if cloud_desc.nil? MU.log "FAILED TO FIND CLOUD DESCRIPTOR FOR #{self}", MU::ERR, details: @config return nil end bok['name'] = @config['name'] bok['cloud_id'] = @cloud_id bok['type'] = @config['type'] bok['type'] ||= "service" if bok['type'] == "service" bok['name'].gsub!(/@.*/, '') if cloud_desc.description and !cloud_desc.description.empty? and !cloud_desc.description.match(/^[A-Z0-9_-]+-[A-Z0-9_-]+-\d{10}-[A-Z]{2}$/) bok['description'] = cloud_desc.description end bok['project'] = @project_id keys = MU::Cloud::Google.iam(credentials: @config['credentials']).list_project_service_account_keys(@cloud_id) if keys and keys.keys and keys.keys.size > 0 bok['create_api_key'] = true end # MU.log "service account #{@cloud_id}", MU::NOTICE, details: MU::Cloud::Google.iam(credentials: @config['credentials']).get_project_service_account_iam_policy(cloud_desc.name) if user_roles["serviceAccount"] and user_roles["serviceAccount"][bok['cloud_id']] and user_roles["serviceAccount"][bok['cloud_id']].size > 0 bok['roles'] = MU::Cloud.resourceClass("Google", "Role").entityBindingsToSchema(user_roles["serviceAccount"][bok['cloud_id']]) end else if user_roles["user"] and user_roles["user"][bok['cloud_id']] and user_roles["user"][bok['cloud_id']].size > 0 bok['roles'] = MU::Cloud.resourceClass("Google", "Role").entityBindingsToSchema(user_roles["user"][bok['cloud_id']], credentials: @config['credentials']) end bok['given_name'] = cloud_desc.name.given_name if cloud_desc.name.given_name and !cloud_desc.name.given_name.empty? bok['family_name'] = cloud_desc.name.family_name if cloud_desc.name.family_name and !cloud_desc.name.family_name.empty? bok['email'] = cloud_desc.primary_email bok['suspend'] = cloud_desc.suspended bok['admin'] = cloud_desc.is_admin end bok['use_if_exists'] = true bok end