Skip to content

Commit

Permalink
Merge pull request microsoft#105 from Azure/challenges
Browse files Browse the repository at this point in the history
  • Loading branch information
a11smiles committed Oct 14, 2021
1 parent 936d4de commit 48deb1f
Show file tree
Hide file tree
Showing 9 changed files with 347 additions and 113 deletions.
74 changes: 73 additions & 1 deletion byos/waf/deploy/deploy.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,74 @@
#!/bin/sh
dotnet run --project src/Deploy/Deploy.csproj -- "$@"

while getopts u:p:s:t:dvm flag
do
case "${flag}" in
u) username=${OPTARG};;
p) password=${OPTARG};;
s) subscription=${OPTARG};;
t) tenant=${OPTARG};;
d) deviceLogin=true;;
v) verbose=true;;
m) manualPat=true;;
esac
done

if [ "$verbose" = true ]
then
set -x;
fi

# Variables
rand="$((1 + $RANDOM % 100000000))"
rbac="rbacDeploy$rand"
devops="devopsDeploy$rand"

cd /deploy

echo "Cleaning authorization remnants..."
rm -f ./oh.azureauth

echo "Logging into Azure via CLI and setting subscription ($subscription)..."
if [ "$deviceLogin" = true ]
then
az login --use-device-code --output none --allow-no-subscriptions
else
az login -u $username -p $password --output none --allow-no-subscriptions --only-show-errors
fi
az account set --subscription $subscription

echo "Creating RBAC identity ($rbac) for deployment service principal..."
az ad sp create-for-rbac --only-show-errors --name $rbac --role Contributor --sdk-auth > oh.azureauth

if [ "$manualPat" = false ]
then
echo "Creating managed app ($devops) for generating Azure DevOps PAT token..."
az ad app create --display-name $devops --native-app --required-resource-accesses @/source/azdo/manifest.json --output none

# Wait 60 seconds to ensure that Azure has successfully created service princpals
sleep 60

appId=$(az ad app list --display-name $devops --query [0].appId)
appId="${appId%\"}"
appId="${appId#\"}"
az ad app permission admin-consent --id "${appId}"

echo "Generating Azure AD access token to access Azure DevOps PAT API..."
access_token=$(curl -sS -X POST -d 'grant_type=password&client_id='$appId'&username='$username'&password='$password'&scope=499b84ac-1321-427f-aa17-267ca6975798/.default' https://login.microsoftonline.com/$tenant/oauth2/v2.0/token | jq '.access_token')

echo "Deploying resources..."
./Deploy -t $access_token -a oh.azureauth -s /source -i $subscription -o $rand
else
echo "Skipping: Creating managed app ($devops) for generating Azure DevOps PAT token..."
echo "Skipping: PAT token will be entered manually..."

# Wait 60 seconds to ensure that Azure has successfully created service princpals
sleep 60

echo "Deploying resources..."
./Deploy -a oh.azureauth -s /source -i $subscription -o $rand
fi

echo
echo "Deployment completed."
echo
4 changes: 3 additions & 1 deletion byos/waf/deploy/src/Deploy/AdoHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public TeamProject CreateProject(string project)

OperationReference operation = projectClient.QueueCreateProject(projectCreateParams).Result;

Operation completedOperation = WaitForLongRunningOperation(_connection, operation.Id, 5, 30).Result;
Operation completedOperation = WaitForLongRunningOperation(_connection, operation.Id, 5, 60).Result;

if (completedOperation.Status == OperationStatus.Succeeded)
{
Expand Down Expand Up @@ -141,6 +141,8 @@ public void CommitRepository(string org, TeamProject project, GitRepository repo
Console.WriteLine($"- Pushing repo '{repo.Name}'...");

diag.Process.Start("git", $@"init {Path.GetFullPath(path)}").WaitForExit();
diag.Process.Start("git", $@"-C {Path.GetFullPath(path)} config user.email ""{org}@localhost.com""").WaitForExit();
diag.Process.Start("git", $@"-C {Path.GetFullPath(path)} config user.name ""{org}""").WaitForExit();
diag.Process.Start("git", $@"-C {Path.GetFullPath(path)} branch -m main").WaitForExit();
diag.Process.Start("git", $@"-C {Path.GetFullPath(path)} remote add {org} {repo.RemoteUrl}").WaitForExit();
diag.Process.Start("git", $@"-C {Path.GetFullPath(path)} add .").WaitForExit();
Expand Down
97 changes: 82 additions & 15 deletions byos/waf/deploy/src/Deploy/AzureHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,71 +5,138 @@
using Microsoft.Azure.Management.Fluent;
using Microsoft.Azure.Management.ResourceManager.Fluent.Core;
using Microsoft.Azure.Management.ResourceManager.Fluent.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Text;

namespace Deploy
{
public class AzureHelper
{
private IAzure _azure;
private string _subscriptionId;
private string _orgId;

private AzureHelper() { }
public AzureHelper(IAzure azure)
public AzureHelper(IAzure azure, string subscriptionId, string orgId)
{
_azure = azure;
_subscriptionId = subscriptionId;
_orgId = orgId;
}

public void DeployTemplate(string subscriptionId, string path)
public void DeployTemplate(string path)
{
Console.WriteLine("- Building ARM Template from Bicep...");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
Process.Start("CMD.exe", $@"/C az bicep build --file " + Path.GetFullPath(path + "\\main.bicep")).WaitForExit();
Process.Start("CMD.exe", $@"/C az bicep build --file " + Path.GetFullPath(path + "/main.bicep")).WaitForExit();
else
Process.Start("az", $@"bicep build --file " + Path.GetFullPath(path + "\\main.bicep")).WaitForExit();
Process.Start("az", $@"bicep build --file " + Path.GetFullPath(path + "/main.bicep")).WaitForExit();

Console.WriteLine("- Parsing ARM Template...");
var templateJson = GetArmTemplate(Path.GetFullPath(path + "\\main.json"));
var templateJson = GetArmTemplate(Path.GetFullPath(path + "/main.json"));

Console.WriteLine("- Creating resource group 'webapp'...");
_azure.ResourceGroups.Define("webapp")
.WithRegion(Region.USEast)
.Create();

Console.WriteLine($"- Deploying template to subscription '{subscriptionId}' (this may take 15-20 minutes)...");
Console.WriteLine($"- Deploying template to subscription '{_subscriptionId}' (this may take 15-20 minutes)...");
_azure.Deployments.Define("woodgrove")
.WithExistingResourceGroup("webapp")
.WithTemplate(templateJson)
.WithParameters("{}")
.WithMode(DeploymentMode.Complete)
.Create();

File.Delete(Path.GetFullPath(path + "\\main.json"));
File.Delete(Path.GetFullPath(path + "/main.json"));
}

public void DeployAzDoTemplate(string path)
{
Console.WriteLine("- Parsing ARM Template...");
var templateJson = GetAzDoArmTemplate(Path.GetFullPath(path + "/azuredevops.json"));

Console.WriteLine($"- Creating Azure DevOps tenant 'WAFOpenHack{_orgId}'...");
_azure.ResourceGroups.Define("azdoTenant")
.WithRegion(Region.USCentral)
.Create();

Console.WriteLine($"- Deploying template to subscription '{_subscriptionId}' (this may take a few minutes)...");

Process.Start("az", $@"deployment group create --output none --resource-group azdoTenant --name azdo --template-file {System.IO.Path.GetFullPath(path + "/azuredevops.json")} --parameters devOpsOrgId={_orgId}").WaitForExit();

/*
* The below is commented out due to deploying an Azure DevOps tenant using the FluidAPI will generate an error about no user provided.
* Therefore, the above process is leveraging the Azure CLI for deploying Azure DevOps. Once the bug is fixed, the below code can be used
* in favor over the above process.
*/
/*
_azure.Deployments.Define("azdo")
.WithExistingResourceGroup("azdoTenant")
.WithTemplate(templateJson)
.WithParameters("{}")
.WithMode(DeploymentMode.Complete)
.Create();
*/
}

public string GetArmTemplate(string templateFileName)
{
var armTemplateString = File.ReadAllText(templateFileName);
var parsedTemplate = JObject.Parse(armTemplateString);
var rand = new Random().Next(0, 1000000).ToString("D6");

parsedTemplate.SelectToken("parameters.resource_group_name")["defaultValue"] = "webapp";
parsedTemplate.SelectToken("parameters.region")["defaultValue"] = "eastus";
parsedTemplate.SelectToken("parameters.vnet_name")["defaultValue"] = "vnet-webapp";
parsedTemplate.SelectToken("parameters.elb_name")["defaultValue"] = "elbwebapp";
parsedTemplate.SelectToken("parameters.nsg_name")["defaultValue"] = "nsg-webapp";
parsedTemplate.SelectToken("parameters.storage_web")["defaultValue"] = "storwoodgroveweb" + rand;
parsedTemplate.SelectToken("parameters.storage_sql")["defaultValue"] = "storwoodgrovesql" + rand;
parsedTemplate.SelectToken("parameters.web1vm_dnslabel")["defaultValue"] = "woodgroveweb1" + rand;
parsedTemplate.SelectToken("parameters.web2vm_dnslabel")["defaultValue"] = "woodgroveweb2" + rand;
parsedTemplate.SelectToken("parameters.worker1vm_dnslabel")["defaultValue"] = "woodgroveworker1" + rand;
parsedTemplate.SelectToken("parameters.sqlsvr1vm_dnslabel")["defaultValue"] = "woodgrovesql1" + rand;
parsedTemplate.SelectToken("parameters.external_load_balancer_dnslabel")["defaultValue"] = "woodgroveelb" + rand;
parsedTemplate.SelectToken("parameters.storage_web")["defaultValue"] = "storwoodgroveweb" + _orgId;
parsedTemplate.SelectToken("parameters.storage_sql")["defaultValue"] = "storwoodgrovesql" + _orgId;
parsedTemplate.SelectToken("parameters.web1vm_dnslabel")["defaultValue"] = "woodgroveweb1" + _orgId;
parsedTemplate.SelectToken("parameters.web2vm_dnslabel")["defaultValue"] = "woodgroveweb2" + _orgId;
parsedTemplate.SelectToken("parameters.worker1vm_dnslabel")["defaultValue"] = "woodgroveworker1" + _orgId;
parsedTemplate.SelectToken("parameters.sqlsvr1vm_dnslabel")["defaultValue"] = "woodgrovesql1" + _orgId;
parsedTemplate.SelectToken("parameters.external_load_balancer_dnslabel")["defaultValue"] = "woodgroveelb" + _orgId;
parsedTemplate.SelectToken("parameters.admin_username")["defaultValue"] = "cloudadmin";
parsedTemplate.SelectToken("parameters.admin_password")["defaultValue"] = "Pass@word1234!";
parsedTemplate.SelectToken("parameters.sql_admin_username")["defaultValue"] = "cloudsqladmin";
parsedTemplate.SelectToken("parameters.sql_admin_password")["defaultValue"] = "(Pass@word)1234!";

return parsedTemplate.ToString();
}

public string GetAzDoArmTemplate(string templateFileName)
{
var armTemplateString = File.ReadAllText(templateFileName);
var parsedTemplate = JObject.Parse(armTemplateString);

parsedTemplate.SelectToken("parameters.devOpsOrgId")["defaultValue"] = _orgId;

return parsedTemplate.ToString();
}

public async Task<string> GeneratePat(string accessToken)
{
using (var client = new HttpClient()) {
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

string json = Newtonsoft.Json.JsonConvert.SerializeObject(new {
displayName = "openhack",
scope = "app_token",
allOrgs = false
});

var data = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync($"https://vssps.dev.azure.com/WAFOpenHack{_orgId}/_apis/Tokens/Pats?api-version=6.1-preview", data);

var result = response.Content.ReadAsStringAsync().Result;
var jObj = JObject.Parse(result);
return jObj["patToken"]["token"].ToString();
}
}
}
}
10 changes: 5 additions & 5 deletions byos/waf/deploy/src/Deploy/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ namespace Deploy
{
public class Options
{
[Option('p', "pat", HelpText = "A personal access token for Azure DevOps.")]
[Option('t', "token", HelpText = "A generated Azure AD access token (NOTE: This is NOT an Azure DevOps PAT).")]
public string AccessToken { get; set; }

[Option('a', "auth", HelpText = "The path to the Azure AUTH file.")]
public string AuthFile { get; set; }

[Option('o', "org", HelpText = "The name of the Azure DevOps organization.")]
public string Organization { get; set; }

[Option('s', "source", HelpText = "The path of the parent source folder.")]
public string Source { get; set; }

[Option('u', "subscriptionId", HelpText = "The Azure subscription Id.")]
[Option('i', "id", HelpText = "The Azure subscription Id.")]
public string SubscriptionId { get; set; }

[Option('o', "org", HelpText = "Optional. A pre-generated organization id.")]
public string OrganizationId { get; set; }
}

}
Loading

0 comments on commit 48deb1f

Please sign in to comment.