Https paths for workflow argument (#62)

* Adding blueprint provider for https paths

* enables you to deploy with a publc url as the workflow argument
* logs references to template files as they're used, like any http calls
* github repo urls are re-written as the corresponding raw file location

* Updating readme to show running from url in Getting Started

Resolves #59
This commit is contained in:
Louis DeJardin 2018-10-10 12:52:23 -07:00 коммит произвёл GitHub
Родитель 04848c3c88
Коммит 075bcfbf1f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 299 добавлений и 7 удалений

Просмотреть файл

@ -44,7 +44,16 @@ If you want to use a package manager:
## Getting Started
From a console window, `mkdir demo` to create a new subfolder.
An existing workflow can be executed directly from a public web server. You
can run any of the [examples][Atlas Examples] in this repository with the `atlas deploy` command:
```
atlas deploy https://github.com/Microsoft/Atlas/tree/master/examples/101-messages
```
#### Creating a new workflow
To create a new workflow, from a console window execute `mkdir demo` to create a new subfolder.
Add a `demo/workflow.yaml` file to declare operations:
@ -55,7 +64,7 @@ operations:
- message: "All values: {{ json . }}"
```
Add a `hello/values.yaml` file to declare defaults:
Add a `demo/values.yaml` file to declare defaults:
```
info:
@ -77,7 +86,9 @@ Atlas
- All values: {"info": {"greeting": "Hello", "name": "Atlas"}}
```
Clone the [Atlas Examples](https://github.com/Microsoft/Atlas/tree/master/examples) folder to see additional
#### Exploring the examples
You can also clone the Atlas GitHub repo to explore the [examples][Atlas Examples] and see
kinds of operations Atlas can perform.
```
@ -183,6 +194,7 @@ email to ensure we received your original message. Further information, includin
the [Security TechCenter](https://technet.microsoft.com/en-us/security/default).
[Atlas Logo]: https://github.com/Microsoft/Atlas/raw/master/docs/icon-128.png
[Atlas Examples]: https://github.com/Microsoft/Atlas/tree/master/examples
[Handlebars]: http://handlebarsjs.com/
[YAML]: http://yaml.org/
[JSON]: http://json.org/

Просмотреть файл

@ -4,7 +4,7 @@
using System;
using System.IO;
namespace Microsoft.Atlas.CommandLine.Blueprints
namespace Microsoft.Atlas.CommandLine.Blueprints.Providers
{
public class ArchiveBlueprintPackage : IBlueprintPackage
{

Просмотреть файл

@ -3,7 +3,7 @@
using System.IO;
namespace Microsoft.Atlas.CommandLine.Blueprints
namespace Microsoft.Atlas.CommandLine.Blueprints.Providers
{
public class ArchiveBlueprintPackageProvider : IBlueprintPackageProvider
{

Просмотреть файл

@ -3,7 +3,7 @@
using System.IO;
namespace Microsoft.Atlas.CommandLine.Blueprints
namespace Microsoft.Atlas.CommandLine.Blueprints.Providers
{
public class DirectoryBlueprintPackage : IBlueprintPackage
{

Просмотреть файл

@ -3,7 +3,7 @@
using System.IO;
namespace Microsoft.Atlas.CommandLine.Blueprints
namespace Microsoft.Atlas.CommandLine.Blueprints.Providers
{
public class DirectoryBlueprintPackageProvider : IBlueprintPackageProvider
{

Просмотреть файл

@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.IO;
using System.Net.Http;
using Microsoft.Atlas.CommandLine.OAuth2;
namespace Microsoft.Atlas.CommandLine.Blueprints.Providers
{
internal class HttpsFilesBlueprintPackage : IBlueprintPackage
{
private readonly IHttpClientFactory _httpClientFactory;
private UriParts _blueprintUri;
private HttpClient _httpClient;
public HttpsFilesBlueprintPackage(IHttpClientFactory httpClientFactory, UriParts blueprintUri)
{
_httpClientFactory = httpClientFactory;
_blueprintUri = blueprintUri;
_httpClient = _httpClientFactory.Create(auth: null);
}
public bool Exists(string path)
{
var request = new HttpRequestMessage(HttpMethod.Head, _blueprintUri.Append(path).ToString());
var response = _httpClient.SendAsync(request).GetAwaiter().GetResult();
var statusCode = (int)response.StatusCode;
if (statusCode == 404)
{
return false;
}
response.EnsureSuccessStatusCode();
return true;
}
public TextReader OpenText(string path)
{
var request = new HttpRequestMessage(HttpMethod.Get, _blueprintUri.Append(path).ToString());
var response = _httpClient.SendAsync(request).GetAwaiter().GetResult();
response.EnsureSuccessStatusCode();
var text = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
return new StringReader(text);
}
}
}

Просмотреть файл

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Atlas.CommandLine.OAuth2;
namespace Microsoft.Atlas.CommandLine.Blueprints.Providers
{
public class HttpsFilesBlueprintPackageProvider : IBlueprintPackageProvider
{
private readonly IHttpClientFactory _httpClientFactory;
public HttpsFilesBlueprintPackageProvider(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public IBlueprintPackage TryGetBlueprintPackage(string blueprint)
{
if (!blueprint.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
{
return null;
}
var uriParts = UriParts.Parse(blueprint);
uriParts.RewriteGitHubUris();
uriParts.RemoveWorkflowYaml();
return new HttpsFilesBlueprintPackage(_httpClientFactory, uriParts);
}
}
}

Просмотреть файл

@ -0,0 +1,131 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
namespace Microsoft.Atlas.CommandLine.Blueprints.Providers
{
public class UriParts
{
private static readonly HostString _github = new HostString("github.com");
private static readonly HostString _githubusercontent = new HostString("raw.githubusercontent.com");
private static readonly PathString _tree = new PathString("/tree");
private static readonly PathString _blob = new PathString("/blob");
private static readonly PathString _workflowYaml = new PathString("/workflow.yaml");
public string Scheme { get; set; }
public HostString Host { get; set; }
public PathString Path { get; set; }
public QueryString Query { get; set; }
public FragmentString Fragment { get; set; }
public static UriParts Parse(string uri)
{
UriHelper.FromAbsolute(
uri,
out var scheme,
out var host,
out var path,
out var query,
out var fragment);
return new UriParts
{
Scheme = scheme,
Host = host,
Path = path,
Query = query,
Fragment = fragment,
};
}
public UriParts Append(string path)
{
return new UriParts
{
Scheme = Scheme,
Host = Host,
Path = Path + new PathString("/" + path),
Query = Query,
Fragment = Fragment,
};
}
public override string ToString()
{
return Scheme + "://" + Host + Path + Query + Fragment;
}
public bool RewriteGitHubUris()
{
if (!Host.Equals(_github))
{
return false;
}
var segments = SplitPathSegments(Path);
if (segments.Count < 3)
{
return false;
}
if (!segments[2].Equals(_tree) && !segments[2].Equals(_blob))
{
return false;
}
segments.RemoveAt(2);
Host = _githubusercontent;
Path = segments.Aggregate(default(PathString), (a, b) => a + b);
return true;
}
public bool RemoveWorkflowYaml()
{
var result = new List<PathString>();
var segments = SplitPathSegments(Path);
if (segments.Count < 1)
{
return false;
}
if (!segments.Last().Equals(_workflowYaml))
{
return false;
}
segments.RemoveAt(segments.Count() - 1);
Path = segments.Aggregate(default(PathString), (a, b) => a + b);
return true;
}
public List<PathString> SplitPathSegments(PathString path)
{
var segments = new List<PathString>();
var slashIndex = 0;
while (slashIndex < path.Value.Length)
{
var nextIndex = path.Value.IndexOf('/', slashIndex + 1);
if (nextIndex < 0)
{
nextIndex = path.Value.Length;
}
segments.Add(new PathString(path.Value.Substring(slashIndex, nextIndex - slashIndex)));
slashIndex = nextIndex;
}
return segments;
}
}
}

Просмотреть файл

@ -25,6 +25,7 @@
<PackageReference Include="Handlebars.Net" Version="1.9.5" />
<PackageReference Include="JmesPath.Net" Version="1.0.101" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />

Просмотреть файл

@ -7,6 +7,7 @@ using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.Atlas.CommandLine.Accounts;
using Microsoft.Atlas.CommandLine.Blueprints;
using Microsoft.Atlas.CommandLine.Blueprints.Providers;
using Microsoft.Atlas.CommandLine.Commands;
using Microsoft.Atlas.CommandLine.ConsoleOutput;
using Microsoft.Atlas.CommandLine.JsonClient;
@ -84,6 +85,7 @@ namespace Microsoft.Atlas.CommandLine
.AddTransient<IPatternMatcherFactory, PatternMatcherFactory>()
.AddTransient<IBlueprintManager, BlueprintManager>()
.AddTransient<IBlueprintPackageProvider, HttpsFilesBlueprintPackageProvider>()
.AddTransient<IBlueprintPackageProvider, DirectoryBlueprintPackageProvider>()
.AddTransient<IBlueprintPackageProvider, ArchiveBlueprintPackageProvider>()

Просмотреть файл

@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.AspNetCore.Http;
using Microsoft.Atlas.CommandLine.Blueprints.Providers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.Atlas.CommandLine.Tests.Blueprints.Providers
{
[TestClass]
public class UriPartsTests
{
[TestMethod]
public void UriIsParsedIntoParts()
{
var parts = UriParts.Parse("https://example.com/one/two?three=four&five=six#/seven");
Assert.AreEqual("https", parts.Scheme);
Assert.AreEqual(new HostString("example.com"), parts.Host);
Assert.AreEqual(new PathString("/one") + new PathString("/two"), parts.Path);
Assert.AreEqual(QueryString.Create("three", "four") + QueryString.Create("five", "six"), parts.Query);
Assert.AreEqual(new FragmentString("#/seven"), parts.Fragment);
}
[TestMethod]
public void UriBecomesStringAgain()
{
var parts = new UriParts
{
Scheme = "https",
Host = new HostString("example.com"),
Path = new PathString("/one") + new PathString("/two"),
Query = QueryString.Create("three", "four") + QueryString.Create("five", "six"),
Fragment = new FragmentString("#/seven"),
};
Assert.AreEqual("https://example.com/one/two?three=four&five=six#/seven", parts.ToString());
}
[TestMethod]
[DataRow("https://github.com/name1/name2/tree/master/stable/workflow-name", "https://raw.githubusercontent.com/name1/name2/master/stable/workflow-name")]
[DataRow("https://github.com/name1/name2/tree/master/stable/workflow-name/", "https://raw.githubusercontent.com/name1/name2/master/stable/workflow-name/")]
[DataRow("https://github.com/name1/name2/blob/master/README.md", "https://raw.githubusercontent.com/name1/name2/master/README.md")]
public void GitHubPathsChangedToRawFilePaths(string originalPath, string expectedPath)
{
var parts = UriParts.Parse(originalPath);
parts.RewriteGitHubUris();
Assert.AreEqual(expectedPath, parts.ToString());
}
[TestMethod]
[DataRow("https://github.com/name1/name2/tree/master/stable/workflow-name/workflow.yaml", "https://github.com/name1/name2/tree/master/stable/workflow-name")]
[DataRow("https://raw.githubusercontent.com/name1/name2/master/stable/workflow-name/workflow.yaml", "https://raw.githubusercontent.com/name1/name2/master/stable/workflow-name")]
[DataRow("https://anywhere.com/path/workflow.yaml?x=4#y", "https://anywhere.com/path?x=4#y")]
public void WorkflowYamlRemovedFromPath(string originalPath, string expectedPath)
{
var parts = UriParts.Parse(originalPath);
parts.RemoveWorkflowYaml();
Assert.AreEqual(expectedPath, parts.ToString());
}
}
}