Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Azure function to remove used subscriptions #287

Merged
merged 12 commits into from
Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/azFunction/AzFunctionCode/.funcignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.git*
.vscode
local.settings.json
test
6 changes: 6 additions & 0 deletions .github/azFunction/AzFunctionCode/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Overview
This folder contains the various functions that are contained in the overall Azure Functions. The following functions are present:
1. timerschedule; this is a simple timer trigger which runs every five hours, and triggers the next function through placing a queue item in the startjob queue.
2. getPullRequests; this is a queue based trigger (startjob queue) which gets the latest x closed pull requests from GitHub. PR title, number and state for each is saved in a queue item in the closedPullRequests queue.
3. getSubscriptions; this is a queue based trigger (closedPullRequests queue), which for each pull request looks for an corresponding, active subscription. If the subscription is found subscription name and id is saved in a queue item in the subscriptionsToClose queue.
4. cancelSubscriptions; this is a queue based trigger (subscriptionsToClose queue), which for each subscription tries to cancel the subscription. If succesful subscription name and id is saved in a queue item in the canceledSubscriptions queue.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"bindings": [
{
"name": "QueueItem",
"type": "queueTrigger",
"direction": "in",
"queueName": "subscriptionsToClose",
"connection": "AzureWebJobsStorage"
},
{
"type": "queue",
"direction": "out",
"name": "canceledSubscriptions",
"queueName": "canceledSubscriptions",
"connection": "AzureWebJobsStorage"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Overview
This function is triggered by items arriving on the subscriptionsToClose queue as defined in input bindings in the function.json file. Upon triggering the function will cancel the subscription using Azure rest api. If succesful, subscription name and id is placed in the queue canceledSubscriptions on associated storage as specified in output bindings in the function.json file.
18 changes: 18 additions & 0 deletions .github/azFunction/AzFunctionCode/cancelSubscriptions/run.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Input bindings are passed in via param block.
param($QueueItem, $TriggerMetadata)
# Write out the queue message and insertion time to the information log.
Write-Output "PowerShell queue trigger function processed work item: $QueueItem"
$subscriptionId = $QueueItem.Body.subscriptionId
$subscriptionName = $QueueItem.Body.subscriptionName
Write-Output "Subscription to be canceled is $subscriptionName with id: $subscriptionId"
$cancelUri = "https://management.azure.com/subscriptions/$($subscriptionId)/providers/Microsoft.Subscription/cancel?api-version=2020-09-01"
Invoke-AzRestMethod -Uri $cancelUri -Method POST
$body = @{
subscriptionName = $subscriptionName
subscriptionId = $subscriptionId
}
Push-OutputBinding -Name canceledSubscriptions -Value ([HttpResponseContext]@{
Body = $body
})
Write-Output "Queue item insertion time: $($TriggerMetadata.InsertionTime)"

18 changes: 18 additions & 0 deletions .github/azFunction/AzFunctionCode/getPullRequests/function.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"bindings": [
{
"name": "QueueItem",
"type": "queueTrigger",
"direction": "in",
"queueName": "startjob",
"connection": "AzureWebJobsStorage"
},
{
"type": "queue",
"direction": "out",
"name": "pullRequests",
"queueName": "closedPullRequests",
"connection": "AzureWebJobsStorage"
}
]
}
2 changes: 2 additions & 0 deletions .github/azFunction/AzFunctionCode/getPullRequests/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Overview
This function is triggered by items arriving on the startjob queue as defined in input bindings in the function.json file. Upon triggering the function will get a number of the latest closed pull requests on the ALZ-Bicep GitHub repo. This is all specified in the run.ps1 file, currently it's set to the last 20 on page 1 (latest). Note that Github API have a maximum of 100 items per page pulled so if a greater number was required, more pages would need to be queried. For each PR the title, PR number and state is placed in the queue closedPullRequests on associated storage as specified in output bindings in the function.json file.
17 changes: 17 additions & 0 deletions .github/azFunction/AzFunctionCode/getPullRequests/run.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Input bindings are passed in via param block.
param($QueueItem, $TriggerMetadata)
# Write out the queue message and insertion time to the information log.
Write-Output "PowerShell queue trigger function processed work item: $QueueItem"
Write-Output "Queue item insertion time: $($TriggerMetadata.InsertionTime)"
$perPageCount = 0
$closedPrs = Invoke-RestMethod -Method Get -Uri "https://api.github.com/repos/Azure/ALZ-Bicep/pulls?per_page=$perPageCount&state=closed&page=1"
$closedPrs | Select-Object -unique -Property title, number, state | ForEach-Object {
$body = @{
prTitle = $PSItem.title
prNumber = $PSItem.number
prState = $PSItem.state
}
Push-OutputBinding -Name pullRequests -Value ([HttpResponseContext]@{
Body = $body
})
}
18 changes: 18 additions & 0 deletions .github/azFunction/AzFunctionCode/getSubscriptions/function.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"bindings": [
{
"name": "QueueItem",
"type": "queueTrigger",
"direction": "in",
"queueName": "closedPullRequests",
"connection": "AzureWebJobsStorage"
},
{
"type": "queue",
"direction": "out",
"name": "subscriptionsToClose",
"queueName": "subscriptionsToClose",
"connection": "AzureWebJobsStorage"
}
]
}
2 changes: 2 additions & 0 deletions .github/azFunction/AzFunctionCode/getSubscriptions/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Overview
This function is triggered by items arriving on the closedPullRequests queue as defined in input bindings in the function.json file. Upon triggering the function will try to get the subscription state based on subscription name using Get-AzSubscription. If subscription exists and is active, subscription name and id is placed in the queue subscriptionsToClose on associated storage as specified in output bindings in the function.json file.
31 changes: 31 additions & 0 deletions .github/azFunction/AzFunctionCode/getSubscriptions/run.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Input bindings are passed in via param block.
param($QueueItem, $TriggerMetadata)
# Write out the queue message and insertion time to the information log.
Write-Output "PowerShell queue trigger function processed work item: $QueueItem"
$prNumber = $QueueItem.Body.prNumber
$subscriptionName = "sub-unit-test-pr-$prNumber"
Write-Output "Subscription to look for is $subscriptionName"
Write-Output "Queue item insertion time: $($TriggerMetadata.InsertionTime)"
#MSI to look for subscripition in current tenant
Import-module Az.Accounts -verbose
$subscription = Get-AzSubscription -SubscriptionName $subscriptionName -ErrorAction SilentlyContinue
If ($subscription) {
Write-Output "found subscription $subscriptionName"
$subscriptionId = $subscription.Id
$subscriptionState = $subscription.State
If ($subscriptionState -eq "Enabled") {
$body = @{
subscriptionId = $subscriptionId
subscriptionName = $subscriptionName
}
Push-OutputBinding -Name subscriptionsToClose -Value ([HttpResponseContext]@{
Body = $body
})
}
Else {
Write-Output "Subscription $subscriptionName is already canceled"
}
}
Else {
Write-Output "Could not find subscription $subscriptionName"
}
18 changes: 18 additions & 0 deletions .github/azFunction/AzFunctionCode/host.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[2.*, 3.0.0)"
},
"managedDependency": {
"enabled": true
}
}
22 changes: 22 additions & 0 deletions .github/azFunction/AzFunctionCode/profile.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Azure Functions profile.ps1
#
# This profile.ps1 will get executed every "cold start" of your Function App.
# "cold start" occurs when:
#
# * A Function App starts up for the very first time
# * A Function App starts up after being de-allocated due to inactivity
#
# You can define helper functions, run commands, or specify environment variables
# NOTE: any variables defined that are not environment variables will get reset after the first execution

# Authenticate with Azure PowerShell using MSI.
# Remove this if you are not planning on using MSI or Azure PowerShell.
if ($env:MSI_SECRET) {
Disable-AzContextAutosave -Scope Process | Out-Null
Connect-AzAccount -Identity
}

# Uncomment the next line to enable legacy AzureRm alias in Azure PowerShell.
# Enable-AzureRmAlias

# You can also define functions or aliases that can be referenced in any of your PowerShell functions.
8 changes: 8 additions & 0 deletions .github/azFunction/AzFunctionCode/requirements.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# This file enables modules to be automatically managed by the Functions service.
# See https://aka.ms/functionsmanageddependency for additional information.
#
@{
# For latest supported version, go to 'https://www.powershellgallery.com/packages/Az'.
# To use the Az module in your function app, please uncomment the line below.
'Az.Accounts' = '2.*'
}
17 changes: 17 additions & 0 deletions .github/azFunction/AzFunctionCode/timerschedule/function.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"bindings": [
{
"name": "Timer",
"type": "timerTrigger",
"direction": "in",
"schedule": "0 0 */2 * * *"
},
{
"type": "queue",
"direction": "out",
"name": "startJob",
"queueName": "startjob",
"connection": "AzureWebJobsStorage"
}
]
}
2 changes: 2 additions & 0 deletions .github/azFunction/AzFunctionCode/timerschedule/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Overview
This trigger runs every x hours (set by cron syntax in the function.json file). When the trigger runs it creates a queue item in the startjob queue on associated storage as defined by output bindings in the function.json file. This queue item is used to trigger subsequent functions.
21 changes: 21 additions & 0 deletions .github/azFunction/AzFunctionCode/timerschedule/run.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Input bindings are passed in via param block.
param($Timer)

# Get the current universal time in the default string format
$currentUTCtime = (Get-Date).ToUniversalTime()

# The 'IsPastDue' porperty is 'true' when the current function invocation is later than scheduled.
if ($Timer.IsPastDue) {
Write-Output "PowerShell timer is running late!"
}

# Write an information log with the current time.
Write-Output "PowerShell timer trigger function ran! TIME: $currentUTCtime"
$body = @{
GitHubRepo = "https://api.github.com/repos/Azure/ALZ-Bicep/pulls"
}
# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name startJob -Value ([HttpResponseContext]@{
#StatusCode = [HttpStatusCode]::OK
Body = $body
})
112 changes: 112 additions & 0 deletions .github/azFunction/AzFunctionInfrastructure/main.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
@description('Azure region to deploy in')
jfaurskov marked this conversation as resolved.
Show resolved Hide resolved
param parLocation string = resourceGroup().location

@description('Azure Function App Name')
param parAzFunctionName string = uniqueString(resourceGroup().id)

@description('Azure Storage Account Name')
param parStorageAccountName string = uniqueString(resourceGroup().id)

@description('App Service Plan Name')
param parApplicationServicePlanName string = 'FunctionPlan'

@description('Application Insights Name')
param parApplicationInsightsName string = 'AppInsights'

resource resStorageAccount 'Microsoft.Storage/storageAccounts@2021-04-01' = {
name: parStorageAccountName
location: parLocation
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
supportsHttpsTrafficOnly: true
encryption: {
services: {
file: {
keyType: 'Account'
enabled: true
}
blob: {
keyType: 'Account'
enabled: true
}
}
keySource: 'Microsoft.Storage'
}
accessTier: 'Hot'
}
}

resource resApplicationInsights 'Microsoft.Insights/components@2020-02-02' = {
name: parApplicationInsightsName
location: parLocation
kind: 'web'
properties: {
Application_Type: 'web'
publicNetworkAccessForIngestion: 'Enabled'
publicNetworkAccessForQuery: 'Enabled'
}
}

resource resApplicationServicePlan 'Microsoft.Web/serverfarms@2020-12-01' = {
name: parApplicationServicePlanName
location: parLocation
kind: 'functionapp,linux'
sku: {
name: 'Y1'
}
properties: {}
}

resource resAzFunction 'Microsoft.Web/sites@2021-03-01' = {
name: parAzFunctionName
location: parLocation
kind: 'functionapp,linux'
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: resApplicationServicePlan.id
enabled: true
reserved: true
isXenon: false
hyperV: false
siteConfig: {
numberOfWorkers: 1
acrUseManagedIdentityCreds: false
alwaysOn: false
http20Enabled: false
functionAppScaleLimit: 200
minimumElasticInstanceCount: 0
appSettings: [
{
name: 'AzureWebJobsStorage'
value: 'DefaultEndpointsProtocol=https;AccountName=${resStorageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(resStorageAccount.id, resStorageAccount.apiVersion).keys[0].value}'
}
{
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
value: 'DefaultEndpointsProtocol=https;AccountName=${resStorageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(resStorageAccount.id, resStorageAccount.apiVersion).keys[0].value}'
}
{
name: 'WEBSITE_CONTENTSHARE'
value: 'denyfunctionb051f2'
}
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: resApplicationInsights.properties.InstrumentationKey
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: 'powerShell'
}
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~4'
}
]
}
httpsOnly: true
}
}
26 changes: 26 additions & 0 deletions .github/azFunction/AzFunctionInfrastructure/rbac.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
targetScope = 'managementGroup'

@description('Azure Function App Name')
param parAzFunctionName string

@description('Resource group for Azure Function App')
param parAzFunctionResourceGroupName string = 'cancelsubscription'

@description('Subscription Id for Azure Function App')
param parAzFunctionSubscriptionId string

// Creating a symbolic name for an existing resource
resource resAzFunction 'Microsoft.Web/sites@2021-03-01' existing = {
name: parAzFunctionName
scope: resourceGroup(parAzFunctionSubscriptionId, parAzFunctionResourceGroupName)
}

module modRoleAssignmentManagementGroup '../../../infra-as-code/bicep/modules/roleAssignments/roleAssignmentManagementGroup.bicep' = {
name: 'Grant-FunctionApp-Owner'
params: {
parAssigneeObjectId: resAzFunction.identity.principalId
parAssigneePrincipalType: 'ServicePrincipal'
parRoleDefinitionId: '8e3af657-a8ff-443c-a75c-2fe8c4bcb635'
parRoleAssignmentNameGuid: '0f69bde8-3eff-476f-93fb-74210d02dbe5'
}
}
2 changes: 2 additions & 0 deletions .github/azFunction/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Overview
This folder contains bicep templates and code to create an Azure function to cancel subscriptions created as part of PR validation testing. This is just for internal ALZ-Bicep environment hygiene and not intended as part of the overall ALZ-Bicep accelerator.
Loading