Skip to content

Commit

Permalink
Merge pull request #1095 from amine-mejaouel/create-b2c-tenant
Browse files Browse the repository at this point in the history
Create b2c tenant
  • Loading branch information
ninjarobot authored Feb 19, 2024
2 parents e3e387e + 186fda4 commit 795f695
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 0 deletions.
3 changes: 3 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Release Notes
=============

## 1.8.7
* B2C Tenants: Support for creating B2C Tenants.

## 1.8.6
* Route Server: Custom name support for Route Server Public IP.

Expand Down
60 changes: 60 additions & 0 deletions docs/content/api-overview/resources/b2c-tenant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
title: "B2C Tenant"
date: 2024-02-01T00:00:00+01:00
chapter: false
weight: 1
---

#### Overview
Creates a new B2C tenant, please note that the current implementation only supports the creation of a new B2C tenant.

Usage of this computation expression when a B2C tenant already exists will result in an error, check the [example](#example) for more infos.

#### B2C Tenant Builder Keywords
| Applies To | Keyword | Purpose |
|-|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| B2C Tenant | initial_domain_name | Initial domain name for the B2C tenant as in `initial_domain_name.onmicrosoft.com` |
| B2C Tenant | display_name | Display name for the B2C tenant. |
| B2C Tenant | sku | [SKU](https://learn.microsoft.com/en-us/rest/api/activedirectory/b2c-tenants/list-by-subscription?view=rest-activedirectory-2021-04-01&tabs=HTTP#b2cresourceskuname) for the B2C tenant. |
| B2C Tenant | country_code | Country code defined by two capital letter, for examples check the official [docs](https://learn.microsoft.com/en-us/azure/active-directory-b2c/data-residency] |
| B2C Tenant | data_residency | Data residency for the B2C tenant, for more infos check the official [docs](https://learn.microsoft.com/en-us/azure/active-directory-b2c/data-residency] |
| B2C Tenant | tags | Tags for the B2C tenant. |

#### Example

Basic creation of a B2C tenant, while avoiding having an error when such tenant already exists.

```fsharp
open Farmer
open Farmer.B2cTenant
open Farmer.Builders
open Farmer.Deploy
let initialDomainName = "myb2c"
let myb2c =
b2cTenant {
initial_domain_name initialDomainName
display_name "My B2C"
sku Sku.PremiumP1
country_code "FR"
data_residency B2cDataResidency.Europe
}
let b2cDoesNotExist (initialDomainName: string) =
let output =
Az.AzHelpers.executeAz $"resource list --name '{initialDomainName}.onmicrosoft.com'"
|> snd
not (output.Contains initialDomainName)
let deployment =
arm {
location Location.FranceCentral
add_resources
[
// This allows to avoid having an error when the B2C tenant already exists
if b2cDoesNotExist initialDomainName then
myb2c
]
}
````
46 changes: 46 additions & 0 deletions src/Farmer/Arm/B2cTenant.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[<AutoOpen>]
module Farmer.Arm.B2cTenant

open Farmer

let b2cTenant =
ResourceType("Microsoft.AzureActiveDirectory/b2cDirectories", "2021-04-01")

type B2cDomainName =
| B2cDomainName of string

static member internal Empty = B2cDomainName ""

member this.AsResourceName =
match this with
| B2cDomainName name -> ResourceName name

type B2cTenant =
{
Name: B2cDomainName
DisplayName: string
DataResidency: Location
CountryCode: string
Tags: Map<string, string>
Sku: B2cTenant.Sku
}

interface IArmResource with
member this.ResourceId = accounts.resourceId this.Name.AsResourceName

member this.JsonModel =
{| b2cTenant.Create(this.Name.AsResourceName, this.DataResidency, tags = this.Tags) with
sku =
{|
name = string this.Sku
tier = "A0"
|}
properties =
{|
createTenantProperties =
{|
countryCode = this.CountryCode
displayName = this.DisplayName
|}
|}
|}
83 changes: 83 additions & 0 deletions src/Farmer/Builders/Builders.B2cTenant.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
[<AutoOpen>]
module Farmer.Builders.B2cTenant

open Farmer
open Farmer.Arm
open Farmer.Validation
open Farmer.B2cTenant

type B2cDomainName with

static member FromInitialDomainName initialDomainName =
[ containsOnlyM [ lettersOrNumbers ] ]
|> validate "B2c initial domain name" initialDomainName
|> Result.map (fun x -> B2cDomainName $"{x}.onmicrosoft.com")

type B2cTenantConfig =
{
Name: B2cDomainName
DisplayName: string
DataResidency: Location
CountryCode: string
Sku: Sku
Tags: Map<string, string>
}

interface IBuilder with
member this.ResourceId = b2cTenant.resourceId this.Name.AsResourceName

member this.BuildResources _ =
[
{
B2cTenant.Name = this.Name
DisplayName = this.DisplayName
DataResidency = this.DataResidency
CountryCode = this.CountryCode
Tags = this.Tags
Sku = this.Sku
}
]

type B2cTenantBuilder() =
member _.Yield _ =
{
Name = B2cDomainName.Empty
DisplayName = ""
DataResidency = B2cDataResidency.Europe.Location
CountryCode = "FR"
Sku = Sku.PremiumP1
Tags = Map.empty
}

[<CustomOperation("initial_domain_name")>]
member _.InitialDomainName(state: B2cTenantConfig, name: string) =
{ state with
Name = B2cDomainName.FromInitialDomainName(name).OkValue
}

[<CustomOperation("display_name")>]
member _.DisplayName(state: B2cTenantConfig, displayName: string) =
{ state with DisplayName = displayName }

[<CustomOperation("sku")>]
member _.Sku(state: B2cTenantConfig, sku: Sku) = { state with Sku = sku }

/// Data residency location as described in: https://learn.microsoft.com/en-us/azure/active-directory-b2c/data-residency#data-residency
[<CustomOperation("data_residency")>]
member _.DataResidency(state: B2cTenantConfig, b2cDataResidency: B2cDataResidency) =
{ state with
DataResidency = b2cDataResidency.Location
}

/// Country Code defined by two capital letters (example: FR), as described in: https://learn.microsoft.com/en-us/azure/active-directory-b2c/data-residency#data-residency
[<CustomOperation("country_code")>]
member _.CountryCode(state: B2cTenantConfig, countryCode: string) =
{ state with CountryCode = countryCode }

interface ITaggable<B2cTenantConfig> with
member _.Add state tags =
{ state with
Tags = state.Tags |> Map.merge tags
}

let b2cTenant = B2cTenantBuilder()
22 changes: 22 additions & 0 deletions src/Farmer/Common.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2301,6 +2301,28 @@ module ContainerService =
| Kubenet -> "kubenet"
| AzureCni -> "azure"

module B2cTenant =
type Sku =
| PremiumP1
| PremiumP2
| Standard

/// Check official documentation for more details: https://learn.microsoft.com/en-us/azure/active-directory-b2c/data-residency#data-residency
type B2cDataResidency =
| UnitedStates
| Europe
| AsiaPacific
| Japan
| Australia

member this.Location =
match this with
| UnitedStates -> Location "United States"
| Europe -> Location "Europe"
| AsiaPacific -> Location "Asia Pacific"
| Japan -> Location "Japan"
| Australia -> Location "Australia"

module Redis =
type Sku =
| Basic
Expand Down
2 changes: 2 additions & 0 deletions src/Farmer/Farmer.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,12 @@
<Compile Include="Arm/LogicApps.fs" />
<Compile Include="Arm/OperationsManagement.fs" />
<Compile Include="Arm\Webhook.fs" />
<Compile Include="Arm\B2cTenant.fs" />
<Compile Include="IdentityExtensions.fs" />
<Compile Include="Builders/Extensions.fs" />
<Compile Include="Builders\Builders.ActionGroup.fs" />
<Compile Include="Builders\Builders.AutoscaleSettings.fs" />
<Compile Include="Builders\Builders.B2cTenant.fs" />
<Compile Include="Builders/Builders.UserAssignedIdentity.fs" />
<Compile Include="Builders/Builders.ResourceGroup.fs" />
<Compile Include="Builders/Builders.LogAnalytics.fs" />
Expand Down
1 change: 1 addition & 0 deletions src/Tests/AllTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ let allTests =
AzCli.tests
AutoscaleSettings.tests
AzureFirewall.tests
B2cTenant.tests
Bastion.tests
BingSearch.tests
Cdn.tests
Expand Down
70 changes: 70 additions & 0 deletions src/Tests/B2cTenant.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
module B2cTenant

open Expecto
open Farmer
open Farmer.B2cTenant
open Farmer.Builders
open Newtonsoft.Json.Linq

let tests =
testList
"B2c tenant tests"
[
test "B2c tenant should generate the expected arm template" {
let deployment =
arm {
location Location.FranceCentral

add_resources
[
b2cTenant {
initial_domain_name "myb2c"
display_name "My B2C tenant"
sku Sku.PremiumP1
country_code "FR"
data_residency B2cDataResidency.Europe
}
]
}

let jobj = deployment.Template |> Writer.toJson |> JObject.Parse

let generatedTemplate = jobj.SelectToken("resources[0]")

Expect.equal
(generatedTemplate.SelectToken("apiVersion").ToString())
"2021-04-01"
"Invalid ARM template api version"

Expect.equal
(generatedTemplate.SelectToken("type").ToString())
"Microsoft.AzureActiveDirectory/b2cDirectories"
"Invalid ARM template type"

Expect.equal
(generatedTemplate.SelectToken("name").ToString())
"myb2c.onmicrosoft.com"
"`name` should match <initial_domain_name>.onmicrosoft.com"

Expect.equal
(generatedTemplate
.SelectToken("properties.createTenantProperties.displayName")
.ToString())
"My B2C tenant"
"Invalid display name"

Expect.equal
(generatedTemplate.SelectToken("location").ToString())
"europe"
"`location` should match with the provided `data_residency`"

Expect.equal
(generatedTemplate
.SelectToken("properties.createTenantProperties.countryCode")
.ToString())
"FR"
"Invalid country code"

Expect.equal (generatedTemplate.SelectToken("sku.name").ToString()) "PremiumP1" "Invalid sku"
}
]
1 change: 1 addition & 0 deletions src/Tests/Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<Compile Include="AutoscaleSettings.fs" />
<Compile Include="AvailabilityTests.fs" />
<Compile Include="AzureFirewall.fs" />
<Compile Include="B2cTenant.fs" />
<Compile Include="DiagnosticSettings.fs" />
<Compile Include="Disk.fs" />
<Compile Include="Common.fs" />
Expand Down

0 comments on commit 795f695

Please sign in to comment.