Summary

Created for the Azure Practice @ contino

Automatic Processing of EA Subscription Requests via Github Actions, Terraform and Azure

https://asciinema.org/a/omOkMnM3tHr4HN7ycNiaEsuws

08th May 2023

Looking for people to collaborate on testing and functionally refining the current version for release

  1. test workflow automations and add/remove scenarios

  2. Clean tenant and Prepare Migration

  3. TBC


![asciicast](https://asciinema.org/a/omOkMnM3tHr4HN7ycNiaEsuws.svg)](https://asciinema.org/a/omOkMnM3tHr4HN7ycNiaEsuws)

Architecture

  • Enterprise Account

    • Billing Account 87561154

      • Enrolment Account 311200

        • Subscription Creator role

      • Enrolment Account 340292

        • Subscription Creator role

more detail

Previously via https://ea.microsoft.com, billing accounts

, billing departments, enrolment accounts are all managed under the Cost Management and Billing section under https://portal.azure.com if you are assigned the correct role

In order to access the features, get assigned the Enterprise Administrator role directly under the Cost Management and Billing IAM section

more detail

in order to allow EA to recognise the squad0 tenant as part of contino, we need to ensure that we enter the tenant into the tenant registration uner https://partner.microsoft.com under settings

more detail

We need to create service principals for each tenant and assign them the roles to be able to create subscriptions and role assignments up to Owner of the new subscriptions

The EA Subscription Deployment service principals will also have scope across the tenant to create and configure a vended service principal when creating a new subscription

  • Github Org github..com/contino

    • azure_practice_management_iac_poc

    • mgmt_subscription

    • azure_iac_squad0_lbg_openai_poc

more detail

As part of the vending process, we have the option to enable automation - which simply means that we provide the resources to allow the user to deliver IaC from a repository into their subscription without additional support

To facilitate this, if automation is selected, we create

  • a service principal

    • owned by the subscription owner

    • with role Owner across the tenant

  • a GitHub repository under org contino-squad0 github org

    • secret entries for SP credentials (allowing the user to use the SP without creating SP or Credentials)

      • ARM_TENANT_ID

      • ARM_CLIENT_ID

      • ARM_CLIENT_SECRET

      • ARM_SUBSCRIPTION_ID

      • REPO_GH_TOKEN (admin token for the repo)


Configuration

Enterprise Account Preparation

  • create a new Account under the Billing Account for each tenant
    • for continohq, the owner email must be under @contino.io
    • for squad0 , the owner email must be @squad0.onmicrosoft.com

Service Principal and Enrolment Role Assignment

  • Create Service Principal script
    • create service principal
    • create app
    • retrieve objectId for create applications
    • assign SP to the enrolment account

https://learn.microsoft.com/en-us/rest/api/billing/2019-10-01-preview/billing-accounts/list?tabs=HTTP

https://learn.microsoft.com/en-us/rest/api/billing/2019-10-01-preview/billing-role-definitions/get-by-billing-account?tabs=HTTP

Primary

Production & Platform

continohq| 538cf6fd-f5d4-4451-8e4a-88c34f2f2619

87561154

311200 - paul.kelleher@contino.io

as continohq global_administrotor using a user account

  • create new account with owner email @contino.io

we can now create and associate service principal

  • create ea_continohq_sp.sh
export body=$(cat <<EOF
{
  "properties": {
    "principalId": "ea1dc3e5-01c6-4c4f-9fae-9ca7fe80dd66",
    "principalTenantId": "538cf6fd-f5d4-4451-8e4a-88c34f2f2619",
    "roleDefinitionId": "/providers/Microsoft.Billing/billingAccounts/87561154/enrollmentAccounts/311200/billingRoleDefinitions/a0bcee42-bf30-4d1b-926a-48d21664ef71"
  }
}
EOF)
az ad sp create-for-rbac --name {principalname} --role Owner --scope /
az ad sp credential reset --id 1c3fba73-1ed3-4e86-91f2-a3eff03b148d --query 'password' -o tsv

az rest --method put --url "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/87561154/enrollmentAccounts/311200/billingRoleAssignments/9dad08c2-69a3-4d47-85bd-1cdba1408402?api-version=2019-10-01-preview" --body "${body}"

ensure to generate a new valid GUID to replace 9dad08c2-69a3-4d47-85bd-1cdba1408402 zas this is the “name” odf your role assignment

output

{
  "id": "/providers/Microsoft.Billing/billingAccounts/87561154/enrollmentAccounts/340292/billingRoleAssignments/9dfd08c2-69a3-4d47-85bd-1cdba1408402",
  "name": "9dfd08c2-69a3-4d47-85bd-1cdba1408402",
  "properties": {
    "createdByPrincipalId": "93c688a9-1dc1-455e-8659-a87167beee7e",
    "createdByPrincipalPuid": "100320029C35282E",
    "createdByPrincipalTenantId": "2fc87606-f7b2-4dc1-81a0-71e4dd53584d",
    "createdByUserEmailAddress": "adm-pau.kelleher@squad0.onmicrosoft.com",
    "createdOn": "2023-05-05T12:51:43.0011832+00:00",
    "modifiedByPrincipalId": "93c688a9-1dc1-455e-8659-a87167beee7e",
    "modifiedByPrincipalPuid": "100320029C35282E",
    "modifiedByPrincipalTenantId": "2fc87606-f7b2-4dc1-81a0-71e4dd53584d",
    "modifiedByUserEmailAddress": "adm-pau.kelleher@squad0.onmicrosoft.com",
    "modifiedOn": "2023-05-05T12:51:43.0011868+00:00",
    "principalId": "142a88cc-1cfa-47c7-b8c3-a85f7da33041",
    "principalTenantId": "2fc87606-f7b2-4dc1-81a0-71e4dd53584d",
    "roleDefinitionId": "/providers/Microsoft.Billing/billingAccounts/87561154/enrollmentAccounts/340292/billingRoleDefinitions/a0bcee42-bf30-4d1b-926a-48d21664ef71",
    "scope": "/providers/Microsoft.Billing/billingAccounts/87561154/enrollmentAccounts/340292",
    "userAuthenticationType": "Organization"
  },
  "type": "Microsoft.Billing/billingRoleAssignments"
}

Secondary

Internal Engineering

squad0 | 2fc87606-f7b2-4dc1-81a0-71e4dd53584d

as continohq global_administrotor using a user account

  • create new account

we can now create and associate service princ

  • create ea_squad0_sp.sh
export body=$(cat <<EOF
{
  "properties": {
    "principalId": "63a6ec65-074f-47c0-824e-54d178d5e00a",
    "principalTenantId": "2fc87606-f7b2-4dc1-81a0-71e4dd53584d",
    "roleDefinitionId": "/providers/Microsoft.Billing/billingAccounts/87561154/enrollmentAccounts/311200/billingRoleDefinitions/a0bcee42-bf30-4d1b-926a-48d21664ef71"
   }
}
EOF)


az rest --method put --url "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/87561154/enrollmentAccounts/340492/billingRoleAssignments/9dad08c3-69a3-4d47-85bd-1cdba1408402?api-version=2019-10-01-preview" --body "${body}"

https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-convert-app-to-be-multi-tenant#update-registration-to-be-multi-tenant:~:text=guide%20homepage.-,Update%20registration%20to%20be%20multi%2Dtenant,-By%20default%2C%20web

ensure to generate a new valid GUID to replace 9dad08c2-69a3-4d47-85bd-1cdba1408402 zas this is the “name” odf your role assignment

output

{
  "id": "/providers/Microsoft.Billing/billingAccounts/87561154/enrollmentAccounts/340292/billingRoleAssignments/9dfd08c2-69a3-4d47-85bd-1cdba1408402",
  "name": "9dfd08c2-69a3-4d47-85bd-1cdba1408402",
  "properties": {
    "createdByPrincipalId": "93c688a9-1dc1-455e-8659-a87167beee7e",
    "createdByPrincipalPuid": "100320029C35282E",
    "createdByPrincipalTenantId": "2fc87606-f7b2-4dc1-81a0-71e4dd53584d",
    "createdByUserEmailAddress": "adm-pau.kelleher@squad0.onmicrosoft.com",
    "createdOn": "2023-05-05T12:51:43.0011832+00:00",
    "modifiedByPrincipalId": "93c688a9-1dc1-455e-8659-a87167beee7e",
    "modifiedByPrincipalPuid": "100320029C35282E",
    "modifiedByPrincipalTenantId": "2fc87606-f7b2-4dc1-81a0-71e4dd53584d",
    "modifiedByUserEmailAddress": "adm-pau.kelleher@squad0.onmicrosoft.com",
    "modifiedOn": "2023-05-05T12:51:43.0011868+00:00",
    "principalId": "142a88cc-1cfa-47c7-b8c3-a85f7da33041",
    "principalTenantId": "2fc87606-f7b2-4dc1-81a0-71e4dd53584d",
    "roleDefinitionId": "/providers/Microsoft.Billing/billingAccounts/87561154/enrollmentAccounts/340292/billingRoleDefinitions/a0bcee42-bf30-4d1b-926a-48d21664ef71",
    "scope": "/providers/Microsoft.Billing/billingAccounts/87561154/enrollmentAccounts/340292",
    "userAuthenticationType": "Organization"
  },
  "type": "Microsoft.Billing/billingRoleAssignments"
}

AAD Configuration

Providers

Once the configuration has been completed, we can create the providers for terraform to start deploying subscriptions

provider "azurerm" {
 features {}

 skip_provider_registration = false
   storage_use_azuread = true
   tenant_id = "2fc87606-f7b2-4dc1-81a0-71e4dd53584d"
   client_id = "b8878837-d11b-4e42-ab2b-a84f0e838875"
   client_secret= ""
   subscription_id="8d6ed91d-a8e7-4171-a72b-42fd57075f9c"
 alias="contino"
}

provider "azuread" {
  tenant_id = "2fc87606-f7b2-4dc1-81a0-71e4dd53584d"
   client_id = "b8878837-d11b-4e42-ab2b-a84f0e838875"
   client_secret= ""
}

provider "github" {
    token = var.org_gh_token
    owner = "contino" # workaround due to not having perms to create in contino!
}
provider "azurerm" {
 features {}

 skip_provider_registration = false
 storage_use_azuread = true
 #tenant_id = "2fc87606-f7b2-4dc1-81a0-71e4dd53584d"
 #client_id = "b8878837-d11b-4e42-ab2b-a84f0e838875"
 #client_secret= "hf68Q~2OAq.S1jlOiM~Xn7jjNrfa-4ayccCPXc~S"
 #subscription_id="8d6ed91d-a8e7-4171-a72b-42fd57075f9c"
 alias="s0"
}

provider "azuread" {
   tenant_id = "2fc87606-f7b2-4dc1-81a0-71e4dd53584d"
   client_id = "b8878837-d11b-4e42-ab2b-a84f0e838875"
   client_secret= ""
}

provider "github" {
    token = var.org_gh_token
    owner = "contino" # workaround due to not having perms to create in contino!
}

Operation

In order to deploy a new subscription, the repository containing the automated-workflow will identify changes when updated into the main branch of https://github.com/contino/azure_practice_management_iac_poc

  • currently there is no enforced branch protection.

  • the repository also contains the infrastructure deployments for management which - as they are technical operations requiring understanding of the possibly impact of changes - require manual plan/apply

to add a new EA subscription

git clone https://github.com/contino/azure_practice_management_iac_poc
cd azure_practice_management_iac_poc
git checkout -b <branch_name>
cp ~/my_local.configurations/ea_siubscriptions/user_name_squad0.json ./lzeasubscriptionvending/subscription_configurations/
git add ./lzeasubscriptionvending/subscription_configurations/user_name_squad0.json
git commit -m "user_name_squad0 config"
git push -u origin <branch name>
gh pr create --fill

your pull request will run terraform plan against the new configuration so you can ensure it is valid

the subscription config json file

{
  "tenant" : "contino",

  "owner" : "paul.kelleher",
  "email" : "paul.kelleher@contino.io", 
  "purpose" : "ea_testing_s0",
  "alias" : "ea_testing",
  "sub_name" : "ea_testing_contino",
  "workload": "DevTest",

  "apply_budget" : true,
  "management_group" : "UK",
  
  "create_automation_repo" : true,
  "github_repository": "new",
  "github_username": "pknw1",

  "template_repo_org" : "pknw1",
  "template_repo" : "terraform-boilerplate",
  "service_principal_name" : "new"

  }

config file format
{
  "tenant"                 : "<contino|squad0>",                     #  selects the tenant to deply into 
  "owner"                  : "<any string>",                         #  requesting users name
  "email"                  : "<valid email @contino.io>",            #  requesting users email - will be used as owner email
  "purpose"                : "ea_testing_s0",                        #  for housekeeping and management
  "alias"                  : "<must be unique subscription alias>",  #  
  "subscription_name"      : "<must be unique repository name>",     #  if not entered, a programmatic name wll be created
  "workload"               : "<Production|DevTest>",                 #  for Contino must be Production for all others DevTest
  "apply_budget"           : true,                                   #  skip adding rhe 60GBP budget
  "github_repository"      : <""|"new"|"<existing>">,                #  if "new" creates new, or specify a repo to use existing
  "github_username"        : "pknw1",                                #  github username to add as a new repo owner
  "github_repo_org"        : "<contino|contino-squad0>",             #  specify the target org for the repo
  "template_repo_org"      : "<contino|contino-squad0|pknw1>",       #  specify the target org for the repo 
  "template_repo"          : "terraform-boilerplate",                # 
  "service_principal_name" : <""|"new"|"<existing>">                 # if "new" creates new, or specify existing sp (must be owner)
  }

squad0 tenant - automation enabled
{
  "tenant"            : "squad0",

  "owner"             : "paul.kelleher",
  "email"             : "paul.kelleher@contino.io", 
  "purpose"           : "lbg_open_ai",
  "alias"             : "dev",
  "subscription_name" : "squad0_lbg_openai_poc",
  "workload"          : "DevTest",

  "apply_budget"      : true,
  
  "github_repository" : "new",
  "github_username"   : "pknw1",

  "template_repo_org"   : "pknw1",
  "template_repo"       : "terraform-boilerplate",
  "service_principal_name" : "new",
  "enrollment_account" : ""
  }

continoi tenant - automation disabled
{
  "tenant"            : "contino",

  "owner"             : "paul.kelleher",
  "email"             : "paul.kelleher@contino.io", 
  "purpose"           : "lbg_open_ai",
  "alias"             : "dev",
  "subscription_name" : "continohq_lbg_openai_poc",
  "workload"          : "Production",

  "apply_budget"      : true,
  
  "github_repository" : "",
  "github_username"   : "",

  "template_repo_org"   : "pknw1",
  "template_repo"       : "terraform-boilerplate",
  "service_principal_name" : "",
  "enrollment_account" : ""
  }

Deployment

GitHub Workflow

Compile and Process config.json files

Locals

Variable

Details

json_files

json_data

user_list

squad0_users

locals {
    json_files = fileset(path.module, "subscription_configurations/*json")   
    json_data  = [ for f in local.json_files : jsondecode(file("${f}")) ] 
    user_list  = distinct([ for f in local.json_data : f.email ] )
    squad0_users = distinct(local.user_list) 
}

Computed continohq->squad0 tenant accounts tf module

So that users can access the squad0 resources using their continohq tenant account, a de-deplicated list of valid users is generated from the available configuration files

  • If the user already exists, we do not re-add
  • if the user no longer has any resources, so drops from the list, the guest account in squad0 is removed
  • if the user is new, we automatically invite the user, accept the invite and assign them appropriate roles under the squad0 tenant
Squad0 users

Variable

Details

user_email

module "squad0_users" {
providers = {
    azurerm.s0=azurerm.s0
    azurerm.contino=azurerm.contino
    azuread.s0=azuread.s0
    azuread.contino=azuread.contino
}

source = "./modules/aad_create_guest_users"
  count = length(local.squad0_users)
  user_email = local.squad0_users[count.index]
}

EA Subscription Deployment tf module

azurerm_ea_subscription

Variable

Default

Details

tenant

squad0

from config only

owner

""

from config

purpose

""

from config

alias

""

from config

email

""

from config

subscription_name

`owner_workload

workload

DevTest

contino = Production

aquad0 = DevTEst

apply_budget

true

service_principal_name

tenent_subscription_id

“" = nothing

new = requests a new sp

exiasting = uses existibg

github_username

""

github_repository

""

“" = nothing

new = requests a new sp

exiasting = uses existibg

enrollment_account

311200

Optional Components Install

Azure Subscription Budget true | false

Automatically, we add a budget of 60GBP onto then subscription with pre-configured email alerts to the subscription and practice owner for exceeding.

The budget can be manually changed by users

Subscription Level Automation Service Principal "" | "new" | "existing_sp"

"" - leaving the setting blank is a noop

new - Most users will require a service principal at some point, so we can automatically create a new service principal and assign it the Owner role over the EA Subscription - itr will not be able to register new apps or service principals as it will be assigned only Reader role on AD

"existing - Any role specified that exists will have the details retrieved and used but the subscription owner must also own that service principal

GitHub IaC Repository "" | "new" |"<existing_repo_url>"

"" - leaving the setting blank is a noop

new - we create a new repository under a github org depending on the target tenant

  • using tenant contino implies that the deployment is considered business applicable and as such uses

  • using squad0 tenant implies any non-production usage and uses

    • GitHub org contino-squad0

    • Workload Devtest [ms-azr-0148p] [Enterprise DevTest]

"existing" - Any role specified that exists will have the details retrieved and used but the subscription owner must also own that service principal

module "azurerm_ea_subscription" {
  source = "./modules/ea_subscription_vending_multitennant"

    for_each = { for f in local.json_data : f.subscription_name => f }

    providers = {
        azurerm.s0=azurerm.s0
        azurerm.contino=azurerm.contino
        azurerm.continovend=azurerm.continovend
        azuread.s0=azuread.s0
        azuread.contino=azuread.contino
    }
    tenant                  = each.value.tenant
    owner                   = each.value.owner
    purpose                 = each.value.purpose
    email                   = each.value.email
    alias                   = each.value.alias
    subscription_name       = each.value.subscription_name  
    workload                = each.value.workload      
    apply_budget            = each.value.apply_budget
    service_principal_name  = each.value.service_principal_name
    github_username         = each.value.github_username
    github_repository       = each.value.github_repository
    enrollment_account      = each.value.enrollment_account

    depends_on = [module.squad0_users]
}

complet
locals {
    json_files = fileset(path.module, "subscription_configurations/*json")   
    json_data  = [ for f in local.json_files : jsondecode(file("${f}")) ] 
    user_list  = distinct([ for f in local.json_data : f.email ] )
    squad0_users = distinct(local.user_list) 
}


module "squad0_users" {
providers = {
    azurerm.s0=azurerm.s0
    azurerm.contino=azurerm.contino
    azuread.s0=azuread.s0
    azuread.contino=azuread.contino

}

source = "./modules/aad_create_guest_users"
    count = length(local.squad0_users)
    user_email = local.squad0_users[count.index]


}


module "azurerm_ea_subscription" {
#    source="git@github.com:contino/terraform-azurerm-easubscription"
source = "./modules/ea_subscription_vending_multitennant"

    for_each = { for f in local.json_data : f.subscription_name => f }

    providers = {
        azurerm.s0=azurerm.s0
        azurerm.contino=azurerm.contino
        azurerm.continovend=azurerm.continovend
        azuread.s0=azuread.s0
        azuread.contino=azuread.contino
    }
    tenant                  = each.value.tenant
    owner                   = each.value.owner
    purpose                 = each.value.purpose
    email                   = each.value.email
    alias                   = each.value.alias
    subscription_name       = each.value.subscription_name  
    workload                = each.value.workload      
    apply_budget            = each.value.apply_budget
    service_principal_name  = each.value.service_principal_name
    github_username         = each.value.github_username
    github_repository       = each.value.github_repository
    enrollment_account      = each.value.enrollment_account

    depends_on = [module.squad0_users]
}


{
  "tenant" : "contino",

  "owner" : "paul.kelleher",
  "email" : "paul.kelleher@contino.io", 
  "purpose" : "ea_testing_s0",
  "alias" : "ea_testing",
  "sub_name" : "ea_testing_contino",
  "workload": "DevTest",

  "apply_budget" : true,
  "management_group" : "UK",
  
  "create_automation_repo" : true,
  "github_repository": "new",
  "github_username": "pknw1",

  "template_repo_org" : "pknw1",
  "template_repo" : "terraform-boilerplate",
  "service_principal_name" : "new"

  }

Variable

Default

Details

Details

tenant

squad0

squad0

contino

purpose

""

<string>

alias

""

<string>

email

""

<string>

owner

""

<string>

subscription_name

owner_workload

<string>

workload

DevTest

Production

DevTest

apply_budget

true

true

false

service_principal_name

tenent_subs_id

“"

nothing

new

new = requests a new sp

exiasting

exiasting = uses existibg

github_username

""

github_repository

""

“"

new

requests a new sp

exiasting

uses existibg

enrollment_account

311200


Sample Configurations

{
  "tenant"            : "squad0",

  "owner"             : "test.two",
  "email"             : "paul.kelleher@contino.io", 
  "purpose"           : "purpose_tag",
  "alias"             : "alias_tag",
  "subscription_name" : "subscription_name_tag",
  "workload"          : "DevTest",

  "apply_budget"      : true,
  
  "github_repository" : "new",
  "github_username"   : "pknw1",

  "template_repo_org"   : "pknw1",
  "template_repo"       : "terraaform-boilerplate",
  "service_principal_name" : "new",
  "enrollment_account" : ""
  }

Full Deployment

output
  # module.azurerm_ea_subscription["subscription_name_tag"].azurerm_consumption_budget_subscription.s0[0] will be created
  + resource "azurerm_consumption_budget_subscription" "s0" {
      + amount          = 60
      + etag            = (known after apply)
      + id              = (known after apply)
      + name            = "subscription_name_tag"
      + subscription_id = (known after apply)
      + time_grain      = "Monthly"

      + notification {
          + contact_emails = [
              + "paul.kelleher@contino.io",
            ]
          + contact_groups = []
          + contact_roles  = [
              + "Owner",
            ]
          + enabled        = true
          + operator       = "EqualTo"
          + threshold      = 90
          + threshold_type = "Actual"
        }
      + notification {
          + contact_emails = [
              + "paul.kelleher@contino.io",
            ]
          + contact_groups = []
          + contact_roles  = []
          + enabled        = false
          + operator       = "GreaterThan"
          + threshold      = 100
          + threshold_type = "Forecasted"
        }

      + time_period {
          + end_date   = (known after apply)
          + start_date = (known after apply)
        }
    }

  # module.azurerm_ea_subscription["subscription_name_tag"].azurerm_role_assignment.subscriptiption[0] will be created
  + resource "azurerm_role_assignment" "subscriptiption" {
      + id                               = (known after apply)
      + name                             = (known after apply)
      + principal_id                     = "79ff1f2c-1133-416d-bed5-d8f83fae55f3"
      + principal_type                   = (known after apply)
      + role_definition_id               = (known after apply)
      + role_definition_name             = "Owner"
      + scope                            = (known after apply)
      + skip_service_principal_aad_check = (known after apply)
    }

  # module.azurerm_ea_subscription["subscription_name_tag"].azurerm_subscription.create_s0[0] will be created
  + resource "azurerm_subscription" "create_s0" {
      + alias             = (known after apply)
      + billing_scope_id  = "/providers/Microsoft.Billing/billingAccounts/87561154/enrollmentAccounts/340292"
      + id                = (known after apply)
      + subscription_id   = (known after apply)
      + subscription_name = "subscription_name_tag"
      + tenant_id         = (known after apply)
      + workload          = "DevTest"
    }

  # module.azurerm_ea_subscription["subscription_name_tag"].module.iac[0].data.azurerm_subscription.create_s0[0] will be read during apply
  # (config refers to values not yet known)
 <= data "azurerm_subscription" "create_s0" {
      + display_name          = (known after apply)
      + id                    = (known after apply)
      + location_placement_id = (known after apply)
      + quota_id              = (known after apply)
      + spending_limit        = (known after apply)
      + state                 = (known after apply)
      + subscription_id       = (known after apply)
      + tags                  = (known after apply)
      + tenant_id             = (known after apply)
    }

  # module.azurerm_ea_subscription["subscription_name_tag"].module.iac[0].data.github_actions_public_key.public_key[0] will be read during apply
  # (depends on a resource or a module with changes pending)
 <= data "github_actions_public_key" "public_key" {
      + id         = (known after apply)
      + key        = (known after apply)
      + key_id     = (known after apply)
      + repository = "azure_iac_subscription_name_tag"
    }

  # module.azurerm_ea_subscription["subscription_name_tag"].module.iac[0].azuread_application.sp_s0[0] will be created
  + resource "azuread_application" "sp_s0" {
      + app_role_ids                = (known after apply)
      + application_id              = (known after apply)
      + disabled_by_microsoft       = (known after apply)
      + display_name                = (known after apply)
      + id                          = (known after apply)
      + logo_url                    = (known after apply)
      + oauth2_permission_scope_ids = (known after apply)
      + object_id                   = (known after apply)
      + owners                      = [
          + "79ff1f2c-1133-416d-bed5-d8f83fae55f3",
        ]
      + prevent_duplicate_names     = false
      + publisher_domain            = (known after apply)
      + sign_in_audience            = "AzureADMyOrg"
      + tags                        = (known after apply)
      + template_id                 = (known after apply)
    }

  # module.azurerm_ea_subscription["subscription_name_tag"].module.iac[0].azuread_service_principal.sp_s0[0] will be created
  + resource "azuread_service_principal" "sp_s0" {
      + account_enabled              = true
      + app_role_assignment_required = false
      + app_role_ids                 = (known after apply)
      + app_roles                    = (known after apply)
      + application_id               = (known after apply)
      + application_tenant_id        = (known after apply)
      + display_name                 = (known after apply)
      + homepage_url                 = (known after apply)
      + id                           = (known after apply)
      + logout_url                   = (known after apply)
      + oauth2_permission_scope_ids  = (known after apply)
      + oauth2_permission_scopes     = (known after apply)
      + object_id                    = (known after apply)
      + owners                       = [
          + "79ff1f2c-1133-416d-bed5-d8f83fae55f3",
        ]
      + redirect_uris                = (known after apply)
      + saml_metadata_url            = (known after apply)
      + service_principal_names      = (known after apply)
      + sign_in_audience             = (known after apply)
      + tags                         = (known after apply)
      + type                         = (known after apply)
    }

  # module.azurerm_ea_subscription["subscription_name_tag"].module.iac[0].azurerm_role_assignment.subscription_s0[0] will be created
  + resource "azurerm_role_assignment" "subscription_s0" {
      + id                               = (known after apply)
      + name                             = (known after apply)
      + principal_id                     = (known after apply)
      + principal_type                   = (known after apply)
      + role_definition_id               = (known after apply)
      + role_definition_name             = "Owner"
      + scope                            = (known after apply)
      + skip_service_principal_aad_check = (known after apply)
    }

  # module.azurerm_ea_subscription["subscription_name_tag"].module.iac[0].github_actions_secret.ARM_CLIENT_ID[0] will be created
  + resource "github_actions_secret" "ARM_CLIENT_ID" {
      + created_at      = (known after apply)
      + id              = (known after apply)
      + plaintext_value = (sensitive value)
      + repository      = "azure_iac_subscription_name_tag"
      + secret_name     = "ARM_CLIENT_ID"
      + updated_at      = (known after apply)
    }

  # module.azurerm_ea_subscription["subscription_name_tag"].module.iac[0].github_actions_secret.ARM_SUBSCRIPTION_ID[0] will be created
  + resource "github_actions_secret" "ARM_SUBSCRIPTION_ID" {
      + created_at      = (known after apply)
      + id              = (known after apply)
      + plaintext_value = (sensitive value)
      + repository      = "azure_iac_subscription_name_tag"
      + secret_name     = "ARM_SUBSCRIPTION_ID"
      + updated_at      = (known after apply)
    }

  # module.azurerm_ea_subscription["subscription_name_tag"].module.iac[0].github_actions_secret.ARM_TENANT_ID[0] will be created
  + resource "github_actions_secret" "ARM_TENANT_ID" {
      + created_at      = (known after apply)
      + id              = (known after apply)
      + plaintext_value = (sensitive value)
      + repository      = "azure_iac_subscription_name_tag"
      + secret_name     = "ARM_TENANT_ID"
      + updated_at      = (known after apply)
    }

  # module.azurerm_ea_subscription["subscription_name_tag"].module.iac[0].github_actions_secret.org_gh_token[0] will be created
  + resource "github_actions_secret" "org_gh_token" {
      + created_at  = (known after apply)
      + id          = (known after apply)
      + repository  = "azure_iac_subscription_name_tag"
      + secret_name = "org_gh_token"
      + updated_at  = (known after apply)
    }

  # module.azurerm_ea_subscription["subscription_name_tag"].module.iac[0].github_repository.repo[0] will be created
  + resource "github_repository" "repo" {
      + allow_auto_merge            = false
      + allow_merge_commit          = true
      + allow_rebase_merge          = true
      + allow_squash_merge          = true
      + archived                    = false
      + auto_init                   = true
      + default_branch              = (known after apply)
      + delete_branch_on_merge      = false
      + description                 = "Automation repository for subscription_name_tag"
      + etag                        = (known after apply)
      + full_name                   = (known after apply)
      + git_clone_url               = (known after apply)
      + html_url                    = (known after apply)
      + http_clone_url              = (known after apply)
      + id                          = (known after apply)
      + merge_commit_message        = "PR_TITLE"
      + merge_commit_title          = "MERGE_MESSAGE"
      + name                        = "azure_iac_subscription_name_tag"
      + node_id                     = (known after apply)
      + private                     = (known after apply)
      + repo_id                     = (known after apply)
      + squash_merge_commit_message = "COMMIT_MESSAGES"
      + squash_merge_commit_title   = "COMMIT_OR_PR_TITLE"
      + ssh_clone_url               = (known after apply)
      + svn_url                     = (known after apply)
      + visibility                  = "private"

      + template {
          + include_all_branches = false
          + owner                = "pknw1"
          + repository           = "terraform-boilerplate"
        }
    }

  # module.azurerm_ea_subscription["test_subscription_1"].module.iac[0].github_actions_secret.ARM_CLIENT_ID has moved to module.azurerm_ea_subscription["test_subscription_1"].module.iac[0].github_actions_secret.ARM_CLIENT_ID[0]
    resource "github_actions_secret" "ARM_CLIENT_ID" {
        id              = "azure_iac_test_subscription_1:ARM_CLIENT_ID"
        # (5 unchanged attributes hidden)
    }

  # module.azurerm_ea_subscription["test_subscription_1"].module.iac[0].github_actions_secret.ARM_SUBSCRIPTION_ID has moved to module.azurerm_ea_subscription["test_subscription_1"].module.iac[0].github_actions_secret.ARM_SUBSCRIPTION_ID[0]
    resource "github_actions_secret" "ARM_SUBSCRIPTION_ID" {
        id              = "azure_iac_test_subscription_1:ARM_SUBSCRIPTION_ID"
        # (5 unchanged attributes hidden)
    }

  # module.azurerm_ea_subscription["test_subscription_1"].module.iac[0].github_actions_secret.ARM_TENANT_ID has moved to module.azurerm_ea_subscription["test_subscription_1"].module.iac[0].github_actions_secret.ARM_TENANT_ID[0]
    resource "github_actions_secret" "ARM_TENANT_ID" {
        id              = "azure_iac_test_subscription_1:ARM_TENANT_ID"
        # (5 unchanged attributes hidden)
    }

  # module.azurerm_ea_subscription["test_subscription_1"].module.iac[0].github_actions_secret.org_gh_token has moved to module.azurerm_ea_subscription["test_subscription_1"].module.iac[0].github_actions_secret.org_gh_token[0]
    resource "github_actions_secret" "org_gh_token" {
        id          = "azure_iac_test_subscription_1:org_gh_token"
        # (4 unchanged attributes hidden)
    }

Plan: 11 to add, 0 to change, 0 to destroy.
{
  "tenant"            : "squad0",

  "owner"             : "test.two",
  "email"             : "paul.kelleher@contino.io", 
  "purpose"           : "",
  "alias"             : "",
  "subscription_name" : "minimal_options",
  "workload"          : "DevTest",

  "apply_budget"      : false,
  
  "github_repository" : "",
  "github_username"   : "",

  "template_repo_org"   : "",
  "template_repo"       : "",
  "service_principal_name" : "",
  "enrollment_account" : ""
  }
output
erraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following
symbols:
  + create

Terraform will perform the following actions:

  # module.azurerm_ea_subscription["minimal_options"].azurerm_consumption_budget_subscription.s0[0] will be created
  + resource "azurerm_consumption_budget_subscription" "s0" {
      + amount          = 60
      + etag            = (known after apply)
      + id              = (known after apply)
      + name            = "minimal_options"
      + subscription_id = (known after apply)
      + time_grain      = "Monthly"

      + notification {
          + contact_emails = [
              + "paul.kelleher@contino.io",
            ]
          + contact_groups = []
          + contact_roles  = [
              + "Owner",
            ]
          + enabled        = true
          + operator       = "EqualTo"
          + threshold      = 90
          + threshold_type = "Actual"
        }
      + notification {
          + contact_emails = [
              + "paul.kelleher@contino.io",
            ]
          + contact_groups = []
          + contact_roles  = []
          + enabled        = false
          + operator       = "GreaterThan"
          + threshold      = 100
          + threshold_type = "Forecasted"
        }

      + time_period {
          + end_date   = (known after apply)
          + start_date = (known after apply)
        }
    }

  # module.azurerm_ea_subscription["minimal_options"].azurerm_role_assignment.subscriptiption[0] will be created
  + resource "azurerm_role_assignment" "subscriptiption" {
      + id                               = (known after apply)
      + name                             = (known after apply)
      + principal_id                     = "79ff1f2c-1133-416d-bed5-d8f83fae55f3"
      + principal_type                   = (known after apply)
      + role_definition_id               = (known after apply)
      + role_definition_name             = "Owner"
      + scope                            = (known after apply)
      + skip_service_principal_aad_check = (known after apply)
    }

  # module.azurerm_ea_subscription["minimal_options"].azurerm_subscription.create_s0[0] will be created
  + resource "azurerm_subscription" "create_s0" {
      + alias             = (known after apply)
      + billing_scope_id  = "/providers/Microsoft.Billing/billingAccounts/87561154/enrollmentAccounts/340292"
      + id                = (known after apply)
      + subscription_id   = (known after apply)
      + subscription_name = "minimal_options"
      + tenant_id         = (known after apply)
      + workload          = "DevTest"
    }
{
  "tenant"            : "squad0",

  "owner"             : "test.two",
  "email"             : "paul.kelleher@contino.io", 
  "purpose"           : "max_purpoise_test",
  "alias"             : "max_alias_test",
  "subscription_name" : "maximum_options",
  "workload"          : "DevTest",

  "apply_budget"      : true,
  
  "github_repository" : "new",
  "github_username"   : "pknw1",

  "template_repo_org"   : "pknw1",
  "template_repo"       : "terraform-boilerplate",
  "service_principal_name" : "new",
  "enrollment_account" : ""
  }

all options enabled