public static async Task <string> GetOrCreateResourceGroup(GithubRepo repo) { using (var client = await CreateClient(repo)) { var path = $"resourceGroups/{repo.AzureData.ResourceGroup}?api-version=2018-02-01"; var resp = await client.GetAsync(path); if (resp.StatusCode == HttpStatusCode.OK) { return(repo.AzureData.ResourceGroup); } if (resp.StatusCode == HttpStatusCode.NotFound) { var input = new { location = repo.AzureData.Location, properties = new { }, name = repo.AzureData.ResourceGroup, }; resp = await SendMessage(client, path, HttpMethod.Put, input); var data = await resp.Content.ReadAsStringAsync(); resp.EnsureSuccessStatusCode(); return(repo.AzureData.ResourceGroup);; } var error = await resp.Content.ReadAsStringAsync(); resp.EnsureSuccessStatusCode(); } return(repo.AzureData.ResourceGroup); }
public static async Task <string> CreateAppService(GithubRepo repo, Build build) { var name = string.IsNullOrWhiteSpace(build.AzureAppId) ? $"{repo.Id}-Pull{build.PullRequestId}-{Guid.NewGuid().ToString().Substring(0, 8)}" : build.AzureAppId.Replace("_", "-"); using (var client = await CreateClient(repo)) { var path = $"resourceGroups/{repo.AzureData.ResourceGroup}/providers/Microsoft.Web/serverfarms/{name}?api-version=2016-09-01"; var input = new { name = name, location = repo.AzureData.Location, properties = new { name = name, perSiteScaling = false }, sku = new { name = "F1", tier = "Free", capacity = 1 } }; var resp = await SendMessage(client, path, HttpMethod.Put, input, new Dictionary <string, string> { ["CommandName"] = "appservice plan create" }); var data = await resp.Content.ReadAsStringAsync(); resp.EnsureSuccessStatusCode(); build.AzureAppId = name; return(name); } }
static async Task <IActionResult> CleanupOldApp(HttpRequest req, dynamic data) { var repData = data.repository; var id = Database.CleanseName(repData.full_name.ToString()); GithubRepo repo; try{ repo = await Database.GetRepo(id); } catch (Exception ex) { Console.WriteLine(ex); repo = new GithubRepo { Id = id, Owner = repData.owner.login, RepoName = repData.Name, CloneUrl = repData.clone_url, IsPrivate = repData.@private, AzureData = { Location = "southcentralus", } }; await Database.CreateRepo(repo); } var pullRequest = data.pull_request; string statusUrl = pullRequest.statuses_url; var isAuthenticated = await AzureApi.Authenticate(repo); if (!isAuthenticated) { var paring = await Database.GetOrCreatePairingRequestByRepoId(id); var orgUrl = req.Host.Value; var url = $"{req.Scheme}://{orgUrl}/api/RegisterRepo?id={paring.Id}"; await GithubApi.PostStatus(repo, statusUrl, false, url, $"You need to log into Azure before auto deploy will work. {url}"); return(new BadRequestObjectResult($"Please go to {url} to register the app with azure.")); } if (repo.Builds == null) { repo.Builds = new List <Build>(); } var number = (int)data.number; var build = repo.Builds?.FirstOrDefault(x => x.PullRequestId == number); if (!string.IsNullOrWhiteSpace(build?.AzureAppId)) { await AzureApi.DeleteWebApp(repo, build); await AzureApi.DeleteAppService(repo, build); } return(new OkResult()); }
public static async Task <Document> CreateRepo(GithubRepo repo) { var collectionLink = UriFactory.CreateDocumentCollectionUri(databaseId, collectionId); using (var client = new DocumentClient(new Uri(endpointUrl), authorizationKey)) { return(await client.CreateDocumentAsync(collectionLink, repo)); } }
public static async Task <GithubRepo> Save(GithubRepo repo) { var collectionLink = UriFactory.CreateDocumentCollectionUri(databaseId, collectionId); using (var client = new DocumentClient(new Uri(endpointUrl), authorizationKey)) { var resp = await client.UpsertDocumentAsync(collectionLink, repo); return(repo); } }
public static async Task <bool> DeleteWebApp(GithubRepo repo, Build build) { using (var client = await CreateClient(repo)) { var path = $"resourceGroups/{repo.AzureData.ResourceGroup}/providers/Microsoft.Web/sites/{build.AzureAppId}?api-version=2016-08-01"; var resp = await SendMessage(client, path, HttpMethod.Delete, null); var data = await resp.Content.ReadAsStringAsync(); resp.EnsureSuccessStatusCode(); build.IsActive = false; return(true); } }
static async Task <string> FormatRepoCloneUrl(GithubRepo repo, Build build) { var baseUrl = string.IsNullOrWhiteSpace(build.GitUrl) ? repo.CloneUrl : build.GitUrl; var url = $"{baseUrl}#{build.CommitHash}"; if (repo.IsPrivate) { await GithubApi.Authenticate(repo); var uri = new Uri(url); url = $"{uri.Host}://{repo.GithubAccount.Token}@{uri.Host}/{uri.PathAndQuery}"; } return(url); }
public static async Task <bool> Authenticate(GithubRepo repo) { if (repo.GithubAccount.IsValid()) { return(true); } try { var postData = new Dictionary <string, string> { ["grant_type"] = "refresh_token", ["refresh_token"] = repo.GithubAccount.RefreshToken, ["client_id"] = clientId, ["client_secret"] = clientSecret, }; var message = new HttpRequestMessage(HttpMethod.Post, TokenUrl) { Content = new FormUrlEncodedContent(postData), Headers = { { "Accept", "application/json" } } }; using (var client = await CreateClient(repo, false)) { var reply = await client.SendAsync(message); var resp = await reply.Content.ReadAsStringAsync(); var result = JsonConvert.DeserializeObject <OauthResponse>(resp); if (!string.IsNullOrEmpty(result.RefreshToken)) { repo.GithubAccount.RefreshToken = result.RefreshToken; } repo.GithubAccount.TokenType = result.TokenType; repo.GithubAccount.Token = result.AccessToken; repo.GithubAccount.ExpiresIn = result.ExpiresIn; repo.GithubAccount.Created = DateTime.UtcNow; } await Database.Save(repo); return(true); } catch (Exception) { return(false); } }
// static HttpClient CreateClient(GithubRepo repo) => new HttpClient // { // BaseAddress = new Uri($"https://management.azure.com/subscriptions/{repo.AzureData.Subscription}/"), // DefaultRequestHeaders = { // Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(repo.AzureAccount.TokenType, repo.AzureAccount.Token) // } // }; static async Task <HttpClient> CreateClient(GithubRepo repo, bool authenticate = true) { if (authenticate) { await Authenticate(repo); } return(new HttpClient { BaseAddress = new Uri($"https://management.azure.com/subscriptions/{repo.AzureData.Subscription}/"), DefaultRequestHeaders = { Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(repo.AzureAccount.TokenType, repo.AzureAccount.Token) } }); }
public static async Task <bool> DeleteAppService(GithubRepo repo, Build build) { var name = string.IsNullOrWhiteSpace(build.AzureAppId) ? $"{repo.Id}-Pull{build.PullRequestId}-{Guid.NewGuid().ToString().Substring(0, 8)}" : build.AzureAppId.Replace("_", "-"); using (var client = await CreateClient(repo)) { var path = $"resourceGroups/{repo.AzureData.ResourceGroup}/providers/Microsoft.Web/serverfarms/{name}?api-version=2016-09-01"; var resp = await SendMessage(client, path, HttpMethod.Delete, null); var data = await resp.Content.ReadAsStringAsync(); resp.EnsureSuccessStatusCode(); build.AzureAppId = name; return(true); } }
public static async Task <bool> Authenticate(GithubRepo repo, string code, string redirect) { var authUrl = $"https://login.microsoftonline.com/{azureTennant}/oauth2/authorize"; var postData = new Dictionary <string, string> { ["grant_type"] = "authorization_code", ["code"] = code, ["client_id"] = clientId, ["client_secret"] = clientSecret, ["redirect_uri"] = redirect }; var message = new HttpRequestMessage(HttpMethod.Post, TokenUrl) { Content = new FormUrlEncodedContent(postData), Headers = { { "Accept", "application/json" } } }; using (var client = new HttpClient()) { var reply = await client.SendAsync(message); var resp = await reply.Content.ReadAsStringAsync(); var result = JsonConvert.DeserializeObject <OauthResponse>(resp); if (!string.IsNullOrEmpty(result?.Error)) { throw new Exception($"{result.Error} : {result.ErrorDescription}"); } reply.EnsureSuccessStatusCode(); repo.AzureAccount = new Account() { ExpiresIn = result.ExpiresIn, Created = DateTime.UtcNow, RefreshToken = result.RefreshToken, TokenType = result.TokenType, Token = result.AccessToken, IdToken = result.Id }; await Database.Save(repo); } return(true); }
static async Task <HttpClient> CreateClient(GithubRepo repo, bool authenticate = true) { if (authenticate) { await Authenticate(repo); } var client = new HttpClient { BaseAddress = new Uri($"https://api.github.com"), DefaultRequestHeaders = { Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(repo.GithubAccount.TokenType, repo.GithubAccount.Token) } }; client.DefaultRequestHeaders.Add("User-Agent", "AzureDeploy"); return(client); }
public static async Task <bool> PostStatus(GithubRepo repo, string statuUrl, bool success, string url, string description) { using (var client = await CreateClient(repo)) { var resp = await SendMessage(client, statuUrl, HttpMethod.Post, new { state = success ? "success" : "error", description = description, target_url = url, context = "continuous-integration/AzureDeploy", }); var data = await resp.Content.ReadAsStringAsync(); resp.EnsureSuccessStatusCode(); return(true); } }
static async Task <IActionResult> CreateNewApp(HttpRequest req, dynamic data) { var repData = data.repository; var id = Database.CleanseName(repData.full_name.ToString()); var repo = new GithubRepo { Id = id, Owner = repData.owner.login, RepoName = repData.Name, IsPrivate = repData.@private, }; try{ var doc = await Database.CreateRepo(repo); } catch (Exception) { } return(await GetRegisterUrl(req, id)); }
public static async Task <string> CreateWebApp(GithubRepo repo, Build build) { using (var client = await CreateClient(repo)) { var path = $"resourceGroups/{repo.AzureData.ResourceGroup}/providers/Microsoft.Web/sites/{build.AzureAppId}?api-version=2016-08-01"; var resp = await SendMessage(client, path, HttpMethod.Put, new { location = repo.AzureData.Location, properties = new { name = build.AzureAppId, } }); build.IsActive = true; var data = await resp.Content.ReadAsStringAsync(); resp.EnsureSuccessStatusCode(); return(build.AzureAppId); } }
public static async Task <bool> PublishPullRequst(GithubRepo repo, Build build) { using (var client = new HttpClient()) { client.BaseAddress = new Uri($"https://{build.AzureAppId}.scm.azurewebsites.net/"); var credentials = await GetAzureDeployCredentials(repo, build); var key = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{credentials.username}:{credentials.password}")); client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", key); //Switch branches var resp = await SendMessage(client, "settings", HttpMethod.Post, new { key = "branch", value = build.Branch, }); var data = await resp.Content.ReadAsStringAsync(); resp.EnsureSuccessStatusCode(); var url = $"https://{build.AzureAppId}.scm.azurewebsites.net/deploy"; var cloneUrl = await FormatRepoCloneUrl(repo, build); var payload = new { format = "basic", url = cloneUrl }; resp = await SendMessage(client, url, HttpMethod.Post, payload); data = await resp.Content.ReadAsStringAsync(); return(resp.IsSuccessStatusCode); } }
static async Task <IActionResult> BuildPullRequest(HttpRequest req, dynamic data) { var repData = data.repository; var id = Database.CleanseName(repData.full_name.ToString()); GithubRepo repo; try{ repo = await Database.GetRepo(id); } catch (Exception ex) { Console.WriteLine(ex); repo = new GithubRepo { Id = id, Owner = repData.owner.login, RepoName = repData.Name, CloneUrl = repData.clone_url, IsPrivate = repData.@private, AzureData = { Location = "southcentralus", } }; await Database.CreateRepo(repo); } var pullRequest = data.pull_request; string statusUrl = pullRequest.statuses_url; var isGithubAuthenticated = await GithubApi.Authenticate(repo); if (!isGithubAuthenticated) { var paring = await Database.GetOrCreatePairingRequestByRepoId(id); var orgUrl = req.Host.Value; var url = $"{req.Scheme}://{orgUrl}/api/RegisterRepo?id={paring.Id}"; return(new BadRequestObjectResult($"Please go to {url} to register the app with Github.")); } var isAuthenticated = await AzureApi.Authenticate(repo); if (!isAuthenticated) { var paring = await Database.GetOrCreatePairingRequestByRepoId(id); var orgUrl = req.Host.Value; var url = $"{req.Scheme}://{orgUrl}/api/RegisterRepo?id={paring.Id}"; await GithubApi.PostStatus(repo, statusUrl, false, url, $"You need to log into Azure before auto deploy will work. {url}"); return(new BadRequestObjectResult($"Please go to {url} to register the app with azure.")); } if (repo.Builds == null) { repo.Builds = new List <Build>(); } var number = (int)data.number; var build = repo.Builds?.FirstOrDefault(x => x.PullRequestId == number); if (build == null) { build = new Build { PullRequestId = number, }; repo.Builds.Add(build); } build.CommitHash = pullRequest.head.sha; build.Branch = pullRequest.head.@ref; build.StatusUrl = statusUrl; string buildUrl = pullRequest.head.repo.clone_url; if (repo.CloneUrl != buildUrl) { build.GitUrl = buildUrl; } //Make sure we have a resource group! repo.AzureData.ResourceGroup = await AzureApi.GetOrCreateResourceGroup(repo); bool shouldSave = string.IsNullOrWhiteSpace(build.AzureAppId); build.AzureAppId = await AzureApi.CreateAppService(repo, build); if (shouldSave) { await Database.Save(repo); } await AzureApi.CreateWebApp(repo, build); build.DeployedUrl = $"http://{build.AzureAppId}.azurewebsites.net"; var success = await AzureApi.PublishPullRequst(repo, build); await GithubApi.PostStatus(repo, statusUrl, success, build.DeployedUrl, success?$"Deployment was successful! {build.DeployedUrl}" : "There was an error deploying"); await Database.Save(repo); return(new OkResult()); }
public static async Task <(string username, string password)> GetAzureDeployCredentials(GithubRepo repo, Build build) { using (var client = await CreateClient(repo)) { var resp = await client.PostAsync($"resourceGroups/{repo.AzureData.ResourceGroup}/providers/Microsoft.Web/sites/{build.AzureAppId}/publishxml?api-version=2015-08-01", null); var stream = await resp.Content.ReadAsStreamAsync(); var xml = XElement.Load(stream); var element = xml.Elements("publishProfile").Where(el => (string)el.Attribute("publishMethod") == "MSDeploy").FirstOrDefault(); var username = (string)element.Attribute("userName"); var password = (string)element.Attribute("userPWD"); return(username, password); } }