From dfa94af32c5567f2817e6a70f17bf5c0045acc84 Mon Sep 17 00:00:00 2001 From: ashic Date: Fri, 3 Apr 2015 10:51:37 +0100 Subject: [PATCH 1/5] deploy website's agent page doesn't give js error when there are no deployments on target node --- src/deploy.web/Fake.Deploy.Web/Scripts/app/Home.Agent.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/deploy.web/Fake.Deploy.Web/Scripts/app/Home.Agent.js b/src/deploy.web/Fake.Deploy.Web/Scripts/app/Home.Agent.js index daf1cd2cd2e..22ebe58c377 100644 --- a/src/deploy.web/Fake.Deploy.Web/Scripts/app/Home.Agent.js +++ b/src/deploy.web/Fake.Deploy.Web/Scripts/app/Home.Agent.js @@ -46,7 +46,8 @@ function AgentViewModel() { contentType: 'application/json' }).done(function (data) { self.deployments([]); - $.each(data.values, function (i, d) { + + $.each(data.values || [], function (i, d) { $.each(d, function (i2, dep) { var deployment = ko.mapping.fromJS(dep); self.deployments.push(deployment); From 92e7cf15e8bf810144085ad67a79418bbcb73aac Mon Sep 17 00:00:00 2001 From: ashic Date: Fri, 3 Apr 2015 11:55:22 +0100 Subject: [PATCH 2/5] deploy web success and error messages are now correctly displayed in messages box --- .../Fake.Deploy.Web/Modules/Api.Package.fs | 2 +- .../Fake.Deploy.Web/Scripts/app/Home.Agent.js | 27 ++++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/deploy.web/Fake.Deploy.Web/Modules/Api.Package.fs b/src/deploy.web/Fake.Deploy.Web/Modules/Api.Package.fs index 2f3a710fdc6..dbce2e1952d 100644 --- a/src/deploy.web/Fake.Deploy.Web/Modules/Api.Package.fs +++ b/src/deploy.web/Fake.Deploy.Web/Modules/Api.Package.fs @@ -71,7 +71,7 @@ type ApiPackage (dataProvider : IDataProvider) as http = |> Seq.map(fun file -> match postDeploymentPackage url file [||] with | Failure(err) -> - file, Some err, HttpStatusCode.InternalServerError, None + file, Some err, HttpStatusCode.InternalServerError, Some(err) | Success a -> if File.Exists(file) then File.Delete(file) file, None, HttpStatusCode.Created, Some a diff --git a/src/deploy.web/Fake.Deploy.Web/Scripts/app/Home.Agent.js b/src/deploy.web/Fake.Deploy.Web/Scripts/app/Home.Agent.js index 22ebe58c377..9f874da4ce6 100644 --- a/src/deploy.web/Fake.Deploy.Web/Scripts/app/Home.Agent.js +++ b/src/deploy.web/Fake.Deploy.Web/Scripts/app/Home.Agent.js @@ -21,6 +21,18 @@ function AgentViewModel() { self.recentMessages = ko.observableArray(); + self.updateMessagesFrom = function(data) { + self.recentMessages([]); + if (data != null) { + var response = data.xhr().response; + var messages = []; + if (response) messages = $.parseJSON(response); + $.each(messages, function (i, msg) { + self.recentMessages.push(msg); + }); + } + }; + self.getAgentDetails = function () { $.ajax({ type: 'GET', @@ -98,13 +110,7 @@ function AgentViewModel() { $('#filePlaceHolder').modal('hide'); $('#selectPackageBtn').removeClass('hide'); toastr.info('Package deployed'); - self.recentMessages([]); - if (data != null && data.result !== undefined) { - $.each(data.result, function (i, msg) { - self.recentMessages.push(msg); - }); - } - + self.updateMessagesFrom(data); self.refreshDeploymentsForAgent(); }, fail: function (e, data) { @@ -112,12 +118,7 @@ function AgentViewModel() { $('#selectPackageBtn').removeClass('hide'); $('#filePlaceHolder').modal('hide'); toastr.error('Package deployment failed'); - self.recentMessages([]); - if (data != null && data.result !== undefined) { - $.each(data.result, function (i, msg) { - self.recentMessages.push(msg); - }); - } + self.updateMessagesFrom(data); } }); From 68ef072b9c8f8b62ccd5f158f3c032129295afca Mon Sep 17 00:00:00 2001 From: ashic Date: Fri, 3 Apr 2015 15:21:34 +0100 Subject: [PATCH 3/5] deploy.web was using values for agent response, when the field returned is called fields --- src/deploy.web/Fake.Deploy.Web/Modules/Api.Agent.fs | 2 +- src/deploy.web/Fake.Deploy.Web/Scripts/app/Home.Agent.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/deploy.web/Fake.Deploy.Web/Modules/Api.Agent.fs b/src/deploy.web/Fake.Deploy.Web/Modules/Api.Agent.fs index 3814a039e67..060483e8600 100644 --- a/src/deploy.web/Fake.Deploy.Web/Modules/Api.Agent.fs +++ b/src/deploy.web/Fake.Deploy.Web/Modules/Api.Agent.fs @@ -16,7 +16,7 @@ open Fake.Deploy.Web.Module.NancyOp type AgentResponse<'t> = { case : string - values : 't [] } + fields : 't [] } type ApiAgent(dataProvider : IDataProvider, agentProxy : AgentProxy) as http = inherit FakeModule("/api/v1/agent") diff --git a/src/deploy.web/Fake.Deploy.Web/Scripts/app/Home.Agent.js b/src/deploy.web/Fake.Deploy.Web/Scripts/app/Home.Agent.js index 9f874da4ce6..58f1236c20c 100644 --- a/src/deploy.web/Fake.Deploy.Web/Scripts/app/Home.Agent.js +++ b/src/deploy.web/Fake.Deploy.Web/Scripts/app/Home.Agent.js @@ -59,7 +59,7 @@ function AgentViewModel() { }).done(function (data) { self.deployments([]); - $.each(data.values || [], function (i, d) { + $.each(data.fields || [], function (i, d) { $.each(d, function (i2, dep) { var deployment = ko.mapping.fromJS(dep); self.deployments.push(deployment); From e1e5c4dc1465d556fa52237e1bda5e97e3582e96 Mon Sep 17 00:00:00 2001 From: ashic Date: Fri, 3 Apr 2015 18:54:40 +0100 Subject: [PATCH 4/5] removing dead code --- src/deploy.web/Fake.Deploy.Web/Modules/Api.Package.fs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/deploy.web/Fake.Deploy.Web/Modules/Api.Package.fs b/src/deploy.web/Fake.Deploy.Web/Modules/Api.Package.fs index dbce2e1952d..50f59f2a3cc 100644 --- a/src/deploy.web/Fake.Deploy.Web/Modules/Api.Package.fs +++ b/src/deploy.web/Fake.Deploy.Web/Modules/Api.Package.fs @@ -22,17 +22,6 @@ type ApiPackage (dataProvider : IDataProvider) as http = let packageTemp = Path.Combine(appdata.FullName, "Package_Temp") - let deploy packageFileName agent = - let toStr (msgs:seq) = - msgs |> Seq.map(fun msg -> sprintf "%s: %s" (msg.Timestamp.ToString("yyyy-MM-dd HH:mm:ss")) msg.Message ) - - let url = Uri(agent.Address, "/fake/") - let response = postDeploymentPackage url.AbsoluteUri packageFileName [||] - match response with - | Failure x -> { Agent = agent.Name; Messages = toStr x.Messages; Success = not <| x.IsError; Error = x.Exception.ToString() } - | Success x -> { Agent = agent.Name; Messages = toStr x.Messages; Success = not <| x.IsError; Error = "" } - | _ -> { Agent = agent.Name; Messages = []; Success = false; Error = "Unexpected response from agent" } - do http.post "/rollback" (fun p -> let body = http.Bind() From 8377133f079684cae4bcc035a94d9bb2690e0ba6 Mon Sep 17 00:00:00 2001 From: ashic Date: Sat, 4 Apr 2015 00:35:44 +0100 Subject: [PATCH 5/5] Argument passing from Fake.Deploy.Web Currently, no arguments are passed to the agent from Deploy.Web. This pull request passes env=EnvironmentName for the agent. This lets us parameterize deployments. Currently, the mechanism of passing multiple arguments alongside a package POST adds them as a ; separated string, which is then available in the deployment script verbatim. This pull request changes to the approach outlined by RFC 2616 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html) where multiple values are added to the header comma separated and quoted, with only quote characters percent encoded. To support existing clients, POSTs without comma separated quoted format are still passed verbatim. --- .../Fake.Deploy.Lib/Fake.Deploy.Lib.fsproj | 1 + .../Fake.Deploy.Lib/FakeDeployAgentHelper.fs | 2 +- src/app/Fake.Deploy.Lib/HttpHeaderHelper.fs | 34 ++++++++++++++ src/app/Fake.Deploy/DeploymentAgent.fs | 7 +-- .../Fake.Deploy.Web/Modules/Api.Package.fs | 8 +++- .../Http/HttpHeaderParsingSpecs.cs | 44 +++++++++++++++++++ .../Test.Fake.Deploy/Test.Fake.Deploy.csproj | 1 + 7 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 src/app/Fake.Deploy.Lib/HttpHeaderHelper.fs create mode 100644 src/test/Test.Fake.Deploy/Http/HttpHeaderParsingSpecs.cs diff --git a/src/app/Fake.Deploy.Lib/Fake.Deploy.Lib.fsproj b/src/app/Fake.Deploy.Lib/Fake.Deploy.Lib.fsproj index 83e4d366192..90917ea7460 100644 --- a/src/app/Fake.Deploy.Lib/Fake.Deploy.Lib.fsproj +++ b/src/app/Fake.Deploy.Lib/Fake.Deploy.Lib.fsproj @@ -49,6 +49,7 @@ + diff --git a/src/app/Fake.Deploy.Lib/FakeDeployAgentHelper.fs b/src/app/Fake.Deploy.Lib/FakeDeployAgentHelper.fs index d889b04a989..f1aa042d9c9 100644 --- a/src/app/Fake.Deploy.Lib/FakeDeployAgentHelper.fs +++ b/src/app/Fake.Deploy.Lib/FakeDeployAgentHelper.fs @@ -93,7 +93,7 @@ let uploadData (action: Action) (url: Url) (body: byte[]) = let uploadFile (action: Action) (url: Url) (file: FilePath) (args: string[]) = let req = webRequest url action - req.Headers.Add(scriptArgumentsHeaderName, String.Join (";", args)) + req.Headers.Add(scriptArgumentsHeaderName, args |> toHeaderValue) req.AllowWriteStreamBuffering <- false use fileStream = File.OpenRead file req.ContentLength <- fileStream.Length diff --git a/src/app/Fake.Deploy.Lib/HttpHeaderHelper.fs b/src/app/Fake.Deploy.Lib/HttpHeaderHelper.fs new file mode 100644 index 00000000000..6fb10280934 --- /dev/null +++ b/src/app/Fake.Deploy.Lib/HttpHeaderHelper.fs @@ -0,0 +1,34 @@ +[] +module Fake.HttpHeaderHelper +open System +open System.Text.RegularExpressions + +let toHeaderValue (values:string []) : string = + values + |> Array.map (fun x -> + x.Replace("\"", "%22") + |> sprintf "\"%s\"" + ) + |> fun strs -> System.String.Join(",", strs + ) + +let private regex = Regex("(\"[^\"]*\")(?:,(\"[^\"]*\"))*", RegexOptions.Compiled) +let fromHeaderValue (value:string) : string [] = + let matches = regex.Matches(value) + //back compat: existing agents not expecting quoted params will continue to function. + if matches.Count = 0 then [|value|] + else + matches |> Seq.cast + |> Seq.collect (fun (m:Match) -> m.Groups |> Seq.cast) + |> Seq.skip 1 + |> Seq.collect (fun (g:Group) -> + g.Captures |> Seq.cast |> Seq.map (fun (c:Capture) -> c.Value) + |> Seq.map (fun (x:string) -> + x.Substring(1, x.Length - 2) + |> fun y -> y.Replace("%22", "\"") + ) + ) + |> Array.ofSeq + + + \ No newline at end of file diff --git a/src/app/Fake.Deploy/DeploymentAgent.fs b/src/app/Fake.Deploy/DeploymentAgent.fs index 725836960ee..549afac0baf 100644 --- a/src/app/Fake.Deploy/DeploymentAgent.fs +++ b/src/app/Fake.Deploy/DeploymentAgent.fs @@ -19,9 +19,10 @@ let getBodyFromNancyRequest (ctx : Nancy.Request) = let getScriptArgumentsFromNancyRequest (ctx : Nancy.Request) = ctx.Headers - |> Seq.choose (fun pair -> if pair.Key = FakeDeployAgentHelper.scriptArgumentsHeaderName then Some pair.Value else None) - |> Seq.concat - |> Seq.toArray + |> Seq.choose (fun pair -> if pair.Key = FakeDeployAgentHelper.scriptArgumentsHeaderName then Some <| pair.Value else None) + |> Seq.head + |> Seq.head + |> fromHeaderValue let runDeployment workDir (ctx : Nancy.Request) = let packageBytes = getBodyFromNancyRequest ctx diff --git a/src/deploy.web/Fake.Deploy.Web/Modules/Api.Package.fs b/src/deploy.web/Fake.Deploy.Web/Modules/Api.Package.fs index 50f59f2a3cc..319c96b9b91 100644 --- a/src/deploy.web/Fake.Deploy.Web/Modules/Api.Package.fs +++ b/src/deploy.web/Fake.Deploy.Web/Modules/Api.Package.fs @@ -47,6 +47,12 @@ type ApiPackage (dataProvider : IDataProvider) as http = let agentId = http.Request.Form ?> "agentId" let agent = dataProvider.GetAgents [agentId] |> Seq.head let url = agent.Address.AbsoluteUri + "fake/" + let env = + [agent.EnvironmentId] + |>dataProvider.GetEnvironments + |> Seq.head + |> fun x -> x.Name + |> sprintf "env=%s" Directory.CreateDirectory(packageTemp) |> ignore let files = http.Request.Files @@ -58,7 +64,7 @@ type ApiPackage (dataProvider : IDataProvider) as http = let code, message = files |> Seq.map(fun file -> - match postDeploymentPackage url file [||] with + match postDeploymentPackage url file [|env|] with | Failure(err) -> file, Some err, HttpStatusCode.InternalServerError, Some(err) | Success a -> diff --git a/src/test/Test.Fake.Deploy/Http/HttpHeaderParsingSpecs.cs b/src/test/Test.Fake.Deploy/Http/HttpHeaderParsingSpecs.cs new file mode 100644 index 00000000000..0d239e72663 --- /dev/null +++ b/src/test/Test.Fake.Deploy/Http/HttpHeaderParsingSpecs.cs @@ -0,0 +1,44 @@ +using Fake; +using Machine.Specifications; + +namespace Test.Fake.Deploy.Http +{ + public class when_parsing_values_without_commas + { + private static string[] _results; + + private Because of = () => _results = HttpHeaderHelper.fromHeaderValue("\"param%22one\",\"second\""); + + private It should_parse_params_with_quotes = () => + _results[0].ShouldEqual("param\"one"); + private It should_parse_params_without_quotes = () => + _results[1].ShouldEqual("second"); + } + + + public class when_parsing_values_with_commas + { + private static string[] _results; + + private Because of = () => _results = HttpHeaderHelper.fromHeaderValue("\"param,%22one\",\"second\",\"another, %22 parameter\""); + + private It should_parse_params_with_comma_and_quotes = () => + _results[0].ShouldEqual("param,\"one"); + private It should_parse_params_without_quotes = () => + _results[1].ShouldEqual("second"); + private It should_parse_third_param = () => + _results[2].ShouldEqual("another, \" parameter"); + } + + public class when_parsing_unquoted_params + { + private static string[] _results; + + private Because of = () => _results = HttpHeaderHelper.fromHeaderValue("simple command"); + + private It should_parse_exact_value = () => + _results[0].ShouldEqual("simple command"); + } + + +} \ No newline at end of file diff --git a/src/test/Test.Fake.Deploy/Test.Fake.Deploy.csproj b/src/test/Test.Fake.Deploy/Test.Fake.Deploy.csproj index 660e99520df..86728dc3270 100644 --- a/src/test/Test.Fake.Deploy/Test.Fake.Deploy.csproj +++ b/src/test/Test.Fake.Deploy/Test.Fake.Deploy.csproj @@ -62,6 +62,7 @@ Extensions.cs +