Uploading binary downloads to cdn (#8)

* Adding example to create cdn and storage account

* Copying examples folder to artifacts drop location

* Adding support for binary data and files

* Each cdn rule may have only one condition

* Protecting against OS newline differences

* Updating install description and status badges
This commit is contained in:
Louis DeJardin 2018-09-11 12:44:15 -07:00 коммит произвёл GitHub
Родитель 374d805c05
Коммит 8d258a6a2c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
22 изменённых файлов: 581 добавлений и 8 удалений

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

@ -2,7 +2,10 @@
# Atlas
[![Build Status]][Build Latest]
[![Build Status]][Build Latest]
[![Zip Status]][Zip Latest]
[![Tarball Status]][Tarball Latest]
[![Choco Status]][Choco Latest]
![Atlas Logo]
@ -17,6 +20,20 @@ With Atlas you can make the configuration of everything from CI/CD to production
----
## Install
Daily builds of the Atlas CLI are available as self-contained downloads.
| Platform | [Master branch (0.1)][Master Branch] | [Latest build][Latest Json] |
|:------:|:------:|:------:|
| **Windows x64** | [Download latest zip][Zip Latest] | [![Zip Status]][Zip Latest] |
| **Linux x64** | [Download latest tar.gz][Zip Latest] | [![Tarball Status]][Tarball Latest] |
If you want to use a package manager:
#### Chocolatey
* [Chocolatey](https://chocolatey.org/) users can use `choco install atlas-cli -s https://www.myget.org/F/atlas-ci`
## Features
* [YAML] or [JSON] syntax to define workflows and input parameters
@ -88,8 +105,16 @@ the [Security TechCenter](https://technet.microsoft.com/en-us/security/default).
[YAML]: http://yaml.org/
[JSON]: http://json.org/
[JMESPath]: http://jmespath.org/
[Build Status]: https://msasg.visualstudio.com/Falcon/_apis/build/status/Atlas-CI?branch=master
[Build Latest]: https://msasg.visualstudio.com/Falcon/_build/latest?definitionId=6598&branch=master
[Azure RM REST API]: https://docs.microsoft.com/en-us/rest/api/azure/
[Azure AD REST API]: https://docs.microsoft.com/en-us/rest/api/graphrbac/
[VSTS REST API]: https://docs.microsoft.com/en-us/rest/api/vsts/?view=vsts-rest-5.0
[Build Status]: https://msasg.visualstudio.com/Falcon/_apis/build/status/Atlas-CI?branch=master
[Build Latest]: https://msasg.visualstudio.com/Falcon/_build/latest?definitionId=6598&branch=master
[Choco Status]: https://img.shields.io/myget/atlas-ci/vpre/atlas-cli.svg?label=choco
[Choco Latest]: #chocolatey
[Zip Status]: https://img.shields.io/badge/dynamic/json.svg?label=win-x64&url=https%3A%2F%2Fepjdcwdl4ufyw54.azureedge.net%2Fdownloads%2Flatest.json&query=%24[%27win10-x64%27].version
[Zip Latest]: https://epjdcwdl4ufyw54.azureedge.net/downloads/atlas-latest-win10-x64.zip
[Tarball Status]: https://img.shields.io/badge/dynamic/json.svg?label=linux-x64&url=https%3A%2F%2Fepjdcwdl4ufyw54.azureedge.net%2Fdownloads%2Flatest.json&query=%24[%27linux-x64%27].version
[Tarball Latest]: https://epjdcwdl4ufyw54.azureedge.net/downloads/atlas-latest-linux-x64.tar.gz
[Master Branch]: https://github.com/microsoft/atlas/tree/master
[Latest Json]: https://epjdcwdl4ufyw54.azureedge.net/downloads/latest.json

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

@ -71,6 +71,12 @@ steps:
configuration: Release
packDirectory: '$(Build.ArtifactStagingDirectory)/chocolatey'
- task: CopyFiles@2
displayName: 'copy examples to $(Build.ArtifactStagingDirectory)'
inputs:
SourceFolder: examples
TargetFolder: '$(Build.ArtifactStagingDirectory)/examples'
- script: |
npm install -g typescript
npm install -g tfx-cli

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

@ -0,0 +1,11 @@
# https://docs.microsoft.com/en-us/rest/api/resources/deployments/createorupdate
method: PUT
url: https://management.azure.com/subscriptions/{{azure.subscription}}/resourcegroups/{{azure.resourceGroupName}}/providers/Microsoft.Resources/deployments/{{deployment.name}}?api-version=2018-02-01
auth:
tenant: {{azure.tenant}}
resource: https://management.core.windows.net/
client: 04b07795-8ddb-461a-bbee-02f9e1bf7b46 # Azure CLI
body:
properties: {{{json deployment.properties}}}

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

@ -0,0 +1,9 @@
# https://docs.microsoft.com/en-us/rest/api/resources/deployments/get
method: GET
url: https://management.azure.com/subscriptions/{{azure.subscription}}/resourcegroups/{{azure.resourceGroupName}}/providers/Microsoft.Resources/deployments/{{deployment.name}}?api-version=2018-02-01
auth:
tenant: {{azure.tenant}}
resource: https://management.core.windows.net/
client: 04b07795-8ddb-461a-bbee-02f9e1bf7b46 # Azure CLI

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

@ -0,0 +1,11 @@
# https://docs.microsoft.com/en-us/rest/api/resources/deployments/validate
method: POST
url: https://management.azure.com/subscriptions/{{azure.subscription}}/resourcegroups/{{azure.resourceGroupName}}/providers/Microsoft.Resources/deployments/{{deployment.name}}/validate?api-version=2018-02-01
auth:
tenant: {{azure.tenant}}
resource: https://management.core.windows.net/
client: 04b07795-8ddb-461a-bbee-02f9e1bf7b46 # Azure CLI
body:
properties: {{{json deployment.properties}}}

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

@ -0,0 +1,11 @@
# https://docs.microsoft.com/en-us/rest/api/resources/resourcegroups/createorupdate
method: PUT
url: https://management.azure.com/subscriptions/{{azure.subscription}}/resourcegroups/{{azure.resourceGroupName}}?api-version=2018-02-01
auth:
tenant: {{azure.tenant}}
resource: https://management.core.windows.net/
client: 04b07795-8ddb-461a-bbee-02f9e1bf7b46 # Azure CLI
body:
location: {{azure.location}}

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

@ -0,0 +1,9 @@
# https://docs.microsoft.com/en-us/rest/api/resources/resourcegroups/get
method: GET
url: https://management.azure.com/subscriptions/{{azure.subscription}}/resourcegroups/{{azure.resourceGroupName}}?api-version=2018-02-01
auth:
tenant: {{azure.tenant}}
resource: https://management.core.windows.net/
client: 04b07795-8ddb-461a-bbee-02f9e1bf7b46 # Azure CLI

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

@ -0,0 +1,13 @@
# https://docs.microsoft.com/en-us/rest/api/storageservices/put-blob
method: PUT
url: https://{{storage.account}}.blob.core.windows.net/{{storage.container}}/{{blob.path}}
headers:
x-ms-date: {{ datetime add="PT0S" format="r" }}
x-ms-version: "2018-03-28"
x-ms-blob-type: BlockBlob
Authorization: "SharedKey {{storage.account}}:{{storage.key}}"
Content-Type: {{ blob.contentType }}
body: {{ binary file=blob.file }}

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

@ -0,0 +1,12 @@
# https://docs.microsoft.com/en-us/rest/api/storageservices/put-blob
method: PUT
url: https://{{storage.account}}.blob.core.windows.net/{{storage.container}}/{{blob.path}}
headers:
x-ms-date: {{ datetime add="PT0S" format="r" }}
x-ms-version: "2018-03-28"
x-ms-blob-type: BlockBlob
Authorization: "SharedKey {{storage.account}}:{{storage.key}}"
body: "{{blob.body}}"

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

@ -0,0 +1,12 @@
# https://docs.microsoft.com/en-us/rest/api/storageservices/put-blob
method: PUT
url: https://{{storage.account}}.blob.core.windows.net/{{storage.container}}?restype=container
auth:
tenant: {{azure.tenant}}
resource: https://storage.azure.com/
client: 04b07795-8ddb-461a-bbee-02f9e1bf7b46 # Azure CLI
headers:
x-ms-date: {{ datetime add="PT0S" }}
x-ms-version: "2018-03-28"

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

@ -0,0 +1,10 @@
# https://docs.microsoft.com/en-us/rest/api/storagerp/srp_json_get_storage_account_keys
method: POST
url: https://management.azure.com/subscriptions/{{azure.subscription}}/resourceGroups/{{azure.resourceGroupName}}/providers/Microsoft.Storage/storageAccounts/{{storage.account}}/listKeys?api-version=2018-03-01-preview
auth:
tenant: {{azure.tenant}}
resource: https://management.core.windows.net/
client: 04b07795-8ddb-461a-bbee-02f9e1bf7b46 # Azure CLI
secret: keys[*].value

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

@ -0,0 +1,182 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Location for all resources."
}
}
},
"variables": {
"storageAccountName": "[concat('sa', uniqueString(resourceGroup().id))]",
"endpointName": "[concat('ep', uniqueString(resourceGroup().id))]",
"profileName": "CdnProfile1"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[variables('storageAccountName')]",
"apiVersion": "2016-01-01",
"location": "[parameters('location')]",
"tags": {
"displayName": "[variables('storageAccountName')]"
},
"kind": "Storage",
"sku": {
"name": "{{ storage.sku }}"
},
"properties": {}
},
{
"type": "Microsoft.Storage/storageAccounts/blobServices/containers",
"name": "[concat(variables('storageAccountName'), '/default/', '{{ storage.container }}')]",
"dependsOn": [
"[variables('storageAccountName')]"
],
"apiVersion": "2018-03-01-preview",
"properties": {
"publicAccess": "Container"
}
},
{
"name": "[variables('profileName')]",
"type": "Microsoft.Cdn/profiles",
"location": "[parameters('location')]",
"apiVersion": "2017-10-12",
"tags": {
"displayName": "[variables('profileName')]"
},
"sku": {
"name": "Standard_Akamai"
},
"properties": {},
"resources": [
{
"apiVersion": "2017-10-12",
"name": "[variables('endpointName')]",
"type": "endpoints",
"dependsOn": [
"[variables('profileName')]",
"[variables('storageAccountName')]"
],
"location": "[parameters('location')]",
"tags": {
"displayName": "[variables('endpointName')]"
},
"properties": {
"originHostHeader": "[replace(replace(reference(variables('storageAccountName')).primaryEndpoints.blob,'https://',''),'/','')]",
"isHttpAllowed": false,
"isHttpsAllowed": true,
"queryStringCachingBehavior": "IgnoreQueryString",
"contentTypesToCompress": [
"text/plain",
"text/html",
"text/css",
"application/x-javascript",
"text/javascript"
],
"isCompressionEnabled": true,
"origins": [
{
"name": "origin1",
"properties": {
"hostName": "[replace(replace(reference(variables('storageAccountName')).primaryEndpoints.blob,'https://',''),'/','')]"
}
}
],
"deliveryPolicy":{
"rules": [
{
"conditions": [
{
"name": "UrlPath",
"parameters": {
"@odata.type": "Microsoft.Azure.Cdn.Models.DeliveryRuleUrlPathConditionParameters",
"matchType": "Literal",
"path": "/downloads/latest.json"
}
}
],
"actions": [
{
"name": "CacheExpiration",
"parameters": {
"@odata.type": "Microsoft.Azure.Cdn.Models.DeliveryRuleCacheExpirationActionParameters",
"cacheBehavior": "BypassCache",
"cacheType": "All"
}
}
],
"order": 1
},
{
"conditions": [
{
"name": "UrlPath",
"parameters": {
"@odata.type": "Microsoft.Azure.Cdn.Models.DeliveryRuleUrlPathConditionParameters",
"matchType": "Literal",
"path": "/downloads/atlas-latest-win10-x64.zip"
}
}
],
"actions": [
{
"name": "CacheExpiration",
"parameters": {
"@odata.type": "Microsoft.Azure.Cdn.Models.DeliveryRuleCacheExpirationActionParameters",
"cacheBehavior": "BypassCache",
"cacheType": "All"
}
}
],
"order": 2
},
{
"conditions": [
{
"name": "UrlPath",
"parameters": {
"@odata.type": "Microsoft.Azure.Cdn.Models.DeliveryRuleUrlPathConditionParameters",
"matchType": "Literal",
"path": "/downloads/atlas-latest-linux-x64.tar.gz"
}
}
],
"actions": [
{
"name": "CacheExpiration",
"parameters": {
"@odata.type": "Microsoft.Azure.Cdn.Models.DeliveryRuleCacheExpirationActionParameters",
"cacheBehavior": "BypassCache",
"cacheType": "All"
}
}
],
"order": 3
}
]
}
}
}
]
}
],
"outputs": {
"storageAccountName": {
"type": "string",
"value": "[variables('storageAccountName')]"
},
"hostName": {
"type": "string",
"value": "[reference(variables('endpointName')).hostName]"
},
"originHostHeader": {
"type": "string",
"value": "[reference(variables('endpointName')).originHostHeader]"
}
}
}

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

@ -0,0 +1,10 @@
{
"win10-x64": {
"version": "{{ latest.version }}",
"path": "atlas-{{ latest.version }}-win10-x64.zip"
},
"linux-x64": {
"version": "{{ latest.version }}",
"path": "atlas-{{ latest.version }}-linux-x64.tar.gz"
}
}

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

@ -0,0 +1,12 @@
azure:
# Your Azure Active Directory ID from https://ms.portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/Properties
# tenant: TENANT_ID
# Your Azure Subscription ID from https://ms.portal.azure.com/#blade/Microsoft_Azure_Billing/SubscriptionsBlade
# subscription: SUBSCRIPTION_ID
resourceGroupName: atlas-examples-402
location: eastus2
storage:
sku: Standard_LRS
container: downloads

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

@ -0,0 +1,109 @@
values: {{{ json }}}
# Declaring secret {{ secret vsts.token }}
operations:
- message: Validating inputs
- {condition: (!azure.tenant), output: { missing: {azure: {tenant: or --set azure.tenant=AAD_TENANT_ID }}}}
- {condition: (!azure.subscription), output: { missing: {azure: {subscription: or --set azure.subscription=AZURE_SUBSCRIPTION_ID }}}}
- message: Input validation failed
condition: (missing != null)
throw:
message: >
This workflow is missing information which is
needed to run.
Please add them to a values.yaml file in the
current directory, or provide the missing values
as command line options.
details: (missing)
- message: Preparing resource group
request: apis/azure/resourcegroups/apply.yaml
- message: Deploying cdn and storage account
values:
deployment:
name: cdn-and-storage-{{ guid (datetime add="PT0S") }}
properties:
mode: Incremental
template: {{> azuredeploy.json }}
parameters:
location:
value: {{ azure.location }}
operations:
- message: Creating deployment
request: apis/azure/deployments/apply.yaml
- message: Waiting for deployment to complete...
request: apis/azure/deployments/get.yaml
output:
deploymentStatus: (@)
repeat:
condition: deploymentStatus.properties.provisioningState == 'Accepted' || deploymentStatus.properties.provisioningState == 'Running'
delay: PT5S
timeout: PT4M
- message: Deployment failed
condition: deploymentStatus.properties.provisioningState != 'Succeeded'
throw:
message: (['Deployment provisioning state is "', deploymentStatus.properties.provisioningState, '". ', deploymentStatus.properties.error.message])
details: (deploymentStatus.properties.error)
output:
deployment: (deploymentStatus.properties.{state:provisioningState, error:error, outputs:outputs})
storage:
account: (deploymentStatus.properties.outputs.storageAccountName.value)
- message: Uploading files to storage
operations:
- message: Getting storage keys
request: apis/azure/storage/listkeys.yaml
output:
storage:
key: (keys[0].value)
- message: Sending latest.json
values:
blob:
path: latest.json
body: |
{{# indent 10 }}
{{> latest.json }}
{{/ indent }}
contentType: application/json
request: apis/azure/storage/blob-put.yaml
- message: Sending atlas-{{ latest.version }}-win10-x64.zip
values:
blob:
path: atlas-{{ latest.version }}-win10-x64.zip
file: {{ latest.downloads }}/atlas-{{ latest.version }}-win10-x64.zip
contentType: application/zip
request: apis/azure/storage/blob-put-binary.yaml
- message: Sending atlas-{{ latest.version }}-linux-x64.tar.gz
values:
blob:
path: atlas-{{ latest.version }}-linux-x64.tar.gz
file: {{ latest.downloads }}/atlas-{{ latest.version }}-linux-x64.tar.gz
contentType: application/x-gtar
request: apis/azure/storage/blob-put-binary.yaml
- message: Sending atlas-latest-win10-x64.zip
values:
blob:
path: atlas-latest-win10-x64.zip
file: {{ latest.downloads }}/atlas-{{ latest.version }}-win10-x64.zip
contentType: application/zip
request: apis/azure/storage/blob-put-binary.yaml
- message: Sending atlas-latest-linux-x64.tar.gz
values:
blob:
path: atlas-latest-linux-x64.tar.gz
file: {{ latest.downloads }}/atlas-{{ latest.version }}-linux-x64.tar.gz
contentType: application/x-gtar
request: apis/azure/storage/blob-put-binary.yaml

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

@ -47,6 +47,10 @@ namespace Microsoft.Atlas.CommandLine.JsonClient
{
request.Content = new StringContent((string)jsonRequest.body);
}
else if (jsonRequest.body is byte[])
{
request.Content = new ByteArrayContent((byte[])jsonRequest.body);
}
else
{
var memoryStream = new MemoryStream();

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

@ -85,6 +85,7 @@ namespace Microsoft.Atlas.CommandLine
.AddSingleton<ITemplateEngineFactory, TemplateEngineFactory>()
.AddSingleton<TemplateEngineServices>()
.AddTransient<ITemplateHelperProvider, SshHelpers>()
.AddTransient<ITemplateHelperProvider, BinaryHelpers>()
.AddSingleton<CommandLineApplicationServices>()

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

@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using YamlDotNet.Core;
using YamlDotNet.Serialization;
namespace Microsoft.Atlas.CommandLine.Serialization
{
public class ByteArrayConverter : IYamlTypeConverter
{
public bool Accepts(Type type)
{
return type == typeof(byte[]);
}
public object ReadYaml(IParser parser, Type type)
{
var scalar = (YamlDotNet.Core.Events.Scalar)parser.Current;
var bytes = Convert.FromBase64String(scalar.Value);
parser.MoveNext();
return bytes;
}
public void WriteYaml(IEmitter emitter, object value, Type type)
{
var bytes = (byte[])value;
emitter.Emit(new YamlDotNet.Core.Events.Scalar(
null,
"tag:yaml.org,2002:binary",
Convert.ToBase64String(bytes),
ScalarStyle.Plain,
false,
false));
}
}
}

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

@ -12,10 +12,30 @@ namespace Microsoft.Atlas.CommandLine.Serialization
{
public YamlSerializers()
{
YamlDeserializer = new DeserializerBuilder().WithNodeTypeResolver(new NonStringScalarTypeResolver()).Build();
YamlSerializer = new SerializerBuilder().DisableAliases().WithEventEmitter(DoubleQuoteAmbiguousStringScalarEmitter.Factory).Build();
JsonSerializer = new SerializerBuilder().DisableAliases().JsonCompatible().Build();
ValueSerialier = new SerializerBuilder().DisableAliases().JsonCompatible().BuildValueSerializer();
YamlDeserializer = new DeserializerBuilder()
.WithNodeTypeResolver(new NonStringScalarTypeResolver())
.WithTagMapping("tag:yaml.org,2002:binary", typeof(byte[]))
.WithTypeConverter(new ByteArrayConverter())
.Build();
YamlSerializer = new SerializerBuilder()
.DisableAliases()
.WithEventEmitter(DoubleQuoteAmbiguousStringScalarEmitter.Factory)
.WithTypeConverter(new ByteArrayConverter())
.Build();
JsonSerializer = new SerializerBuilder()
.DisableAliases()
.JsonCompatible()
.WithTypeConverter(new ByteArrayConverter())
.Build();
ValueSerialier = new SerializerBuilder()
.DisableAliases()
.JsonCompatible()
.WithTypeConverter(new ByteArrayConverter())
.BuildValueSerializer();
JTokenTranserializer = JTokenTranserializerImpl;
}

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

@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using HandlebarsDotNet;
using Microsoft.Atlas.CommandLine.Secrets;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
namespace Microsoft.Atlas.CommandLine.Templates.Helpers
{
public class BinaryHelpers : TemplateHelperProvider
{
private readonly ISecretTracker _secretTracker;
public BinaryHelpers(ISecretTracker secretTracker)
{
_secretTracker = secretTracker;
}
[Description("binary")]
public void SshKeyGen(TextWriter output, dynamic context, params object[] arguments)
{
var options = arguments.Last() as IDictionary<string, object>;
if (options != null)
{
if (options.TryGetValue("file", out var file))
{
// TODO: file reading service, and restricted source locations
var bytes = File.ReadAllBytes(file.ToString());
output.Write($"!!binary {Convert.ToBase64String(bytes)}");
return;
}
}
throw new Exception("'binary' helper requires a file='path' option");
}
}
}

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

@ -142,7 +142,8 @@ namespace Microsoft.Atlas.CommandLine.Templates
var options = arguments.Last() as IDictionary<string, object>;
if (options != null)
{
if (options.TryGetValue("provider", out var provider) && string.Equals(provider?.ToString(), "RNGCryptoServiceProvider", StringComparison.OrdinalIgnoreCase))
if (options.TryGetValue("provider", out var provider) &&
string.Equals(provider?.ToString(), "RNGCryptoServiceProvider", StringComparison.OrdinalIgnoreCase))
{
using (var rng = new RNGCryptoServiceProvider())
{

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

@ -196,6 +196,27 @@ eta:
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void BinaryCanBeSerialized()
{
var services = Program.AddServices(new ServiceCollection()).BuildServiceProvider();
var serializers = services.GetRequiredService<IYamlSerializers>();
var data = new Dictionary<object, object>
{
{ "data", new byte[] { 0, 1, 2, 3, 254, 255 } },
};
var yaml = serializers.YamlSerializer.Serialize(data);
var json = serializers.JsonSerializer.Serialize(data);
Assert.AreEqual("data: !!binary AAECA/7/", yaml.Replace("\r", string.Empty).Replace("\n", string.Empty));
Assert.AreEqual(@"{""data"": !!binary AAECA/7/}", json.Replace("\r", string.Empty).Replace("\n", string.Empty));
}
private void AssertAreEqual(object expected, object actual)
{
Assert.AreSame(expected?.GetType(), actual?.GetType());