RazorSPoint / azure-devops-azure-policy-extension

Extension for deploying Azure Governance topcis (Policies, Initiatives) with Azure DevOps (formerly VSTS)
https://razorspoint.github.io/azure-devops-azure-policy-extension
MIT License
2 stars 3 forks source link

Execute Azure Blueprints #4

Closed andyofengland closed 5 years ago

andyofengland commented 5 years ago

More a feature request than an issue.

Az MS have released (in Preview at this time) Azure Blueprints to allow a governed definition of "stuff", it would be great to be able to have a nice integration into DevOps whereby the pipeline can execute a Blueprint as a release step.

Whilst it is currently possible to fire off a REST call, this is neither elegant nor desired but a "user friendly" plugin would be much better.

SebastianSchuetze commented 5 years ago

Hi @andyofengland thanks for your feedback. That was actually also part of a plan. Not sure yet, when I can include something like this. But it should come!

Just one question Did you try the current task? Does this work for you?

andyofengland commented 5 years ago

Hi - I didn't try the current one - the info didn't seem to mention Blueprints so I assumed it wasn't supported.

SebastianSchuetze commented 5 years ago

It doesn't yet. Just hoped if you used the policy task :-) haven't gotten any feedback yet. But check in to see if a new task is added soon :-)

andyofengland commented 5 years ago

Hahaha - that's a good reason :)

At the moment, I don't need a policy task so I didn't try the others; I didn't want to have to roll my own PS1 script/code to call Blueprints so scoured the marketplace instead and your modules seemed the closest fit.

For now I might just roll some PS1 code and replace it with something less "funky" once your code is added.

andyofengland commented 5 years ago

When you're looking to start this piece, it may be worth making it so that the module executes the blueprint if it's not already been executed - as in, the step is re-runnable.

I've got it all working using a PS1 script - self validating, re-runnable and so on but as it's PS1 driven, it's not as nice as a module in the pipeline.

SebastianSchuetze commented 5 years ago

I've got it all working using a PS1 script - self validating, re-runnable and so on but as it's PS1 driven, it's not as nice as a module in the pipeline.

Would you be able to provide the script to me? Maybe I would be faster in integrating it, since my task will be using PowerShell as well.

Would you be able to fork my repository and edit the branch "AzureBlueprintTask" with this file and paste your basic code? And then make a pull request into this source repo into the same branch?

https://github.com/RazorSPoint/azure-devops-azure-policy-extension/blob/topic/AzureBluePrintTask/src/AzurePolicy/Ahttps:/github.com/RazorSPoint/azure-devops-azure-policy-extension/tree/topic/AzureBluePrintTask/src/AzureBlueprint/AzureBlueprintV1/DeployBlueprintDefinition.ps1

Of course only if you want! If not I would figure it out myself as well.

andyofengland commented 5 years ago

It seems that you have a folder in the branch that can't be created in Windows "AHttps:"

andyofengland commented 5 years ago

FYI - the recently released Az.Blueprint Powershell library is what I'm using - if you take a look at that module, it's all actually a piece of cake and only needs 2 or 3 commands...

function InstallPSModuleIfMissing()
{
    Param
    (
        $searchModuleKey,
        $moduleName
    )

    if (Get-Module -ListAvailable -Name $searchModuleKey) {
        Write-Host "Blueprint Assignment - $moduleName module already installed"
    } 
    else {
        Write-Host "Blueprint Assignment - $moduleName module does not exist, installing"
        Install-Module $moduleName -AllowClobber -Force -SkipPublisherCheck -Scope CurrentUser
    }
}

function CheckModules()
{
    InstallPSModuleIfMissing -searchModuleKey "Az.Websites" -moduleName "Az"
    InstallPSModuleIfMissing -searchModuleKey "Az.Blueprint" -moduleName "Az.Blueprint"
}

function CurrentSubscriptionId()
{
    return (Get-AzContext).Subscription.Id
}

function AllowedLocation()
{
    Param
    (
        $targetLocation
    )

    $allowedLocations = @("uksouth", "ukwest")

    foreach($loc in $allowedLocations)
    {
        if ($targetLocation -eq $loc)
        {
            return $true
        }
    }

    throw "Blueprint Assignment - The specified location is not allowed"
}

function GetBlueprint() {
    Param
    (
        $blueprintName
    )

    $blueprints = Get-AzBlueprint

    foreach ($print in $blueprints) {
        if ($print.Name -eq $blueprintName) {
            return $print
        }
    }

    throw "Blueprint Assignment - Cannot find blueprint [$blueprintName]"
}

function Wait
{
    Param
    (
        [Int]
        $seconds,

        [string]
        $waitMessage
    )

    Write-Host "Blueprint Assignment - Waiting... [$waitMessage]"
    Start-Sleep -Milliseconds ($seconds * 1000)
}

function Set-Blueprint
{
    Param
    (
        $targetLocation,
        $blueprintName,
        $assignmentName,
        $blueprintParameters
    )    

    # Get the test blueprint
    $subscriptionId = CurrentSubscriptionId

    Write-Host "Blueprint Assignment - Using Current Subscription Id [$subscriptionId]"
    Write-Host "Blueprint Assignment - Assignment Id [$assignmentName]"

    # Check to see if the assignment has alreay been run
    $assigned = Get-AzBlueprintAssignment -SubscriptionId $subscriptionId -Name $assignmentName -ErrorAction SilentlyContinue

    if ($assigned -eq $null)
    {
        Write-Host "Blueprint Assignment - Looking for Blueprint [$blueprintName]"
        $blueprint = GetBlueprint -blueprintName $blueprintName

        Write-Host "Blueprint Assignment - Found Blueprint"
        Write-Host "Blueprint Assignment -" $blueprint.Name

        $assignment = New-AzBlueprintAssignment -Name $assignmentName -Blueprint $blueprint -SubscriptionId $subscriptionId -Location $targetLocation -Parameter $params

        $assigned = Get-AzBlueprintAssignment -SubscriptionId $subscriptionId -Name $assignmentName

        while ($assigned.ProvisioningState -eq "Creating" -or $assigned.ProvisioningState -eq "Deploying" -or $assigned.ProvisioningState -eq "Waiting") 
        {
            $waitMessage = "Blueprint Assignment - Current Status: " + $assigned.ProvisioningState
            Wait -seconds 10 -waitMessage $waitMessage
            $assigned = Get-AzBlueprintAssignment -SubscriptionId $subscriptionId -Name $assignmentName
        }

        $finalState = $assigned.ProvisioningState
        Write-Host "Blueprint Assignment - Final State: " ($finalState)
        return $finalState
    }
    else 
    {
        return "Duplicate"
    }
}

function AssignBlueprint()
{
    Param
    (
        $subscriptionId,
        $targetLocation,
        $blueprintName,
        $assignmentName,
        $blueprintParameters
    ) 

    CheckModules

    AllowedLocation -targetLocation $targetLocation | Out-Null

    $provState = Set-Blueprint -subscriptionId $subscriptionId -targetLocation $targetLocation -blueprintName $blueprintName -assignmentName $assignmentName -blueprintParameters $params

    if ($provState -eq "Succeeded")
    {
        Write-Host "Blueprint Assignment - Provisioning Completed Successfully"
    }
    else 
    {
        if ($provState -eq "Duplicate")
        {
            Write-Host "Blueprint Assignment - Assignment Already Performed"
        }
        else 
        {
            Write-Error "Blueprint Assignment - Something didn't quite go right... [$provState]"     
        }        
    }
}

There are some specific use-case bits in the above (region checking) and I had some issues getting the Get-AzBlueprint to work as defined using the Name parameter so I simply enumerated them all and did a PS1 based loop.

In the above script, I also have a child script that wraps the execution to reduce some of the complexity, such as:

Write-Host "Executing Add Resource Group Blueprint"

$blueprintName = "AddResourceGroup-bp"
$blueprintAssignmentId = "PS-BlueprintApply-$blueprintName-$platformTeamName-$resourceGroupName"

#Whatever params are defined by the blueprint 
$params = @{
    "location" = $targetLocation;
    "blueprintParam1" = $param1;
    "blueprintParam2" = $param2;
    "blueprintParam3" = $param3;
    "[Usergrouporapplicationname]:Owner_RoleAssignmentName" = @($ownerRoleGuid);
    "[Usergrouporapplicationname]:Reader_RoleAssignmentName" = @($readerRoleGuid);
    "[Usergrouporapplicationname]:Contributor_RoleAssignmentName" = @($contributorRoleGuid);
}

AssignBlueprint `
    -targetLocation $targetLocation `
    -blueprintName $blueprintName `
    -assignmentName $blueprintAssignmentId `
    -blueprintParameters $params

This is then the script that's triggered by DevOps - but it could be simplified I suppose.

The DevOps YAML looks like:

steps:
- task: AzurePowerShell@4
  displayName: 'Execute Blueprint'
  inputs:
    azureSubscription: 'xxxxx-xxxxx-xxxxx-xxxxx'
    ScriptPath: '$(System.DefaultWorkingDirectory)/<PathToPS1Script.ps1>'
    ScriptArguments: '<argument list as per the PS1 script>'
    azurePowerShellVersion: LatestVersion

As mentioned before, it works (providing the DevOps service account has the necessary permissions) but it has room for improvement.

SebastianSchuetze commented 5 years ago

I checked your script. There is one problem. This is using the Az module currently. I am using the AzureRM module still.

So it could be, that it takes a bit longer before I can integrate it. :-)

andyofengland commented 5 years ago

Changing the AzureRM Ps1 script to use the Az version is pretty much a search and replace. That said, you gotta test of course.

There is however a big problem with blueprints in Azure... the account they run under needs owner access to a subscription - that is something I have a concern with but, shouldn’t impact the plugin.

SebastianSchuetze commented 5 years ago

Hey, thanks. I already figured that. I still need to write some test. Had already small problems with the current task because of missing tests.

There is no way around the owner role, since it can do all sorts of thing that need ownership:

to cover everything, there is not even a new / custom role that would make sense. The question is maybe how to improve security with that. Maybe not allowing assignment in a pipeline, but rather only definition deployment.

SebastianSchuetze commented 5 years ago

Btw. this is the current status how the task would look like. This will currently only deploy blueprints in the end and not assign them. But that is already a lot in my opinion. ;-)

Assigning would come later. But this would be another task.

image

andyofengland commented 5 years ago

Nice! Being able to publish a blueprint is key for the pipeline. Just the actual blueprint pipeline not the consumer/assignee. Nice work.

SebastianSchuetze commented 5 years ago

@andyofengland I am closing this issue. I didn't have time to look into it much, but since Az.BluePrints has been published as part of the Az module and also the following repository is maintained by an MS employee of the BluePrint team it does not make much sense to include this one in this extension.

https://github.com/neilpeterson/azure-blueprints-pipeline-tasks