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 1 commit
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.
84 changes: 84 additions & 0 deletions acstgen/acstgen.go
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)
}
16 changes: 16 additions & 0 deletions acstgen/api/vlabs/const.go
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"
Copy link
Contributor

@amanohar amanohar Sep 24, 2016

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it is now "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
)
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"
46 changes: 46 additions & 0 deletions acstgen/api/vlabs/types.go
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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be: 'AgentPoolProfile' (singular).

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

Name string `json:"name"`
Count int `json:"count"`
VMSize string `json:"vmSize"`
IsStateless bool `json:"isStateless,omitempty"`
DNSPrefix string `json:"dnsPrefix,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a required field. Why would be it empty?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
}
95 changes: 95 additions & 0 deletions acstgen/api/vlabs/validate.go
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
}
39 changes: 39 additions & 0 deletions acstgen/clusterdefinitions/dcos1.7.3original.json
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",
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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"
}
]
}
}
}
2 changes: 2 additions & 0 deletions acstgen/clustertemplate/doc.go
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"
Loading