-
Notifications
You must be signed in to change notification settings - Fork 558
initial outline of the acs template generator #3
Changes from 1 commit
759e6e9
8404b84
951e899
2dfdfc2
2c13040
15ea4a5
f83eeec
1ccb408
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"flag" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
|
||
"./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()) | ||
} | ||
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 usage(errs ...error) { | ||
for _, err := range errs { | ||
fmt.Printf("error: %s\n\n", err.Error()) | ||
} | ||
fmt.Printf("usage: %s ClusterDefinitionFile\n", os.Args[0]) | ||
fmt.Println(" read the ClusterDefinitionFile and output an arm template") | ||
fmt.Println() | ||
fmt.Println("options:") | ||
flag.PrintDefaults() | ||
} | ||
|
||
var templateDirectory = flag.String("templateDirectory", "./parts", "directory containing base template files") | ||
|
||
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 { | ||
usage(err) | ||
os.Exit(1) | ||
} | ||
|
||
if acsCluster, err = loadAcsCluster(jsonFile); err != nil { | ||
usage(fmt.Errorf("error while loading %s: %s", jsonFile, err.Error())) | ||
os.Exit(1) | ||
} | ||
|
||
if template, err = clustertemplate.GenerateTemplate(acsCluster, *templateDirectory); err != nil { | ||
usage(fmt.Errorf("error generating template %s: %s", jsonFile, err.Error())) | ||
os.Exit(1) | ||
} | ||
|
||
fmt.Print(template) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package vlabs | ||
|
||
const ( | ||
// DCOS is the string constant for DCOS orchestrator type | ||
DCOS = "DCOS" | ||
// 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 | ||
) |
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" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package vlabs | ||
|
||
// AcsCluster represents the ACS cluster definition | ||
type AcsCluster struct { | ||
OrchestratorProfile OrchestratorProfile `json:"orchestratorProfile"` | ||
MasterProfile MasterProfile `json:"masterProfile"` | ||
AgentPoolProfiles []AgentPoolProfiles `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"` | ||
} | ||
|
||
// AgentPoolProfiles represents an agent pool definition | ||
type AgentPoolProfiles struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be: 'AgentPoolProfile' (singular). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
Name string `json:"name"` | ||
Count int `json:"count"` | ||
VMSize string `json:"vmSize"` | ||
IsStateless bool `json:"isStateless,omitempty"` | ||
DNSPrefix string `json:"dnsPrefix,omitempty"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a required field. Why would be it empty? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. validation now added to require this if the user specifies ports |
||
Ports []int `json:"ports,omitempty"` | ||
} | ||
|
||
// 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 { | ||
Validate() error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package vlabs | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
) | ||
|
||
// Validate implements APIObject | ||
func (o *OrchestratorProfile) Validate() error { | ||
switch o.OrchestratorType { | ||
case DCOS: | ||
case SWARM: | ||
default: | ||
return fmt.Errorf("OrchestratorProfile has unknown orchestrator: %s", o.OrchestratorType) | ||
} | ||
return nil | ||
} | ||
|
||
// Validate implements APIObject | ||
func (m *MasterProfile) Validate() error { | ||
if m.Count != 1 && m.Count != 3 && m.Count != 5 { | ||
return fmt.Errorf("MasterProfile count needs to be 1, 3, or 5") | ||
} | ||
if e := validateName(m.DNSPrefix, "MasterProfile.DNSPrefix"); e != nil { | ||
return e | ||
} | ||
if e := validateName(m.VMSize, "MasterProfile.VMSize"); e != nil { | ||
return e | ||
} | ||
return nil | ||
} | ||
|
||
// Validate implements APIObject | ||
func (a *AgentPoolProfiles) Validate() error { | ||
if e := validateName(a.Name, "AgentPoolProfiles.Name"); e != nil { | ||
return e | ||
} | ||
if a.Count < MinAgentCount || a.Count > MaxAgentCount { | ||
return fmt.Errorf("AgentPoolProfiles count needs to be in the range [%d,%d]", MinAgentCount, MaxAgentCount) | ||
} | ||
if e := validateName(a.VMSize, "AgentPoolProfiles.VMSize"); e != nil { | ||
return e | ||
} | ||
if len(a.Ports) > 0 { | ||
for _, port := range a.Ports { | ||
if port < MinPort || port > MaxPort { | ||
return fmt.Errorf("AgentPoolProfiles Ports must be in the range[%d, %d]", MinPort, MaxPort) | ||
} | ||
} | ||
if e := validateName(a.Name, "AgentPoolProfiles.DNSPrefix when specifying AgentPoolProfiles Ports"); e != nil { | ||
return e | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// Validate implements APIObject | ||
func (l *LinuxProfile) Validate() error { | ||
if e := validateName(l.AdminUsername, "LinuxProfile.AdminUsername"); e != nil { | ||
return e | ||
} | ||
if len(l.SSH.PublicKeys) != 1 { | ||
return errors.New("LinuxProfile.PublicKeys requires 1 SSH Key") | ||
} | ||
if e := validateName(l.SSH.PublicKeys[0].KeyData, "LinuxProfile.PublicKeys.KeyData"); e != nil { | ||
return e | ||
} | ||
return nil | ||
} | ||
|
||
// Validate implements APIObject | ||
func (a *AcsCluster) Validate() error { | ||
if e := a.OrchestratorProfile.Validate(); e != nil { | ||
return e | ||
} | ||
if e := a.MasterProfile.Validate(); e != nil { | ||
return e | ||
} | ||
for _, agentPoolProfile := range a.AgentPoolProfiles { | ||
if e := agentPoolProfile.Validate(); e != nil { | ||
return e | ||
} | ||
} | ||
if e := a.LinuxProfile.Validate(); e != nil { | ||
return e | ||
} | ||
return nil | ||
} | ||
|
||
func validateName(name string, label string) error { | ||
if name == "" { | ||
return fmt.Errorf("%s must be a non-empty value", label) | ||
} | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
{ | ||
"orchestratorProfile": { | ||
"orchestratorType": "DCOS" | ||
}, | ||
"masterProfile": { | ||
"count": 1, | ||
"dnsPrefix": "mgmtanhowe0923a", | ||
"vmSize": "Standard_D2_v2" | ||
}, | ||
"agentPoolProfiles": [ | ||
{ | ||
"name": "agentpools", | ||
"count": 5, | ||
"vmSize": "Standard_D2_v2", | ||
"isStateless": false | ||
}, | ||
{ | ||
"name": "agentpools", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agent pool names should be unique within the cluster so you should name them as such in the example as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks, I've added validation to catch this now. |
||
"count": 3, | ||
"vmSize": "Standard_D2_v2", | ||
"dnsPrefix": "appanhowe0923a", | ||
"ports": [ | ||
80, | ||
443, | ||
8080 | ||
] | ||
} | ||
], | ||
"linuxProfile": { | ||
"adminUsername": "azureuser", | ||
"ssh": { | ||
"publicKeys": [ | ||
{ | ||
"keyData": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8fhkh3jpHUQsrUIezFB5k4Rq9giJM8G1Cr0u2IRMiqG++nat5hbOr3gODpTA0h11q9bzb6nJtK7NtDzIHx+w3YNIVpcTGLiUEsfUbY53IHg7Nl/p3/gkST3g0R6BSL7Hg45SfyvpH7kwY30MoVHG/6P3go4SKlYoHXlgaaNr3fMwUTIeE9ofvyS3fcr6xxlsoB6luKuEs50h0NGsE4QEnbfSY4Yd/C1ucc3mEw+QFXBIsENHfHfZYrLNHm2L8MXYVmAH8k//5sFs4Migln9GiUgEQUT6uOjowsZyXBbXwfT11og+syPkAq4eqjiC76r0w6faVihdBYVoc/UcyupgH azureuser@linuxvm" | ||
} | ||
] | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// Package clustertemplate stores an experimental api model for acs | ||
package clustertemplate // import "./clustertemplate" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why all caps for Swarm?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is now "Swarm"