зеркало из https://github.com/Azure/buffalo-azure.git
Add TODO issue numbers.
This commit is contained in:
Родитель
e37aeca9aa
Коммит
fc083137d5
|
@ -0,0 +1,21 @@
|
|||
package cmd
|
||||
|
||||
// DeploymentParameters enables easy marshaling of ARM Template deployment parameters.
|
||||
type DeploymentParameters struct {
|
||||
Schema string `json:"$schema"`
|
||||
ContentVersion string `json:"contentVersion"`
|
||||
Parameters map[string]DeploymentParameter `json:"parameters"`
|
||||
}
|
||||
|
||||
// DeploymentParameter is an individual entry in the parameter list.
|
||||
type DeploymentParameter struct {
|
||||
Value interface{} `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// NewDeploymentParameters creates a new instance of DeploymentParameters with reasonable defaults but no parameters.
|
||||
func NewDeploymentParameters() *DeploymentParameters {
|
||||
return &DeploymentParameters{
|
||||
Schema: "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#",
|
||||
ContentVersion: "1.0.0.0",
|
||||
}
|
||||
}
|
|
@ -23,8 +23,10 @@ package cmd
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
@ -127,9 +129,13 @@ const (
|
|||
// TemplateShorthand is the abbreviated means of using TemplateName.
|
||||
TemplateShorthand = "t"
|
||||
|
||||
// TemplateDefault
|
||||
TemplateDefault = "https://invalidtemplate.gobuffalo.io"
|
||||
templateUsage = "The Azure Resource Management template used to "
|
||||
// TemplateDefault is the name of the Template to use if no value was provided.
|
||||
TemplateDefault = "./azuredeploy.json"
|
||||
|
||||
// TemplateDefaultLink defines the link that will be used if no local rm-template is found, and a link wasn't
|
||||
// provided.
|
||||
TemplateDefaultLink = "https://aka.ms/buffalo-template"
|
||||
templateUsage = "The Azure Resource Management template which specifies the resources to provision."
|
||||
)
|
||||
|
||||
// These constants define a parameter that Azure subscription to own the resources created.
|
||||
|
@ -175,11 +181,15 @@ const (
|
|||
tenantUsage = "The ID (in form of a UUID) of the organization that the identity being used belongs to. "
|
||||
)
|
||||
|
||||
// These constants define a parameter which forces this program to ignore any ambient Azure settings available as
|
||||
// environment variables, and instead forces us to use Device Auth instead.
|
||||
const (
|
||||
DeviceAuthName = "use-device-auth"
|
||||
deviceAuthUsage = "Ignore --client-id and --client-secret, interactively authenticate instead."
|
||||
)
|
||||
|
||||
// These constants define a parameter which toggles whether or not status information will be printed as this program
|
||||
// executes.
|
||||
const (
|
||||
VerboseName = "verbose"
|
||||
VerboseShortname = "v"
|
||||
|
@ -187,6 +197,7 @@ const (
|
|||
)
|
||||
|
||||
var status *log.Logger
|
||||
var errLog *log.Logger
|
||||
|
||||
// provisionCmd represents the provision command
|
||||
var provisionCmd = &cobra.Command{
|
||||
|
@ -203,17 +214,19 @@ var provisionCmd = &cobra.Command{
|
|||
subscriptionID := provisionConfig.GetString(SubscriptionName)
|
||||
clientID := provisionConfig.GetString(ClientIDName)
|
||||
clientSecret := provisionConfig.GetString(ClientSecretName)
|
||||
status.Print("subscription selected: ", subscriptionID)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
auth, err := getAuthorizer(ctx, subscriptionID, clientID, clientSecret, provisionConfig.GetString(TenantIDName))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "unable to authenticate: ", err)
|
||||
errLog.Print("unable to authenticate: ", err)
|
||||
return
|
||||
}
|
||||
status.Print("tenant selected: ", provisionConfig.GetString(TenantIDName))
|
||||
status.Print("subscription selected: ", subscriptionID)
|
||||
templateLocation := provisionConfig.GetString(TemplateName)
|
||||
status.Println("template selected: ", templateLocation)
|
||||
|
||||
groups := resources.NewGroupsClient(subscriptionID)
|
||||
groups.Authorizer = auth
|
||||
|
@ -228,7 +241,7 @@ var provisionCmd = &cobra.Command{
|
|||
rgName := provisionConfig.GetString(ResoureGroupName)
|
||||
created, err := insertResourceGroup(ctx, groups, rgName, provisionConfig.GetString(LocationName))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "unable to fetch or create resource group %s: %v\n", rgName, err)
|
||||
errLog.Printf("unable to fetch or create resource group %s: %v\n", rgName, err)
|
||||
return
|
||||
}
|
||||
if created {
|
||||
|
@ -241,7 +254,36 @@ var provisionCmd = &cobra.Command{
|
|||
deployments := resources.NewDeploymentsClient(subscriptionID)
|
||||
deployments.Authorizer = auth
|
||||
|
||||
status.Print("Done.")
|
||||
status.Println("starting deployment")
|
||||
|
||||
params := NewDeploymentParameters()
|
||||
params.Parameters["database"] = DeploymentParameter{provisionConfig.GetString(DatabaseName)}
|
||||
params.Parameters["imageName"] = DeploymentParameter{provisionConfig.GetString(ImageName)}
|
||||
|
||||
template, err := getDeploymentTemplate(templateLocation)
|
||||
if err != nil {
|
||||
errLog.Print("unable to fetch template: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
template.Parameters = params
|
||||
template.Mode = resources.Incremental
|
||||
|
||||
fut, err := deployments.CreateOrUpdate(ctx, rgName, "buffalo-app", resources.Deployment{
|
||||
Properties: template,
|
||||
})
|
||||
if err != nil {
|
||||
errLog.Print("unable to start deployment: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = fut.WaitForCompletion(ctx, deployments.Client)
|
||||
if err != nil {
|
||||
errLog.Print("unable to poll for completion progress, your assets may or may not have finished provisioning")
|
||||
return
|
||||
}
|
||||
|
||||
status.Print("finished deployment")
|
||||
exitStatus = 0
|
||||
},
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
|
@ -253,7 +295,7 @@ var provisionCmd = &cobra.Command{
|
|||
hasClientSecret := provisionConfig.GetString(ClientSecretName) != ""
|
||||
|
||||
if (hasClientID || hasClientSecret) && !(hasClientID && hasClientSecret) {
|
||||
return errors.New("--client-id and --client-secret must be speficied together or not at all")
|
||||
return errors.New("--client-id and --client-secret must be specified together or not at all")
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -266,7 +308,7 @@ var provisionCmd = &cobra.Command{
|
|||
if provisionConfig.GetBool(VerboseName) {
|
||||
statusWriter = os.Stdout
|
||||
}
|
||||
status = log.New(statusWriter, "[INFO] ", 0)
|
||||
status = newFormattedLog(statusWriter, "information")
|
||||
|
||||
return nil
|
||||
},
|
||||
|
@ -342,6 +384,7 @@ func getAuthorizer(ctx context.Context, subscriptionID, clientID, clientSecret,
|
|||
return nil, err
|
||||
}
|
||||
status.Println("service principal token created for client: ", clientID)
|
||||
|
||||
t := auth.Token()
|
||||
intermediate = &t
|
||||
}
|
||||
|
@ -360,7 +403,31 @@ func getAuthorizer(ctx context.Context, subscriptionID, clientID, clientSecret,
|
|||
}
|
||||
|
||||
func getDatabaseFlavor(buffaloRoot string) string {
|
||||
return "postgres" // TODO: parse buffalo app for the database they're using.
|
||||
return "postgres" // TODO (#29): parse buffalo app for the database they're using.
|
||||
}
|
||||
|
||||
func getDeploymentTemplate(raw string) (*resources.DeploymentProperties, error) {
|
||||
if isSupportedLink(raw) {
|
||||
return &resources.DeploymentProperties{
|
||||
TemplateLink: &resources.TemplateLink{
|
||||
URI: &raw,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
handle, err := os.Open(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadAll(handle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &resources.DeploymentProperties{
|
||||
Template: json.RawMessage(contents),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getTenant(ctx context.Context, common *adal.Token, subscription string) (string, autorest.Authorizer, error) {
|
||||
|
@ -423,7 +490,17 @@ func isSupportedLink(subject string) bool {
|
|||
return ok
|
||||
}
|
||||
|
||||
func newFormattedLog(output io.Writer, identifier string) *log.Logger {
|
||||
const identLen = 4
|
||||
for len(identifier) < identLen {
|
||||
identifier = identifier + " "
|
||||
}
|
||||
return log.New(output, fmt.Sprintf("[%s] ", strings.ToUpper(identifier)[:identLen]), log.Ldate|log.Ltime)
|
||||
}
|
||||
|
||||
func init() {
|
||||
errLog = newFormattedLog(os.Stderr, "error")
|
||||
|
||||
azureCmd.AddCommand(provisionCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
@ -454,7 +531,7 @@ func init() {
|
|||
|
||||
provisionConfig.SetDefault(EnvironmentName, EnvironmentDefault)
|
||||
provisionConfig.SetDefault(DatabaseName, getDatabaseFlavor("."))
|
||||
provisionConfig.SetDefault(ResoureGroupName, "buffalo-app") // TODO: generate a random suffix
|
||||
provisionConfig.SetDefault(ResoureGroupName, "buffalo-app") // TODO (#30): generate a random suffix
|
||||
provisionConfig.SetDefault(LocationName, LocationDefault)
|
||||
|
||||
provisionCmd.Flags().StringP(ImageName, ImageShorthand, ImageDefault, imageUsage)
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Test_getDeploymentTemplate_links(t *testing.T) {
|
||||
testCases := []string{
|
||||
"https://aka.ms/buffalo-template",
|
||||
"http://aka.ms/buffalo-template",
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
result, err := getDeploymentTemplate(tc)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if result.Template != nil {
|
||||
t.Log("unexpected value present in template")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if result.TemplateLink == nil {
|
||||
t.Log("unexpected nil template link")
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
|
||||
if result.TemplateLink.URI == nil {
|
||||
t.Log("unexpected nil uri")
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
|
||||
if got := *result.TemplateLink.URI; !strings.EqualFold(got, tc) {
|
||||
t.Logf("got:\n\t%q\nwant:\n\t%q", got, tc)
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getDeploymentTemplate_localFiles(t *testing.T) {
|
||||
testCases := []string{
|
||||
"./testdata/template1.json",
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc, func(t *testing.T) {
|
||||
handle, err := os.Open(tc)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := getDeploymentTemplate(tc)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if result.TemplateLink != nil {
|
||||
t.Log("unexpected value present in template link")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if result.Template == nil {
|
||||
t.Log("unexpected nil template")
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
|
||||
want, err := ioutil.ReadAll(handle)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
minimized := bytes.NewBuffer([]byte{})
|
||||
enc := json.NewEncoder(minimized)
|
||||
err = enc.Encode(json.RawMessage(want))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
want = minimized.Bytes()
|
||||
want = []byte(strings.TrimSpace(string(want)))
|
||||
|
||||
got, err := json.Marshal(result.Template)
|
||||
|
||||
report := func(got, want []byte) string {
|
||||
shrink := func(target []byte, maxLength int) (retval []byte) {
|
||||
if len(target) > maxLength {
|
||||
retval = append(target[:maxLength/2], []byte("...")...)
|
||||
retval = append(retval, target[len(target)-maxLength/2:]...)
|
||||
} else {
|
||||
retval = target
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const maxLength = 30
|
||||
|
||||
gotLength := len(got)
|
||||
got = shrink(got, maxLength)
|
||||
|
||||
wantLength := len(want)
|
||||
want = shrink(want, maxLength)
|
||||
|
||||
return fmt.Sprintf("\ngot (len %d):\n\t%q\nwant (len %d):\n\t%q", gotLength, got, wantLength, want)
|
||||
}
|
||||
|
||||
if len(want) == len(got) {
|
||||
for i, current := range want {
|
||||
if got[i] != current {
|
||||
t.Log(report(got, want))
|
||||
t.Fail()
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
t.Log(report(got, want))
|
||||
t.Fail()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"imageName" : {
|
||||
"type": "String",
|
||||
"defaultValue": "appsvc/sample-hello-world:latest"
|
||||
},
|
||||
"name": {
|
||||
"type": "String",
|
||||
"defaultValue": "[concat('site', uniqueString(resourceGroup().id, deployment().name))]"
|
||||
},
|
||||
"database": {
|
||||
"type": "String",
|
||||
"defaultValue": "none",
|
||||
"allowedValues": [
|
||||
"none",
|
||||
"postgres"
|
||||
]
|
||||
},
|
||||
"databaseName": {
|
||||
"type": "String",
|
||||
"defaultValue": "buffalo_development"
|
||||
},
|
||||
"databaseAdministratorLogin": {
|
||||
"type": "String",
|
||||
"defaultValue": "[concat('admin', parameters('name'))]"
|
||||
},
|
||||
"databaseAdministratorLoginPassword": {
|
||||
"type": "SecureString",
|
||||
"defaultValue": ""
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"hostingPlanName": "[concat('hostingPlan-', parameters('name'))]",
|
||||
"postgresName": "[concat(parameters('name'), '-postgres')]",
|
||||
"postgresConnection": "[concat('postgres://', parameters('databaseAdministratorLogin'), ':', parameters('databaseAdministratorLoginPassword'), '@', variables('postgresname'), '.postgres.database.azure.com/', parameters('databaseName'), '?sslmode=required')]"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"type": "Microsoft.Web/sites",
|
||||
"name": "[parameters('name')]",
|
||||
"apiVersion": "2016-03-01",
|
||||
"location": "[resourceGroup().location]",
|
||||
"tags": {
|
||||
"[concat('hidden-related:', subscription().id, '/resourcegroups/', resourceGroup().name, '/providers/Microsoft.Web/serverfarms/', variables('hostingPlanName'))]": "empty",
|
||||
"gobuffalo": "empty"
|
||||
},
|
||||
"properties": {
|
||||
"name": "[parameters('name')]",
|
||||
"siteConfig": {
|
||||
"appSettings": [
|
||||
{
|
||||
"name": "WEBSITES_ENABLE_APP_SERVICE_STORAGE",
|
||||
"value": "false"
|
||||
}
|
||||
],
|
||||
"connectionStrings":[
|
||||
{
|
||||
"name":"DATABASE_URL",
|
||||
"connectionString": "[if(equals(parameters('database'), 'postgres'), variables('postgresConnection'), 'not applicable')]",
|
||||
"type":"custom"
|
||||
}
|
||||
],
|
||||
"appCommandLine": "",
|
||||
"linuxFxVersion": "[concat('DOCKER|', parameters('imageName'))]"
|
||||
},
|
||||
"serverFarmId": "[concat(subscription().id, '/resourcegroups/', resourceGroup().name, '/providers/Microsoft.Web/serverfarms/', variables('hostingPlanName'))]",
|
||||
"hostingEnvironment": ""
|
||||
},
|
||||
"dependsOn": [
|
||||
"[variables('hostingPlanName')]",
|
||||
"[variables('postgresName')]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Microsoft.Web/serverfarms",
|
||||
"sku": {
|
||||
"Tier": "Basic",
|
||||
"Name": "B1"
|
||||
},
|
||||
"kind": "linux",
|
||||
"name": "[variables('hostingPlanName')]",
|
||||
"apiVersion": "2016-09-01",
|
||||
"location": "[resourceGroup().location]",
|
||||
"properties": {
|
||||
"name": "[variables('hostingPlanName')]",
|
||||
"workerSizeId": "0",
|
||||
"reserved": true,
|
||||
"numberOfWorkers": "1",
|
||||
"hostingEnvironment": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"condition":"[equals(parameters('database'), 'postgres')]",
|
||||
"type": "Microsoft.DBforPostgreSQL/servers",
|
||||
"sku": {
|
||||
"name": "B_Gen5_1",
|
||||
"family": "Gen5",
|
||||
"capacity": "",
|
||||
"size": "5120",
|
||||
"tier":"Basic"
|
||||
},
|
||||
"kind":"",
|
||||
"name":"[variables('postgresName')]",
|
||||
"apiVersion": "2017-12-01-preview",
|
||||
"location":"[resourceGroup().location]",
|
||||
"properties": {
|
||||
"version": "9.6",
|
||||
"administratorLogin": "[parameters('databaseAdministratorLogin')]",
|
||||
"administratorLoginPassword": "[parameters('databaseAdministratorLoginPassword')]"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
Загрузка…
Ссылка в новой задаче