Restructure commands. Introduce cobra/logrus

This commit is contained in:
Cole Mickens 2017-05-09 15:50:19 -07:00
Родитель cb47749394
Коммит 362923e886
12 изменённых файлов: 327 добавлений и 243 удалений

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

@ -5,13 +5,17 @@
VERSION=`git describe --always --long --dirty`
BUILD=`date +%FT%T%z`
all: build
prereqs:
go get github.com/jteeuwen/go-bindata/...
go get github.com/Sirupsen/logrus
go get github.com/spf13/cobra
build: prereqs
go generate -v ./...
go get .
go build -v -ldflags="-X main.AcsEngineBuildSHA=${VERSION} -X main.AcsEngineBuildTime=${BUILD}"
go build -v -ldflags="-X github.com/Azure/acs-engine/cmd.BuildSHA=${VERSION} -X github.com/Azure/acs-engine/cmd.BuildTime=${BUILD}"
cd test/acs-engine-test; go build -v
test: prereqs test_fmt

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

@ -48,7 +48,7 @@ $ vim examples/kubernetes.classic.json
# insert your preferred, unique DNS prefix
# insert your SSH public key
$ ./acs-engine examples/kubernetes.classic.json
$ ./acs-engine generate examples/kubernetes.classic.json
```
This produces a new directory inside `_output/` that contains an ARM template

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

@ -44,7 +44,7 @@ $ vim examples/kubernetes.classic.json
# 修改默认的DNS prefix
# 修改ssh public key
$ ./acs-engine examples/kubernetes.classic.json
$ ./acs-engine generate examples/kubernetes.classic.json
```
This produces a new directory inside `_output/` that contains an ARM template

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

@ -1,237 +0,0 @@
package main
import (
"errors"
"flag"
"fmt"
"io/ioutil"
"os"
"path"
"time"
"github.com/Azure/acs-engine/pkg/acsengine"
"github.com/Azure/acs-engine/pkg/api"
)
func writeArtifacts(containerService *api.ContainerService, apiVersion, template, parameters, artifactsDir string, certsGenerated bool, parametersOnly bool) error {
if len(artifactsDir) == 0 {
artifactsDir = fmt.Sprintf("%s-%s", containerService.Properties.OrchestratorProfile.OrchestratorType, acsengine.GenerateClusterID(containerService.Properties))
artifactsDir = path.Join("_output", artifactsDir)
}
// convert back the API object, and write it
var b []byte
var err error
if !parametersOnly {
b, err = api.SerializeContainerService(containerService, apiVersion)
if err != nil {
return err
}
if e := saveFile(artifactsDir, "apimodel.json", b); e != nil {
return e
}
if e := saveFileString(artifactsDir, "azuredeploy.json", template); e != nil {
return e
}
}
if e := saveFileString(artifactsDir, "azuredeploy.parameters.json", parameters); e != nil {
return e
}
if certsGenerated {
properties := containerService.Properties
if properties.OrchestratorProfile.OrchestratorType == api.Kubernetes {
directory := path.Join(artifactsDir, "kubeconfig")
var locations []string
if containerService.Location != "" {
locations = []string{containerService.Location}
} else {
locations = acsengine.AzureLocations
}
for _, location := range locations {
b, gkcerr := acsengine.GenerateKubeConfig(properties, location)
if gkcerr != nil {
return gkcerr
}
if e := saveFileString(directory, fmt.Sprintf("kubeconfig.%s.json", location), b); e != nil {
return e
}
}
}
if e := saveFileString(artifactsDir, "ca.key", properties.CertificateProfile.GetCAPrivateKey()); e != nil {
return e
}
if e := saveFileString(artifactsDir, "ca.crt", properties.CertificateProfile.CaCertificate); e != nil {
return e
}
if e := saveFileString(artifactsDir, "apiserver.key", properties.CertificateProfile.APIServerPrivateKey); e != nil {
return e
}
if e := saveFileString(artifactsDir, "apiserver.crt", properties.CertificateProfile.APIServerCertificate); e != nil {
return e
}
if e := saveFileString(artifactsDir, "client.key", properties.CertificateProfile.ClientPrivateKey); e != nil {
return e
}
if e := saveFileString(artifactsDir, "client.crt", properties.CertificateProfile.ClientCertificate); e != nil {
return e
}
if e := saveFileString(artifactsDir, "kubectlClient.key", properties.CertificateProfile.KubeConfigPrivateKey); e != nil {
return e
}
if e := saveFileString(artifactsDir, "kubectlClient.crt", properties.CertificateProfile.KubeConfigCertificate); e != nil {
return e
}
}
return nil
}
func saveFileString(dir string, file string, data string) error {
return saveFile(dir, file, []byte(data))
}
func saveFile(dir string, file string, data []byte) error {
if _, err := os.Stat(dir); os.IsNotExist(err) {
if e := os.MkdirAll(dir, 0700); e != nil {
return fmt.Errorf("error creating directory '%s': %s", dir, e.Error())
}
}
path := path.Join(dir, file)
if err := ioutil.WriteFile(path, []byte(data), 0600); err != nil {
return err
}
fmt.Fprintf(os.Stderr, "wrote %s\n", path)
return 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 [OPTIONS] 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()
fmt.Fprintf(os.Stderr, "\n%s --version or -v to get the build version \n", os.Args[0])
}
var noPrettyPrint = flag.Bool("noPrettyPrint", false, "do not pretty print output")
var artifactsDir = flag.String("artifacts", "", "directory where artifacts will be written")
var classicMode = flag.Bool("classicMode", false, "enable classic parameters and outputs")
var parametersOnly = flag.Bool("parametersOnly", false, "only output the parameters")
// AcsEngineBuildSHA is the Git SHA-1 of the last commit
var AcsEngineBuildSHA string
// AcsEngineBuildTime is the timestamp of when acs-engine was built
var AcsEngineBuildTime string
// acs-engine takes the caKey and caCert as args, since the caKey is stored separately
// from the api model since this cannot be easily revoked like the server and client key
var caCertificatePath = flag.String("caCertificatePath", "", "the path to the CA Certificate file")
var caKeyPath = flag.String("caKeyPath", "", "the path to the CA key file")
func main() {
if (len(os.Args) == 2) &&
((os.Args[1] == "--version") || (os.Args[1] == "-v")) {
if len(AcsEngineBuildSHA) == 0 {
fmt.Fprintf(os.Stderr, "No version set. Please run `make build`\n")
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "Git commit: %s\nBuild Timestamp: %s\n", AcsEngineBuildSHA, AcsEngineBuildTime)
os.Exit(0)
}
start := time.Now()
defer func(s time.Time) {
fmt.Fprintf(os.Stderr, "acsengine took %s\n", time.Since(s))
}(start)
var containerService *api.ContainerService
var caCertificateBytes []byte
var caKeyBytes []byte
var template string
var parameters string
var apiVersion 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 (len(*caCertificatePath) > 0 && len(*caKeyPath) == 0) ||
(len(*caCertificatePath) == 0 && len(*caKeyPath) > 0) {
usage(errors.New("caKeyPath and caCertificatePath must be specified together"))
os.Exit(1)
}
if len(*caCertificatePath) > 0 {
if caCertificateBytes, err = ioutil.ReadFile(*caCertificatePath); err != nil {
usage(err)
os.Exit(1)
}
if caKeyBytes, err = ioutil.ReadFile(*caKeyPath); err != nil {
usage(err)
os.Exit(1)
}
}
templateGenerator, e := acsengine.InitializeTemplateGenerator(*classicMode)
if e != nil {
fmt.Fprintf(os.Stderr, "generator initialization failed: %s\n", e.Error())
os.Exit(1)
}
if containerService, apiVersion, err = api.LoadContainerServiceFromFile(jsonFile); err != nil {
fmt.Fprintf(os.Stderr, "error while loading %s: %s", jsonFile, err.Error())
os.Exit(1)
}
if len(caKeyBytes) != 0 {
// the caKey is not in the api model, and should be stored separately from the model
// we put these in the model after model is deserialized
containerService.Properties.CertificateProfile.CaCertificate = string(caCertificateBytes)
containerService.Properties.CertificateProfile.SetCAPrivateKey(string(caKeyBytes))
}
certsGenerated := false
if template, parameters, certsGenerated, err = templateGenerator.GenerateTemplate(containerService); err != nil {
fmt.Fprintf(os.Stderr, "error generating template %s: %s", jsonFile, err.Error())
os.Exit(1)
}
if !*noPrettyPrint {
if template, err = acsengine.PrettyPrintArmTemplate(template); err != nil {
fmt.Fprintf(os.Stderr, "error pretty printing template: %s \n", err.Error())
os.Exit(1)
}
if parameters, err = acsengine.PrettyPrintJSON(parameters); err != nil {
fmt.Fprintf(os.Stderr, "error pretty printing template parameters: %s \n", err.Error())
os.Exit(1)
}
}
if err = writeArtifacts(containerService, apiVersion, template, parameters, *artifactsDir, certsGenerated, *parametersOnly); err != nil {
fmt.Fprintf(os.Stderr, "error writing artifacts: %s \n", err.Error())
os.Exit(1)
}
}

128
cmd/generate.go Normal file
Просмотреть файл

@ -0,0 +1,128 @@
package cmd
import (
"os"
"path"
log "github.com/Sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/Azure/acs-engine/pkg/acsengine"
"github.com/Azure/acs-engine/pkg/api"
)
const (
generateName = "generate"
generateShortDescription = "Generate an Azure Resource Manager template"
generateLongDescription = "Generates an Azure Resource Manager template, parameters file and other assets for a cluster"
)
type generateCmd struct {
apimodelPath string
outputDirectory string // can be auto-determined from clusterDefinition
caCertificatePath string
caPrivateKeyPath string
classicMode bool
noPrettyPrint bool
parametersOnly bool
// Parsed from inputs
containerService *api.ContainerService
apiVersion string
}
func NewGenerateCmd() *cobra.Command {
gc := generateCmd{}
generateCmd := &cobra.Command{
Use: generateName,
Short: generateShortDescription,
Long: generateLongDescription,
RunE: func(cmd *cobra.Command, args []string) error {
return gc.run(cmd, args)
},
}
f := generateCmd.Flags()
f.StringVar(&gc.apimodelPath, "api-model", "", "")
f.StringVar(&gc.outputDirectory, "output-directory", "", "output directory (derived from FQDN if absent)")
f.StringVar(&gc.caCertificatePath, "ca-certificate-path", "", "path to the CA certificate to use for Kubernetes PKI assets")
f.StringVar(&gc.caPrivateKeyPath, "ca-private-key-path", "", "path to the CA private key to use for Kubernetes PKI assets")
f.BoolVar(&gc.classicMode, "classic-mode", false, "enable classic parameters and outputs")
f.BoolVar(&gc.noPrettyPrint, "no-pretty-print", false, "skip pretty printing the output")
f.BoolVar(&gc.parametersOnly, "parameters-only", false, "only output parameters files")
return generateCmd
}
func (gc *generateCmd) validate(cmd *cobra.Command, args []string) {
var caCertificateBytes []byte
var caKeyBytes []byte
if gc.apimodelPath == "" {
if len(args) > 0 {
gc.apimodelPath = args[0]
} else if len(args) > 1 {
cmd.Usage()
log.Fatalln("too many arguments were provided to 'generate'")
} else {
cmd.Usage()
log.Fatalln("--api-model was not supplied, nor was one specified as a positional argument")
}
}
if _, err := os.Stat(gc.apimodelPath); os.IsNotExist(err) {
log.Fatalf("specified api model does not exist (%s)", gc.apimodelPath)
}
containerService, apiVersion, err := api.LoadContainerServiceFromFile(gc.apimodelPath)
if err != nil {
log.Fatalf("error parsing the api model: %s", err.Error())
}
if gc.outputDirectory == "" {
gc.outputDirectory = path.Join("_output", containerService.Properties.MasterProfile.DNSPrefix)
}
if len(caKeyBytes) != 0 {
// the caKey is not in the api model, and should be stored separately from the model
// we put these in the model after model is deserialized
containerService.Properties.CertificateProfile.CaCertificate = string(caCertificateBytes)
containerService.Properties.CertificateProfile.SetCAPrivateKey(string(caKeyBytes))
}
gc.containerService = containerService
gc.apiVersion = apiVersion
}
func (gc *generateCmd) run(cmd *cobra.Command, args []string) error {
gc.validate(cmd, args)
log.Infoln("Generating...")
templateGenerator, err := acsengine.InitializeTemplateGenerator(gc.classicMode)
if err != nil {
log.Fatalln("failed to initialize template generator: %s", err.Error())
}
certsGenerated := false
template, parameters, certsGenerated, err := templateGenerator.GenerateTemplate(gc.containerService)
if err != nil {
log.Fatalf("error generating template %s: %s", gc.apimodelPath, err.Error())
os.Exit(1)
}
if !gc.noPrettyPrint {
if template, err = acsengine.PrettyPrintArmTemplate(template); err != nil {
log.Fatalf("error pretty printing template: %s \n", err.Error())
}
if parameters, err = acsengine.PrettyPrintJSON(parameters); err != nil {
log.Fatalf("error pretty printing template parameters: %s \n", err.Error())
}
}
if err = acsengine.WriteArtifacts(gc.containerService, gc.apiVersion, template, parameters, gc.outputDirectory, certsGenerated, gc.parametersOnly); err != nil {
log.Fatalf("error writing artifacts: %s \n", err.Error())
}
return nil
}

37
cmd/root.go Normal file
Просмотреть файл

@ -0,0 +1,37 @@
package cmd
import (
log "github.com/Sirupsen/logrus"
"github.com/spf13/cobra"
)
const (
rootName = "acs-engine"
rootShortDescription = "ACS-Engine deploys and manages container orchestrators in Azure"
rootLongDescription = "ACS-Engine deploys and manages Kubernetes, Swarm Mode, and DC/OS clusters in Azure"
)
var (
debug bool
)
func NewRootCmd() *cobra.Command {
rootCmd := &cobra.Command{
Use: rootName,
Short: rootShortDescription,
Long: rootLongDescription,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if debug {
log.SetLevel(log.DebugLevel)
}
},
}
p := rootCmd.PersistentFlags()
p.BoolVar(&debug, "debug", false, "enable verbose debug logs")
rootCmd.AddCommand(NewVersionCmd())
rootCmd.AddCommand(NewGenerateCmd())
return rootCmd
}

24
cmd/version.go Normal file
Просмотреть файл

@ -0,0 +1,24 @@
package cmd
import (
log "github.com/Sirupsen/logrus"
"github.com/spf13/cobra"
)
var (
BuildSHA = "unset"
BuildTime = "unset"
)
func NewVersionCmd() *cobra.Command {
versionCmd := &cobra.Command{
Use: "version",
Short: "Print the version of ACS-Engine",
Long: "Print the version of ACS-Engine",
Run: func(cmd *cobra.Command, args []string) {
log.Infof("ACS-Engine Version: %s (%s)", BuildSHA, BuildTime)
},
}
return versionCmd
}

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

@ -138,7 +138,7 @@ Here is an example of how to generate a new deployment. This example assumes yo
1. Before starting ensure you have generated a valid [SSH Public/Private key pair](ssh.md#ssh-key-generation).
2. edit [examples/kubernetes.json](../examples/kubernetes.json) and fill in the blanks.
3. run `acs-engine examples/kubernetes.json` to generate the templates in the _output/Kubernetes-UNIQUEID directory. The UNIQUEID is a hash of your master's FQDN prefix.
3. run `acs-engine generate examples/kubernetes.json` to generate the templates in the _output/Kubernetes-UNIQUEID directory. The UNIQUEID is a hash of your master's FQDN prefix.
4. now you can use the `azuredeploy.json` and `azuredeploy.parameters.json` for deployment as described in [deployment usage](../README.md#deployment-usage).
# Deploying templates

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

@ -132,7 +132,7 @@ ACS引擎使用json格式的[集群定义文件](clusterdefinition.md)作为输
1. 首先需要准备一个[SSH 公钥私钥对](ssh.md#ssh-key-generation).
2. 编辑[examples/kubernetes.json](../examples/kubernetes.json)将其需要的参数配置好.
3. 运行`acs-engine examples/kubernetes.json`命令在_output/Kubernetes-UNIQUEID目录中生成对应的模板。UNIQUEID是master节点的FQDN前缀的hash值
3. 运行`acs-engine generate examples/kubernetes.json`命令在_output/Kubernetes-UNIQUEID目录中生成对应的模板。UNIQUEID是master节点的FQDN前缀的hash值
4. 按照README中指定的方式使用`azuredeploy.json`和`azuredeploy.parameters.json`部署容器集群 [deployment usage](../README.md#deployment-usage).
# 部署方法

16
main.go Normal file
Просмотреть файл

@ -0,0 +1,16 @@
package main
import (
"github.com/Azure/acs-engine/cmd"
log "github.com/Sirupsen/logrus"
)
const (
ClientID = "76e0feec-6b7f-41f0-81a7-b1b944520261"
)
func main() {
if err := cmd.NewRootCmd().Execute(); err != nil {
log.Fatalln(err)
}
}

112
pkg/acsengine/output.go Normal file
Просмотреть файл

@ -0,0 +1,112 @@
package acsengine
import (
"fmt"
"io/ioutil"
"os"
"path"
"github.com/Azure/acs-engine/pkg/api"
)
func WriteArtifacts(containerService *api.ContainerService, apiVersion, template, parameters, artifactsDir string, certsGenerated bool, parametersOnly bool) error {
if len(artifactsDir) == 0 {
artifactsDir = fmt.Sprintf("%s-%s", containerService.Properties.OrchestratorProfile.OrchestratorType, GenerateClusterID(containerService.Properties))
artifactsDir = path.Join("_output", artifactsDir)
}
// convert back the API object, and write it
var b []byte
var err error
if !parametersOnly {
b, err = api.SerializeContainerService(containerService, apiVersion)
if err != nil {
return err
}
if e := saveFile(artifactsDir, "apimodel.json", b); e != nil {
return e
}
if e := saveFileString(artifactsDir, "azuredeploy.json", template); e != nil {
return e
}
}
if e := saveFileString(artifactsDir, "azuredeploy.parameters.json", parameters); e != nil {
return e
}
if certsGenerated {
properties := containerService.Properties
if properties.OrchestratorProfile.OrchestratorType == api.Kubernetes {
directory := path.Join(artifactsDir, "kubeconfig")
var locations []string
if containerService.Location != "" {
locations = []string{containerService.Location}
} else {
locations = AzureLocations
}
for _, location := range locations {
b, gkcerr := GenerateKubeConfig(properties, location)
if gkcerr != nil {
return gkcerr
}
if e := saveFileString(directory, fmt.Sprintf("kubeconfig.%s.json", location), b); e != nil {
return e
}
}
}
if e := saveFileString(artifactsDir, "ca.key", properties.CertificateProfile.GetCAPrivateKey()); e != nil {
return e
}
if e := saveFileString(artifactsDir, "ca.crt", properties.CertificateProfile.CaCertificate); e != nil {
return e
}
if e := saveFileString(artifactsDir, "apiserver.key", properties.CertificateProfile.APIServerPrivateKey); e != nil {
return e
}
if e := saveFileString(artifactsDir, "apiserver.crt", properties.CertificateProfile.APIServerCertificate); e != nil {
return e
}
if e := saveFileString(artifactsDir, "client.key", properties.CertificateProfile.ClientPrivateKey); e != nil {
return e
}
if e := saveFileString(artifactsDir, "client.crt", properties.CertificateProfile.ClientCertificate); e != nil {
return e
}
if e := saveFileString(artifactsDir, "kubectlClient.key", properties.CertificateProfile.KubeConfigPrivateKey); e != nil {
return e
}
if e := saveFileString(artifactsDir, "kubectlClient.crt", properties.CertificateProfile.KubeConfigCertificate); e != nil {
return e
}
}
return nil
}
func saveFileString(dir string, file string, data string) error {
return saveFile(dir, file, []byte(data))
}
func saveFile(dir string, file string, data []byte) error {
if _, err := os.Stat(dir); os.IsNotExist(err) {
if e := os.MkdirAll(dir, 0700); e != nil {
return fmt.Errorf("error creating directory '%s': %s", dir, e.Error())
}
}
path := path.Join(dir, file)
if err := ioutil.WriteFile(path, []byte(data), 0600); err != nil {
return err
}
fmt.Fprintf(os.Stderr, "wrote %s\n", path)
return nil
}

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

@ -64,7 +64,7 @@ function generate_template() {
jqi "${FINAL_CLUSTER_DEFINITION}" ".properties.windowsProfile.secrets[0].vaultCertificates[0].certificateStore = \"My\""
fi
# Generate template
"${DIR}/../acs-engine" -artifacts "${OUTPUT}" "${FINAL_CLUSTER_DEFINITION}"
"${DIR}/../acs-engine" generate --output-directory "${OUTPUT}" "${FINAL_CLUSTER_DEFINITION}"
# Fill in custom hyperkube spec, if it was set
if [[ ! -z "${CUSTOM_HYPERKUBE_SPEC:-}" ]]; then