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.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/Modules/Api.Package.fs b/src/deploy.web/Fake.Deploy.Web/Modules/Api.Package.fs index 2f3a710fdc6..319c96b9b91 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() @@ -58,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 @@ -69,9 +64,9 @@ 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, 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 daf1cd2cd2e..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 @@ -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', @@ -46,7 +58,8 @@ function AgentViewModel() { contentType: 'application/json' }).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); @@ -97,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) { @@ -111,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); } }); 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 +