Skip to content
This repository has been archived by the owner on Jan 11, 2023. It is now read-only.

initial outline of the acs template generator #3

Merged
merged 8 commits into from
Sep 30, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions acstgen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# ACSTGEN - Template generator

Template generator builds a custom template based on user requirements. Examples exist under clusterdefinitions folder.

To build type ```go build```, and running at commandline provides the usage.
139 changes: 139 additions & 0 deletions acstgen/acstgen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package main

import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"os"
"strings"

"./api/vlabs"
"./clustertemplate"
)

// loadAcsCluster loads an ACS Cluster API Model from a JSON file
func loadAcsCluster(jsonFile string) (*vlabs.AcsCluster, error) {
contents, e := ioutil.ReadFile(jsonFile)
if e != nil {
return nil, fmt.Errorf("error reading file %s: %s", jsonFile, e.Error())
}

acsCluster := &vlabs.AcsCluster{}
if e := json.Unmarshal(contents, &acsCluster); e != nil {
return nil, fmt.Errorf("error unmarshalling file %s: %s", jsonFile, e.Error())
}
acsCluster.SetDefaults()
if e := acsCluster.Validate(); e != nil {
return nil, fmt.Errorf("error validating acs cluster from file %s: %s", jsonFile, e.Error())
}

return acsCluster, nil
}

func translateJSON(content string, translateParams [][]string, reverseTranslate bool) string {
for _, tuple := range translateParams {
if len(tuple) != 2 {
panic("string tuples must be of size 2")
}
a := tuple[0]
b := tuple[1]
if reverseTranslate {
content = strings.Replace(content, b, a, -1)
} else {
content = strings.Replace(content, a, b, -1)
}
}
return content
}

func prettyPrintJSON(content string) (string, error) {
var data map[string]interface{}
if err := json.Unmarshal([]byte(content), &data); err != nil {
return "", err
}
prettyprint, err := json.MarshalIndent(data, "", " ")
if err != nil {
return "", err
}
return string(prettyprint), nil
}

func prettyPrintArmTemplate(template string) (string, error) {
translateParams := [][]string{
{"parameters", "dparameters"},
{"variables", "evariables"},
{"resources", "fresources"},
{"outputs", "zoutputs"},
}

template = translateJSON(template, translateParams, false)
Copy link
Contributor

@JackQuincy JackQuincy Sep 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like a bug waiting to happen where a client names something verbedparameterstore, or something of the sort and we rename things to verbeparameterstore. Is there a way we can introduce path to the values we are changing

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I shared this offline and anhowe fixed it by adding the quotes. Still possible, but Much less likely.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

var err error
if template, err = prettyPrintJSON(template); err != nil {
return "", err
}
template = translateJSON(template, translateParams, true)

return template, nil
}

func usage(errs ...error) {
for _, err := range errs {
fmt.Fprintf(os.Stderr, "error: %s\n\n", err.Error())
}
fmt.Fprintf(os.Stderr, "usage: %s ClusterDefinitionFile\n", os.Args[0])
fmt.Fprintf(os.Stderr, " read the ClusterDefinitionFile and output an arm template")
fmt.Fprintf(os.Stderr, "\n")
fmt.Fprintf(os.Stderr, "options:\n")
flag.PrintDefaults()
}

var templateDirectory = flag.String("templateDirectory", "./parts", "directory containing base template files")
var noPrettyPrint = flag.Bool("noPrettyPrint", false, "do not pretty print output")

func main() {
var acsCluster *vlabs.AcsCluster
var template string
var err error

flag.Parse()

if argCount := len(flag.Args()); argCount == 0 {
usage()
os.Exit(1)
}

jsonFile := flag.Arg(0)
if _, err = os.Stat(jsonFile); os.IsNotExist(err) {
usage(fmt.Errorf("file %s does not exist", jsonFile))
os.Exit(1)
}

if _, err = os.Stat(*templateDirectory); os.IsNotExist(err) {
usage(fmt.Errorf("base templates directory %s does not exist", jsonFile))
os.Exit(1)
}

if err = clustertemplate.VerifyFiles(*templateDirectory); err != nil {
fmt.Fprintf(os.Stderr, "verification failed: %s\n", err.Error())
os.Exit(1)
}

if acsCluster, err = loadAcsCluster(jsonFile); err != nil {
fmt.Fprintf(os.Stderr, "error while loading %s: %s", jsonFile, err.Error())
os.Exit(1)
}

if template, err = clustertemplate.GenerateTemplate(acsCluster, *templateDirectory); err != nil {
fmt.Fprintf(os.Stderr, "error generating template %s: %s", jsonFile, err.Error())
os.Exit(1)
}

if !*noPrettyPrint {
if template, err = prettyPrintArmTemplate(template); err != nil {
fmt.Fprintf(os.Stderr, "error pretty printing template %s", err.Error())
os.Exit(1)
}
}
fmt.Print(template)
}
30 changes: 30 additions & 0 deletions acstgen/api/vlabs/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package vlabs

const (
// DCOS is the string constant for DCOS orchestrator type and defaults to DCOS184
DCOS = "DCOS"
// DCOS184 is the string constant for DCOS 1.8.4 orchestrator type
DCOS184 = "DCOS184"
// DCOS173 is the string constant for DCOS 1.7.3 orchestrator type
DCOS173 = "DCOS173"
// Swarm is the string constant for the Swarm orchestrator type
Swarm = "Swarm"
// MinAgentCount are the minimum number of agents
MinAgentCount = 1
// MaxAgentCount are the maximum number of agents
MaxAgentCount = 100
// MinPort specifies the minimum tcp port to open
MinPort = 1
// MaxPort specifies the maximum tcp port to open
MaxPort = 65535
// BaseLBPriority specifies the base lb priority.
BaseLBPriority = 200
// DefaultMasterSubnet specifies the default master subnet
DefaultMasterSubnet = "172.16.0.0/24"
// DefaultFirstConsecutiveStaticIP specifies the static IP address on master 0
DefaultFirstConsecutiveStaticIP = "172.16.0.5"
// DefaultAgentSubnetTemplate specifies a default agent subnet
DefaultAgentSubnetTemplate = "10.%d.0.0/24"
// MaxDisks specifies the maximum attached disks to add to the cluster
MaxDisks = 4
)
41 changes: 41 additions & 0 deletions acstgen/api/vlabs/defaults.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package vlabs

import "fmt"

// SetDefaults implements APIObject
func (o *OrchestratorProfile) SetDefaults() {
}

// SetDefaults implements APIObject
func (m *MasterProfile) SetDefaults() {
if !m.IsCustomVNET() {
m.subnet = DefaultMasterSubnet
m.FirstConsecutiveStaticIP = DefaultFirstConsecutiveStaticIP
}
}

// SetDefaults implements APIObject
func (a *AgentPoolProfile) SetDefaults() {
}

// SetDefaults implements APIObject
func (l *LinuxProfile) SetDefaults() {
}

// SetDefaults implements APIObject
func (a *AcsCluster) SetDefaults() {
a.OrchestratorProfile.SetDefaults()
a.MasterProfile.SetDefaults()

// assign subnets if VNET not specified
subnetCounter := 0
for i := range a.AgentPoolProfiles {
profile := &a.AgentPoolProfiles[i]
profile.SetDefaults()
if !profile.IsCustomVNET() {
profile.subnet = fmt.Sprintf(DefaultAgentSubnetTemplate, subnetCounter)
subnetCounter++
}
}
a.LinuxProfile.SetDefaults()
}
2 changes: 2 additions & 0 deletions acstgen/api/vlabs/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package vlabs stores an experimental api model for acs
package vlabs // import "./api/vlabs"
80 changes: 80 additions & 0 deletions acstgen/api/vlabs/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package vlabs

// AcsCluster represents the ACS cluster definition
type AcsCluster struct {
OrchestratorProfile OrchestratorProfile `json:"orchestratorProfile"`
MasterProfile MasterProfile `json:"masterProfile"`
AgentPoolProfiles []AgentPoolProfile `json:"agentPoolProfiles"`
LinuxProfile LinuxProfile `json:"linuxProfile"`
}

// OrchestratorProfile represents the type of orchestrator
type OrchestratorProfile struct {
OrchestratorType string `json:"orchestratorType"`
}

// MasterProfile represents the definition of the master cluster
type MasterProfile struct {
Count int `json:"count"`
DNSPrefix string `json:"dnsPrefix"`
VMSize string `json:"vmSize"`
VnetSubnetID string `json:"vnetSubnetID,omitempty"`
FirstConsecutiveStaticIP string `json:"firstConsecutiveStaticIP,omitempty"`
// subnet is internal
subnet string
}

// AgentPoolProfile represents an agent pool definition
type AgentPoolProfile struct {
Name string `json:"name"`
Count int `json:"count"`
VMSize string `json:"vmSize"`
DNSPrefix string `json:"dnsPrefix,omitempty"`
Ports []int `json:"ports,omitempty"`
IsStateful bool `json:"isStateful,omitempty"`
DiskSizesGB []int `json:"diskSizesGB,omitempty"`
VnetSubnetID string `json:"vnetSubnetID,omitempty"`
// subnet is internal
subnet string
}

// LinuxProfile represents the linux parameters passed to the cluster
type LinuxProfile struct {
AdminUsername string `json:"adminUsername"`
SSH struct {
PublicKeys []struct {
KeyData string `json:"keyData"`
} `json:"publicKeys"`
} `json:"ssh"`
}

// APIObject defines the required functionality of an api object
type APIObject interface {
SetDefaults()
Validate() error
}

// IsCustomVNET returns true if the customer brought their own VNET
func (m *MasterProfile) IsCustomVNET() bool {
return len(m.VnetSubnetID) > 0
}

// GetSubnet returns the read-only subnet for the master
func (m *MasterProfile) GetSubnet() string {
return m.subnet
}

// IsCustomVNET returns true if the customer brought their own VNET
func (a *AgentPoolProfile) IsCustomVNET() bool {
return len(a.VnetSubnetID) > 0
}

// HasDisks returns true if the customer specified disks
func (a *AgentPoolProfile) HasDisks() bool {
return len(a.DiskSizesGB) > 0
}

// GetSubnet returns the read-only subnet for the agent pool
func (a *AgentPoolProfile) GetSubnet() string {
return a.subnet
}
Loading