From 75965da74bbd9ded96dd2dfc0c3309e8e3eabcf7 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Mon, 15 Nov 2021 17:31:33 -0600 Subject: [PATCH] feat: assign psl --- oclif.manifest.json | 393 ++++++++++++++++++ .../force/user/permsetlicense/assign.ts | 117 ++++-- 2 files changed, 470 insertions(+), 40 deletions(-) create mode 100644 oclif.manifest.json diff --git a/oclif.manifest.json b/oclif.manifest.json new file mode 100644 index 00000000..843819ee --- /dev/null +++ b/oclif.manifest.json @@ -0,0 +1,393 @@ +{ + "version": "1.5.2", + "commands": { + "force:user:create": { + "id": "force:user:create", + "description": "create a user for a scratch org\nCreate a user for a scratch org, optionally setting an alias for use by the CLI, assigning permission sets (e.g., permsets=ps1,ps2), generating a password (e.g., generatepassword=true), and setting User sObject fields.", + "usage": "<%= command.id %> [name=value...] [-a ] [-f ] [-s] [-v ] [-u ] [--apiversion ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]", + "pluginName": "@salesforce/plugin-user", + "pluginType": "core", + "aliases": [], + "examples": [ + "sfdx force:user:create", + "sfdx force:user:create -a testuser1 -f config/project-user-def.json profileName='Chatter Free User'", + "sfdx force:user:create username=testuser1@my.org email=me@my.org permsets=DreamHouse", + "sfdx force:user:create -f config/project-user-def.json email=me@my.org generatepassword=true" + ], + "flags": { + "json": { "name": "json", "type": "boolean", "description": "format output as json", "allowNo": false }, + "loglevel": { + "name": "loglevel", + "type": "option", + "description": "logging level for this command invocation", + "required": false, + "helpValue": "(trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL)", + "options": [ + "trace", + "debug", + "info", + "warn", + "error", + "fatal", + "TRACE", + "DEBUG", + "INFO", + "WARN", + "ERROR", + "FATAL" + ], + "default": "warn" + }, + "targetdevhubusername": { + "name": "targetdevhubusername", + "type": "option", + "char": "v", + "description": "username or alias for the dev hub org; overrides default dev hub org" + }, + "targetusername": { + "name": "targetusername", + "type": "option", + "char": "u", + "description": "username or alias for the target org; overrides default target org" + }, + "apiversion": { + "name": "apiversion", + "type": "option", + "description": "override the api version used for api requests made by this command" + }, + "setalias": { + "name": "setalias", + "type": "option", + "char": "a", + "description": "set an alias for the created username to reference within the CLI" + }, + "definitionfile": { + "name": "definitionfile", + "type": "option", + "char": "f", + "description": "file path to a user definition" + }, + "setuniqueusername": { + "name": "setuniqueusername", + "type": "boolean", + "char": "s", + "description": "force the username, if specified in the definition file or at the command line, to be unique by appending the org ID", + "allowNo": false + } + }, + "args": [] + }, + "force:user:display": { + "id": "force:user:display", + "description": "displays information about a user of a scratch org\nOutput includes the profile name, org ID, access token, instance URL, login URL, and alias if applicable.", + "usage": "<%= command.id %> [-v ] [-u ] [--apiversion ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]", + "pluginName": "@salesforce/plugin-user", + "pluginType": "core", + "aliases": [], + "examples": ["sfdx force:user:display", "sfdx force:user:display -u me@my.org --json"], + "flags": { + "json": { "name": "json", "type": "boolean", "description": "format output as json", "allowNo": false }, + "loglevel": { + "name": "loglevel", + "type": "option", + "description": "logging level for this command invocation", + "required": false, + "helpValue": "(trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL)", + "options": [ + "trace", + "debug", + "info", + "warn", + "error", + "fatal", + "TRACE", + "DEBUG", + "INFO", + "WARN", + "ERROR", + "FATAL" + ], + "default": "warn" + }, + "targetdevhubusername": { + "name": "targetdevhubusername", + "type": "option", + "char": "v", + "description": "username or alias for the dev hub org; overrides default dev hub org" + }, + "targetusername": { + "name": "targetusername", + "type": "option", + "char": "u", + "description": "username or alias for the target org; overrides default target org" + }, + "apiversion": { + "name": "apiversion", + "type": "option", + "description": "override the api version used for api requests made by this command" + } + }, + "args": [] + }, + "force:user:list": { + "id": "force:user:list", + "description": "list all authenticated users of an org\nThe original scratch org admin is marked with \"(A)\"", + "usage": "<%= command.id %> [-v ] [-u ] [--apiversion ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]", + "pluginName": "@salesforce/plugin-user", + "pluginType": "core", + "aliases": [], + "examples": [ + "sfdx force:user:list", + "sfdx force:user:list -u me@my.org --json", + "sfdx force:user:list --json > tmp/MyUserList.json" + ], + "flags": { + "json": { "name": "json", "type": "boolean", "description": "format output as json", "allowNo": false }, + "loglevel": { + "name": "loglevel", + "type": "option", + "description": "logging level for this command invocation", + "required": false, + "helpValue": "(trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL)", + "options": [ + "trace", + "debug", + "info", + "warn", + "error", + "fatal", + "TRACE", + "DEBUG", + "INFO", + "WARN", + "ERROR", + "FATAL" + ], + "default": "warn" + }, + "targetdevhubusername": { + "name": "targetdevhubusername", + "type": "option", + "char": "v", + "description": "username or alias for the dev hub org; overrides default dev hub org" + }, + "targetusername": { + "name": "targetusername", + "type": "option", + "char": "u", + "description": "username or alias for the target org; overrides default target org" + }, + "apiversion": { + "name": "apiversion", + "type": "option", + "description": "override the api version used for api requests made by this command" + } + }, + "args": [] + }, + "force:user:password:generate": { + "id": "force:user:password:generate", + "description": "generate a password for scratch org users\nGenerates and sets a random password for one or more scratch org users. Targets the usernames listed with the --onbehalfof parameter or the --targetusername parameter. Defaults to the defaultusername.\n\nIf you haven’t set a default Dev Hub, or if your scratch org isn’t associated with your default Dev Hub, --targetdevhubusername is required.\n\nTo change the password strength, set the --complexity parameter to a value between 0 and 5. Each value specifies the types of characters used in the generated password: \n\n0 - lower case letters only\n1 - lower case letters and numbers only\n2 - lower case letters and symbols only\n3 - lower and upper case letters and numbers only\n4 - lower and upper case letters and symbols only\n5 - lower and upper case letters and numbers and symbols only \n\nTo see a password that was previously generated, run \"sfdx force:user:display\".", + "usage": "<%= command.id %> [-o ] [-l ] [-c ] [-v ] [-u ] [--apiversion ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]", + "pluginName": "@salesforce/plugin-user", + "pluginType": "core", + "aliases": [], + "examples": [ + "sfdx force:user:password:generate", + "sfdx force:user:password:generate -l 12", + "sfdx force:user:password:generate -c 3", + "sfdx force:user:password:generate -u me@my.org --json", + "sfdx force:user:password:generate -o \"user1@my.org,user2@my.org,user3@my.org\"" + ], + "flags": { + "json": { "name": "json", "type": "boolean", "description": "format output as json", "allowNo": false }, + "loglevel": { + "name": "loglevel", + "type": "option", + "description": "logging level for this command invocation", + "required": false, + "helpValue": "(trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL)", + "options": [ + "trace", + "debug", + "info", + "warn", + "error", + "fatal", + "TRACE", + "DEBUG", + "INFO", + "WARN", + "ERROR", + "FATAL" + ], + "default": "warn" + }, + "targetdevhubusername": { + "name": "targetdevhubusername", + "type": "option", + "char": "v", + "description": "username or alias for the dev hub org; overrides default dev hub org" + }, + "targetusername": { + "name": "targetusername", + "type": "option", + "char": "u", + "description": "username or alias for the target org; overrides default target org" + }, + "apiversion": { + "name": "apiversion", + "type": "option", + "description": "override the api version used for api requests made by this command" + }, + "onbehalfof": { + "name": "onbehalfof", + "type": "option", + "char": "o", + "description": "comma-separated list of usernames or aliases to assign the password to" + }, + "length": { + "name": "length", + "type": "option", + "char": "l", + "description": "number of characters in the generated password; valid values are between 8 and 1000", + "default": 13 + }, + "complexity": { + "name": "complexity", + "type": "option", + "char": "c", + "description": "level of password complexity or strength; the higher the value, the stronger the password", + "default": 5 + } + }, + "args": [] + }, + "force:user:permset:assign": { + "id": "force:user:permset:assign", + "description": "assign a permission set to one or more users of an org\nTo specify an alias for the -u or -o parameter, use the username alias you set with the \"alias:set\" CLI command, not the User.Alias value of the org user.", + "usage": "<%= command.id %> -n [-o ] [-u ] [--apiversion ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]", + "pluginName": "@salesforce/plugin-user", + "pluginType": "core", + "aliases": [], + "examples": [ + "sfdx force:user:permset:assign -n \"DreamHouse, LargeDreamHouse\"", + "sfdx force:user:permset:assign -n DreamHouse -u me@my.org", + "sfdx force:user:permset:assign -n DreamHouse -o \"user1@my.org,user2,user3\"" + ], + "flags": { + "json": { "name": "json", "type": "boolean", "description": "format output as json", "allowNo": false }, + "loglevel": { + "name": "loglevel", + "type": "option", + "description": "logging level for this command invocation", + "required": false, + "helpValue": "(trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL)", + "options": [ + "trace", + "debug", + "info", + "warn", + "error", + "fatal", + "TRACE", + "DEBUG", + "INFO", + "WARN", + "ERROR", + "FATAL" + ], + "default": "warn" + }, + "targetusername": { + "name": "targetusername", + "type": "option", + "char": "u", + "description": "username or alias for the target org; overrides default target org" + }, + "apiversion": { + "name": "apiversion", + "type": "option", + "description": "override the api version used for api requests made by this command" + }, + "permsetname": { + "name": "permsetname", + "type": "option", + "char": "n", + "description": "comma-separated list of permission sets to assign", + "required": true + }, + "onbehalfof": { + "name": "onbehalfof", + "type": "option", + "char": "o", + "description": "comma-separated list of usernames or aliases to assign the permission set to" + } + }, + "args": [] + }, + "force:user:permsetlicense:assign": { + "id": "force:user:permsetlicense:assign", + "description": "assign a permission set license to one or more users of an org", + "usage": "<%= command.id %> -n [-o ] [-u ] [--apiversion ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]", + "pluginName": "@salesforce/plugin-user", + "pluginType": "core", + "aliases": [], + "examples": [ + "sfdx force:user:permsetlicense:assign -n DreamHouse", + "sfdx force:user:permsetlicense:assign -n DreamHouse -u me@my.org", + "sfdx force:user:permsetlicense:assign -n DreamHouse -o \"user1@my.org,user2,user3\"" + ], + "flags": { + "json": { "name": "json", "type": "boolean", "description": "format output as json", "allowNo": false }, + "loglevel": { + "name": "loglevel", + "type": "option", + "description": "logging level for this command invocation", + "required": false, + "helpValue": "(trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL)", + "options": [ + "trace", + "debug", + "info", + "warn", + "error", + "fatal", + "TRACE", + "DEBUG", + "INFO", + "WARN", + "ERROR", + "FATAL" + ], + "default": "warn" + }, + "targetusername": { + "name": "targetusername", + "type": "option", + "char": "u", + "description": "username or alias for the target org; overrides default target org" + }, + "apiversion": { + "name": "apiversion", + "type": "option", + "description": "override the api version used for api requests made by this command" + }, + "name": { + "name": "name", + "type": "option", + "char": "n", + "description": "the name of the permission set license to assign", + "required": true + }, + "onbehalfof": { + "name": "onbehalfof", + "type": "option", + "char": "o", + "description": "comma-separated list of usernames or aliases to assign the permission set license to" + } + }, + "args": [] + } + } +} diff --git a/src/commands/force/user/permsetlicense/assign.ts b/src/commands/force/user/permsetlicense/assign.ts index 5fc5def7..12dc9fd1 100644 --- a/src/commands/force/user/permsetlicense/assign.ts +++ b/src/commands/force/user/permsetlicense/assign.ts @@ -7,7 +7,7 @@ import * as os from 'os'; import { flags, FlagsConfig, SfdxCommand } from '@salesforce/command'; -import { Aliases, Connection, Messages, User, AuthInfo, Org, UserFields } from '@salesforce/core'; +import { Aliases, Messages, SfdxError, User, UserFields } from '@salesforce/core'; Messages.importMessagesDirectory(__dirname); const messages = Messages.loadMessages('@salesforce/plugin-user', 'permsetlicense.assign'); @@ -48,53 +48,35 @@ export class UserPermsetLicenseAssignCommand extends SfdxCommand { }; private readonly successes: SuccessMsg[] = []; private readonly failures: FailureMsg[] = []; + private pslId: string; public async run(): Promise { const usernames = (this.flags.onbehalfof as string[]) ?? [this.org.getUsername()]; - - for (const username of usernames) { - // Convert any aliases to usernames - const aliasOrUsername = (await Aliases.fetch(username)) || username; - const connection: Connection = await Connection.create({ - authInfo: await AuthInfo.create({ username }), - }); - const org = await Org.create({ connection }); - const user: User = await User.create({ org }); - const fields: UserFields = await user.retrieve(username); - - const pslName = this.flags.name as string; - - const psl = await connection.singleRecordQuery( - `select Id from PermissionSetLicense where DeveloperName = '${pslName}' or MasterLabel = '${pslName}'` - ); - - try { - await connection.sobject('PermissionSetLicenseAssign').create({ - AssigneeId: fields.id, - PermissionSetLicenseId: psl.Id, - }); - this.successes.push({ - name: aliasOrUsername, - value: this.flags.name as string, - }); - } catch (e) { - // idempotency. If user(s) already have PSL, the API will throw an error about duplicate value. - if (e instanceof Error && e.message.startsWith('duplicate value found')) { - this.ux.warn(messages.getMessage('duplicateValue', [aliasOrUsername, pslName])); - this.successes.push({ - name: aliasOrUsername, - value: pslName, - }); + this.logger.debug(`will assign permset to users: ${usernames.join(', ')}`); + const pslName = this.flags.name as string; + + const conn = this.org.getConnection(); + try { + this.pslId = ( + await conn.singleRecordQuery( + `select Id from PermissionSetLicense where DeveloperName = '${pslName}' or MasterLabel = '${pslName}'` + ) + ).Id; + } catch { + throw new SfdxError('PermissionSetLicense not found'); + } + (await Promise.all(usernames.map((username) => this.usernameToPSLAssignment({ pslName, username })))).map( + (result) => { + if (isSuccess(result)) { + this.successes.push(result); } else { - this.failures.push({ - name: aliasOrUsername, - message: e instanceof Error ? e.message : 'error contained no message', - }); + this.failures.push(result); } } - } + ); this.print(); + this.setExitCode(); return { successes: this.successes, @@ -102,6 +84,57 @@ export class UserPermsetLicenseAssignCommand extends SfdxCommand { }; } + // handles one username/psl combo so these can run in parallel + private async usernameToPSLAssignment({ + pslName, + username, + }: { + pslName: string; + username: string; + }): Promise { + // Convert any aliases to usernames + const aliasOrUsername = (await Aliases.fetch(username)) || username; + + const user: User = await User.create({ org: this.org }); + const fields: UserFields = await user.retrieve(username); + + try { + await this.org.getConnection().sobject('PermissionSetLicenseAssign').create({ + AssigneeId: fields.id, + PermissionSetLicenseId: this.pslId, + }); + return { + name: aliasOrUsername, + value: pslName, + }; + } catch (e) { + // idempotency. If user(s) already have PSL, the API will throw an error about duplicate value. + // but we're going to call that a success + if (e instanceof Error && e.message.startsWith('duplicate value found')) { + this.ux.warn(messages.getMessage('duplicateValue', [aliasOrUsername, pslName])); + return { + name: aliasOrUsername, + value: pslName, + }; + } else { + return { + name: aliasOrUsername, + message: e instanceof Error ? e.message : 'error contained no message', + }; + } + } + } + + private setExitCode(): void { + if (this.failures.length && this.successes.length) { + process.exitCode = 69; + } else if (this.failures.length) { + process.exitCode = 1; + } else if (this.successes.length) { + process.exitCode = 0; + } + } + private print(): void { if (this.successes.length > 0) { this.ux.styledHeader('Permset Licenses Assigned'); @@ -128,3 +161,7 @@ export class UserPermsetLicenseAssignCommand extends SfdxCommand { } } } + +const isSuccess = (input: SuccessMsg | FailureMsg): input is SuccessMsg => { + return (input as SuccessMsg).value !== undefined; +};