-
Notifications
You must be signed in to change notification settings - Fork 312
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New command: m365 entra multitenant set
- Loading branch information
Showing
6 changed files
with
298 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,6 +67,7 @@ const dictionary = [ | |
'member', | ||
'messaging', | ||
'model', | ||
'multitenant', | ||
'm365', | ||
'news', | ||
'oauth2', | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import Global from '/docs/cmd/_global.mdx'; | ||
|
||
# entra multitenant set | ||
|
||
Updates the properties of a multitenant organization | ||
|
||
## Usage | ||
|
||
```sh | ||
m365 entra multitenant set [options] | ||
``` | ||
|
||
## Options | ||
|
||
```md definition-list | ||
`-n, --displayName [displayName]` | ||
: New display name of the multitenant organization. Both options, `displayName` and `description` are optional but at least must be specified. | ||
|
||
`-d, --description [description]` | ||
: New description of the multitenant organization. Both options, `displayName` and `description` are optional but at least must be specified. | ||
``` | ||
|
||
<Global /> | ||
|
||
## Remarks | ||
|
||
:::info | ||
|
||
To use this command you must be at least **Security Administrator**. | ||
|
||
::: | ||
|
||
## Examples | ||
|
||
Update display name of multitenant organization | ||
|
||
```sh | ||
m365 entra multitenant set --displayName 'Fabrikam organization' | ||
``` | ||
|
||
Update display name and description of multitenant organization | ||
|
||
```sh | ||
m365 entra multitenant set --displayName 'Fabrikam organization' --description 'Multitenant organization between Fabrikam and Contoso' | ||
``` | ||
|
||
## Response | ||
|
||
The command won't return a response on success | ||
|
||
## More information | ||
|
||
- Multitenant organization: https://learn.microsoft.com/entra/identity/multi-tenant-organizations/overview |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
138 changes: 138 additions & 0 deletions
138
src/m365/entra/commands/multitenant/multitenant-set.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import assert from 'assert'; | ||
import sinon from 'sinon'; | ||
import auth from '../../../../Auth.js'; | ||
import { cli } from '../../../../cli/cli.js'; | ||
import { CommandInfo } from '../../../../cli/CommandInfo.js'; | ||
import commands from '../../commands.js'; | ||
import command from './multitenant-set.js'; | ||
import { telemetry } from '../../../../telemetry.js'; | ||
import { pid } from '../../../../utils/pid.js'; | ||
import { session } from '../../../../utils/session.js'; | ||
import { sinonUtil } from '../../../../utils/sinonUtil.js'; | ||
import request from '../../../../request.js'; | ||
import { Logger } from '../../../../cli/Logger.js'; | ||
import { CommandError } from '../../../../Command.js'; | ||
|
||
describe(commands.MULTITENANT_SET, () => { | ||
let log: string[]; | ||
let logger: Logger; | ||
let commandInfo: CommandInfo; | ||
|
||
before(() => { | ||
sinon.stub(auth, 'restoreAuth').resolves(); | ||
sinon.stub(telemetry, 'trackEvent').returns(); | ||
sinon.stub(pid, 'getProcessName').returns(''); | ||
sinon.stub(session, 'getId').returns(''); | ||
auth.connection.active = true; | ||
commandInfo = cli.getCommandInfo(command); | ||
}); | ||
|
||
beforeEach(() => { | ||
log = []; | ||
logger = { | ||
log: async (msg: string) => { | ||
log.push(msg); | ||
}, | ||
logRaw: async (msg: string) => { | ||
log.push(msg); | ||
}, | ||
logToStderr: async (msg: string) => { | ||
log.push(msg); | ||
} | ||
}; | ||
(command as any).pollingInterval = 0; | ||
}); | ||
|
||
afterEach(() => { | ||
sinonUtil.restore([ | ||
request.patch | ||
]); | ||
}); | ||
|
||
after(() => { | ||
sinon.restore(); | ||
auth.connection.active = false; | ||
}); | ||
|
||
it('has correct name', () => { | ||
assert.strictEqual(command.name, commands.MULTITENANT_SET); | ||
}); | ||
|
||
it('has a description', () => { | ||
assert.notStrictEqual(command.description, null); | ||
}); | ||
|
||
it('passes validation when only displayName is specified', async () => { | ||
const actual = await command.validate({ options: { displayName: 'Contoso organization' } }, commandInfo); | ||
assert.strictEqual(actual, true); | ||
}); | ||
|
||
it('passes validation when only description is specified', async () => { | ||
const actual = await command.validate({ options: { description: 'Contoso and partners' } }, commandInfo); | ||
assert.strictEqual(actual, true); | ||
}); | ||
|
||
it('passes validation when the displayName and description are specified', async () => { | ||
const actual = await command.validate({ options: { displayName: 'Contoso organization', description: 'Contoso and partners' } }, commandInfo); | ||
assert.strictEqual(actual, true); | ||
}); | ||
|
||
it('fails validation when no option is specified', async () => { | ||
const actual = await command.validate({ options: {} }, commandInfo); | ||
assert.notStrictEqual(actual, true); | ||
}); | ||
|
||
it('updates a displayName of a multitenant organization', async () => { | ||
const patchRequestStub = sinon.stub(request, 'patch').callsFake(async (opts) => { | ||
if (opts.url === 'https://graph.microsoft.com/v1.0/tenantRelationships/multiTenantOrganization') { | ||
return; | ||
} | ||
|
||
throw 'Invalid request'; | ||
}); | ||
|
||
await command.action(logger, { options: { displayName: 'Contoso organization' } }); | ||
assert(patchRequestStub.called); | ||
}); | ||
|
||
it('updates a description of a multitenant organization', async () => { | ||
const patchRequestStub = sinon.stub(request, 'patch').callsFake(async (opts) => { | ||
if (opts.url === 'https://graph.microsoft.com/v1.0/tenantRelationships/multiTenantOrganization') { | ||
return; | ||
} | ||
|
||
throw 'Invalid request'; | ||
}); | ||
|
||
await command.action(logger, { options: { description: 'Contoso and partners' } }); | ||
assert(patchRequestStub.called); | ||
}); | ||
|
||
it('updates displayName and description of a multitenant organization', async () => { | ||
const patchRequestStub = sinon.stub(request, 'patch').callsFake(async (opts) => { | ||
if (opts.url === 'https://graph.microsoft.com/v1.0/tenantRelationships/multiTenantOrganization') { | ||
return; | ||
} | ||
|
||
throw 'Invalid request'; | ||
}); | ||
|
||
await command.action(logger, { options: { displayName: 'Contoso organization', description: 'Contoso and partners' } }); | ||
assert(patchRequestStub.called); | ||
}); | ||
|
||
it('correctly handles API OData error', async () => { | ||
sinon.stub(request, 'patch').rejects({ | ||
error: { | ||
'odata.error': { | ||
code: '-1, InvalidOperationException', | ||
message: { | ||
value: 'Invalid request' | ||
} | ||
} | ||
} | ||
}); | ||
|
||
await assert.rejects(command.action(logger, { options: {} } as any), new CommandError('Invalid request')); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import GlobalOptions from '../../../../GlobalOptions.js'; | ||
import { Logger } from '../../../../cli/Logger.js'; | ||
import request, { CliRequestOptions } from '../../../../request.js'; | ||
import GraphCommand from '../../../base/GraphCommand.js'; | ||
import commands from '../../commands.js'; | ||
|
||
interface CommandArgs { | ||
options: Options; | ||
} | ||
|
||
interface MultitenantOrganization { | ||
createdDateTime?: string; | ||
displayName?: string; | ||
description?: string; | ||
id?: string; | ||
state?: string; | ||
} | ||
|
||
interface Options extends GlobalOptions { | ||
displayName?: string; | ||
description?: string; | ||
} | ||
|
||
class EntraMultitenantSetCommand extends GraphCommand { | ||
public get name(): string { | ||
return commands.MULTITENANT_SET; | ||
} | ||
|
||
public get description(): string { | ||
return 'Updates the properties of a multitenant organization'; | ||
} | ||
|
||
constructor() { | ||
super(); | ||
|
||
this.#initTelemetry(); | ||
this.#initOptions(); | ||
this.#initValidators(); | ||
} | ||
|
||
#initTelemetry(): void { | ||
this.telemetry.push((args: CommandArgs) => { | ||
Object.assign(this.telemetryProperties, { | ||
displayName: typeof args.options.displayName !== 'undefined', | ||
description: typeof args.options.description !== 'undefined' | ||
}); | ||
}); | ||
} | ||
|
||
#initOptions(): void { | ||
this.options.unshift( | ||
{ | ||
option: '-n, --displayName [displayName]' | ||
}, | ||
{ | ||
option: '-d, --description [description]' | ||
} | ||
); | ||
} | ||
|
||
#initValidators(): void { | ||
this.validators.push( | ||
async (args: CommandArgs) => { | ||
if (!args.options.displayName && !args.options.description) { | ||
return 'Specify either displayName or description or both.'; | ||
} | ||
|
||
return true; | ||
} | ||
); | ||
} | ||
|
||
public async commandAction(logger: Logger, args: CommandArgs): Promise<void> { | ||
|
||
const requestOptions: CliRequestOptions = { | ||
url: `${this.resource}/v1.0/tenantRelationships/multiTenantOrganization`, | ||
headers: { | ||
accept: 'application/json;odata.metadata=none' | ||
}, | ||
responseType: 'json', | ||
data: { | ||
description: args.options.description, | ||
displayName: args.options.displayName | ||
} | ||
}; | ||
|
||
try { | ||
await request.patch<MultitenantOrganization>(requestOptions); | ||
} | ||
catch (err: any) { | ||
this.handleRejectedODataJsonPromise(err); | ||
} | ||
} | ||
} | ||
|
||
export default new EntraMultitenantSetCommand(); |