diff --git a/implement/PersistentProcess/PersistentProcess.Test/TestWebHost.cs b/implement/PersistentProcess/PersistentProcess.Test/TestWebHost.cs index fe40941d..15c8ced9 100644 --- a/implement/PersistentProcess/PersistentProcess.Test/TestWebHost.cs +++ b/implement/PersistentProcess/PersistentProcess.Test/TestWebHost.cs @@ -1190,8 +1190,9 @@ public void tooling_supports_deploy_app_directly_on_process_store() var deployReport = elm_fullstack.Program.deployApp( sourcePath: "./../../../../../example-apps/docker-image-default-app", site: testDirectory, - sitePassword: null, - initElmAppState: true); + siteDefaultPassword: null, + initElmAppState: true, + promptForPasswordOnConsole: false); using (var restoredProcess = Kalmit.PersistentProcess.WebHost.PersistentProcess.PersistentProcessVolatileRepresentation.Restore( diff --git a/implement/elm-fullstack/Program.cs b/implement/elm-fullstack/Program.cs index 5cb7c9eb..03fb307c 100644 --- a/implement/elm-fullstack/Program.cs +++ b/implement/elm-fullstack/Program.cs @@ -227,8 +227,9 @@ CommandOption verboseLogOptionFromCommand(CommandLineApplication command) => deployApp( sourcePath: fromOption.Value(), site: site, - sitePassword: sitePassword, - initElmAppState: initElmAppStateOption.HasValue()); + siteDefaultPassword: sitePassword, + initElmAppState: initElmAppStateOption.HasValue(), + promptForPasswordOnConsole: true); writeReportToFileInReportDirectory( reportContent: Newtonsoft.Json.JsonConvert.SerializeObject(deployReport, Newtonsoft.Json.Formatting.Indented), @@ -252,8 +253,9 @@ CommandOption verboseLogOptionFromCommand(CommandLineApplication command) => var attemptReport = setElmAppState( site: site, - sitePassword: sitePassword, - sourcePath: fromOption.Value()); + siteDefaultPassword: sitePassword, + sourcePath: fromOption.Value(), + promptForPasswordOnConsole: true); writeReportToFileInReportDirectory( reportContent: Newtonsoft.Json.JsonConvert.SerializeObject(attemptReport, Newtonsoft.Json.Formatting.Indented), @@ -416,8 +418,9 @@ public class ResponseFromServerStruct static public DeployAppReport deployApp( string sourcePath, string site, - string sitePassword, - bool initElmAppState) + string siteDefaultPassword, + bool initElmAppState, + bool promptForPasswordOnConsole) { var beginTime = CommonConversion.TimeStringViewForReport(DateTimeOffset.UtcNow); @@ -449,52 +452,53 @@ static public DeployAppReport deployApp( if (Regex.IsMatch(site, "^http(|s)\\:")) { - using (var httpClient = new System.Net.Http.HttpClient()) - { - httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue( - "Basic", - Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes( - Kalmit.PersistentProcess.WebHost.Configuration.BasicAuthenticationForAdmin(sitePassword)))); - - var deployAddress = - (site.TrimEnd('/')) + - (initElmAppState - ? - StartupAdminInterface.PathApiDeployAppConfigAndInitElmAppState - : - StartupAdminInterface.PathApiDeployAppConfigAndMigrateElmAppState); - - Console.WriteLine("Beginning to deploy app '" + appConfigBuildId + "' to '" + deployAddress + "'..."); + var deployAddress = + (site.TrimEnd('/')) + + (initElmAppState + ? + StartupAdminInterface.PathApiDeployAppConfigAndInitElmAppState + : + StartupAdminInterface.PathApiDeployAppConfigAndMigrateElmAppState); - var httpContent = new System.Net.Http.ByteArrayContent(appConfigZipArchive); + Console.WriteLine("Attempting to deploy app '" + appConfigBuildId + "' to '" + deployAddress + "'..."); - httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/zip"); - httpContent.Headers.ContentDisposition = - new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment") { FileName = appConfigBuildId + ".zip" }; + var httpResponse = AttemptHttpRequest(() => + { + var httpContent = new System.Net.Http.ByteArrayContent(appConfigZipArchive); - var httpResponse = httpClient.PostAsync(deployAddress, httpContent).Result; + httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/zip"); + httpContent.Headers.ContentDisposition = + new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment") { FileName = appConfigBuildId + ".zip" }; - var responseContentString = httpResponse.Content.ReadAsStringAsync().Result; + return new System.Net.Http.HttpRequestMessage + { + Method = System.Net.Http.HttpMethod.Post, + RequestUri = new Uri(deployAddress), + Content = httpContent, + }; + }, + defaultPassword: siteDefaultPassword, + promptForPasswordOnConsole: promptForPasswordOnConsole).Result; - Console.WriteLine( - "Server response: " + httpResponse.StatusCode + "\n" + - responseContentString); + var responseContentString = httpResponse.Content.ReadAsStringAsync().Result; - object responseBodyReport = responseContentString; + Console.WriteLine( + "Server response: " + httpResponse.StatusCode + "\n" + responseContentString); - try - { - responseBodyReport = - Newtonsoft.Json.JsonConvert.DeserializeObject(responseContentString); - } - catch { } + object responseBodyReport = responseContentString; - responseFromServer = new DeployAppReport.ResponseFromServerStruct - { - statusCode = (int)httpResponse.StatusCode, - body = responseBodyReport, - }; + try + { + responseBodyReport = + Newtonsoft.Json.JsonConvert.DeserializeObject((string)responseBodyReport); } + catch { } + + responseFromServer = new DeployAppReport.ResponseFromServerStruct + { + statusCode = (int)httpResponse.StatusCode, + body = responseBodyReport, + }; } else { @@ -548,6 +552,45 @@ static public DeployAppReport deployApp( }; } + static async System.Threading.Tasks.Task + AttemptHttpRequest( + Func buildRequest, + string defaultPassword, + bool promptForPasswordOnConsole) + { + using (var httpClient = new System.Net.Http.HttpClient()) + { + void setHttpClientPassword(string password) + { + httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue( + "Basic", + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes( + Kalmit.PersistentProcess.WebHost.Configuration.BasicAuthenticationForAdmin(password)))); + } + + setHttpClientPassword(defaultPassword); + + var httpResponse = await httpClient.SendAsync(buildRequest()); + + if (promptForPasswordOnConsole && + httpResponse.StatusCode == System.Net.HttpStatusCode.Unauthorized && + httpResponse.Headers.WwwAuthenticate.Any()) + { + Console.WriteLine("The server at '" + httpResponse.RequestMessage.RequestUri.ToString() + "' is asking for authentication. Please enter the password we should use to authenticate there:"); + + var password = ReadLine.ReadPassword("> ").Trim(); + + Console.WriteLine("I retry using this password..."); + + setHttpClientPassword(password); + + httpResponse = await httpClient.SendAsync(buildRequest()); + } + + return httpResponse; + } + } + class SetElmAppStateReport { public string beginTime; @@ -570,7 +613,11 @@ public class ResponseFromServerStruct } } - static SetElmAppStateReport setElmAppState(string site, string sitePassword, string sourcePath) + static SetElmAppStateReport setElmAppState( + string site, + string siteDefaultPassword, + string sourcePath, + bool promptForPasswordOnConsole) { var beginTime = CommonConversion.TimeStringViewForReport(DateTimeOffset.UtcNow); @@ -586,41 +633,42 @@ static SetElmAppStateReport setElmAppState(string site, string sitePassword, str SetElmAppStateReport.ResponseFromServerStruct responseFromServer = null; - using (var httpClient = new System.Net.Http.HttpClient()) - { - httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue( - "Basic", - Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes( - Kalmit.PersistentProcess.WebHost.Configuration.BasicAuthenticationForAdmin(sitePassword)))); - - var httpContent = new System.Net.Http.ByteArrayContent(elmAppStateSerialized); - - httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + var httpResponse = AttemptHttpRequest(() => + { + var httpContent = new System.Net.Http.ByteArrayContent(elmAppStateSerialized); - var httpResponse = httpClient.PostAsync( - site.TrimEnd('/') + StartupAdminInterface.PathApiElmAppState, httpContent).Result; + httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); - var responseContentString = httpResponse.Content.ReadAsStringAsync().Result; + return new System.Net.Http.HttpRequestMessage + { + Method = System.Net.Http.HttpMethod.Post, + RequestUri = new Uri(site.TrimEnd('/') + StartupAdminInterface.PathApiElmAppState), + Content = httpContent, + }; + }, + defaultPassword: siteDefaultPassword, + promptForPasswordOnConsole: promptForPasswordOnConsole).Result; - Console.WriteLine( - "Server response: " + httpResponse.StatusCode + "\n" + - responseContentString); + var responseContentString = httpResponse.Content.ReadAsStringAsync().Result; - object responseBodyReport = responseContentString; + Console.WriteLine( + "Server response: " + httpResponse.StatusCode + "\n" + + responseContentString); - try - { - responseBodyReport = - Newtonsoft.Json.JsonConvert.DeserializeObject(responseContentString); - } - catch { } + object responseBodyReport = responseContentString; - responseFromServer = new SetElmAppStateReport.ResponseFromServerStruct - { - statusCode = (int)httpResponse.StatusCode, - body = responseBodyReport, - }; + try + { + responseBodyReport = + Newtonsoft.Json.JsonConvert.DeserializeObject((string)responseBodyReport); } + catch { } + + responseFromServer = new SetElmAppStateReport.ResponseFromServerStruct + { + statusCode = (int)httpResponse.StatusCode, + body = responseBodyReport, + }; return new SetElmAppStateReport { @@ -838,8 +886,8 @@ string getCurrentValueOfEnvironmentVariable() => // https://docs.microsoft.com/en-us/previous-versions//cc723564(v=technet.10)?redirectedfrom=MSDN#XSLTsection127121120120 Console.WriteLine( - "I added the path '" + executableDirectoryPath + "' to the '" + environmentVariableName + - "' environment variable for the current user account. You will be able to use the '" + commandName + "' command in newer instances of the Command Prompt."); + "I added the path '" + executableDirectoryPath + "' to the '" + environmentVariableName + + "' environment variable for the current user account. You will be able to use the '" + commandName + "' command in newer instances of the Command Prompt."); }); var executableIsRegisteredOnPath = diff --git a/implement/elm-fullstack/elm-fullstack.csproj b/implement/elm-fullstack/elm-fullstack.csproj index 83d8eb23..7ac66e18 100644 --- a/implement/elm-fullstack/elm-fullstack.csproj +++ b/implement/elm-fullstack/elm-fullstack.csproj @@ -15,6 +15,7 @@ +