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

Support --set flag in deploy command #3188

Merged
merged 6 commits into from
Jun 11, 2018
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
83 changes: 72 additions & 11 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"errors"
"fmt"
"io/ioutil"
"math/rand"
Expand Down Expand Up @@ -51,6 +52,7 @@ type deployCmd struct {
caPrivateKeyPath string
classicMode bool
parametersOnly bool
set []string

// derived
containerService *api.ContainerService
Expand All @@ -71,12 +73,18 @@ func newDeployCmd() *cobra.Command {
Short: deployShortDescription,
Long: deployLongDescription,
RunE: func(cmd *cobra.Command, args []string) error {
if err := dc.validate(cmd, args); err != nil {
if err := dc.validateArgs(cmd, args); err != nil {
log.Fatalf(fmt.Sprintf("error validating deployCmd: %s", err.Error()))
}
if err := dc.load(cmd, args); err != nil {
if err := dc.mergeAPIModel(); err != nil {
log.Fatalf(fmt.Sprintf("error merging API model in deployCmd: %s", err.Error()))
}
if err := dc.loadAPIModel(cmd, args); err != nil {
log.Fatalln("failed to load apimodel: %s", err.Error())
}
if _, _, err := dc.validateApimodel(); err != nil {
log.Fatalln("Failed to validate the apimodel after populating values: %s", err.Error())
}
return dc.run()
},
}
Expand All @@ -91,13 +99,14 @@ func newDeployCmd() *cobra.Command {
f.StringVarP(&dc.resourceGroup, "resource-group", "g", "", "resource group to deploy to (will use the DNS prefix from the apimodel if not specified)")
f.StringVarP(&dc.location, "location", "l", "", "location to deploy to (required)")
f.BoolVarP(&dc.forceOverwrite, "force-overwrite", "f", false, "automatically overwrite existing files in the output directory")
f.StringArrayVar(&dc.set, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")

addAuthFlags(&dc.authArgs, f)

return deployCmd
}

func (dc *deployCmd) validate(cmd *cobra.Command, args []string) error {
func (dc *deployCmd) validateArgs(cmd *cobra.Command, args []string) error {
var err error

dc.locale, err = i18n.LoadTranslations()
Expand Down Expand Up @@ -129,7 +138,29 @@ func (dc *deployCmd) validate(cmd *cobra.Command, args []string) error {
return nil
}

func (dc *deployCmd) load(cmd *cobra.Command, args []string) error {
func (dc *deployCmd) mergeAPIModel() error {
var err error

// if --set flag has been used
if dc.set != nil && len(dc.set) > 0 {
m := make(map[string]transform.APIModelValue)
transform.MapValues(m, dc.set)

// overrides the api model and generates a new file
dc.apimodelPath, err = transform.MergeValuesWithAPIModel(dc.apimodelPath, m)
if err != nil {
return fmt.Errorf(fmt.Sprintf("error merging --set values with the api model: %s", err.Error()))
}

log.Infoln(fmt.Sprintf("new api model file has been generated during merge: %s", dc.apimodelPath))
}

return nil
}

func (dc *deployCmd) loadAPIModel(cmd *cobra.Command, args []string) error {
var caCertificateBytes []byte
var caKeyBytes []byte
var err error

apiloader := &api.Apiloader{
Expand All @@ -144,6 +175,35 @@ func (dc *deployCmd) load(cmd *cobra.Command, args []string) error {
return fmt.Errorf(fmt.Sprintf("error parsing the api model: %s", err.Error()))
}

if dc.outputDirectory == "" {
if dc.containerService.Properties.MasterProfile != nil {
dc.outputDirectory = path.Join("_output", dc.containerService.Properties.MasterProfile.DNSPrefix)
} else {
dc.outputDirectory = path.Join("_output", dc.containerService.Properties.HostedMasterProfile.DNSPrefix)
}
}

// consume dc.caCertificatePath and dc.caPrivateKeyPath
if (dc.caCertificatePath != "" && dc.caPrivateKeyPath == "") || (dc.caCertificatePath == "" && dc.caPrivateKeyPath != "") {
return errors.New("--ca-certificate-path and --ca-private-key-path must be specified together")
}

if dc.caCertificatePath != "" {
if caCertificateBytes, err = ioutil.ReadFile(dc.caCertificatePath); err != nil {
return fmt.Errorf(fmt.Sprintf("failed to read CA certificate file: %s", err.Error()))
}
if caKeyBytes, err = ioutil.ReadFile(dc.caPrivateKeyPath); err != nil {
return fmt.Errorf(fmt.Sprintf("failed to read CA private key file: %s", err.Error()))
}

prop := dc.containerService.Properties
if prop.CertificateProfile == nil {
prop.CertificateProfile = &api.CertificateProfile{}
}
prop.CertificateProfile.CaCertificate = string(caCertificateBytes)
prop.CertificateProfile.CaPrivateKey = string(caKeyBytes)
}

if dc.containerService.Location == "" {
dc.containerService.Location = dc.location
} else if dc.containerService.Location != dc.location {
Expand All @@ -163,11 +223,6 @@ func (dc *deployCmd) load(cmd *cobra.Command, args []string) error {
return err
}

_, _, err = validateApimodel(apiloader, dc.containerService, dc.apiVersion)
if err != nil {
return fmt.Errorf("Failed to validate the apimodel after populating values: %s", err)
}

dc.random = rand.New(rand.NewSource(time.Now().UnixNano()))

return nil
Expand Down Expand Up @@ -293,9 +348,15 @@ func autofillApimodel(dc *deployCmd) error {
return nil
}

func validateApimodel(apiloader *api.Apiloader, containerService *api.ContainerService, apiVersion string) (*api.ContainerService, string, error) {
func (dc *deployCmd) validateApimodel() (*api.ContainerService, string, error) {
apiloader := &api.Apiloader{
Translator: &i18n.Translator{
Locale: dc.locale,
},
}

// This isn't terribly elegant, but it's the easiest way to go for now w/o duplicating a bunch of code
rawVersionedAPIModel, err := apiloader.SerializeContainerService(containerService, apiVersion)
rawVersionedAPIModel, err := apiloader.SerializeContainerService(dc.containerService, dc.apiVersion)
if err != nil {
return nil, "", err
}
Expand Down
63 changes: 61 additions & 2 deletions cmd/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func TestValidate(t *testing.T) {
}

for _, c := range cases {
err = c.dc.validate(r, c.args)
err = c.dc.validateArgs(r, c.args)
if err != nil && c.expectedErr != nil {
if err.Error() != c.expectedErr.Error() {
t.Fatalf("expected validate deploy command to return error %s, but instead got %s", c.expectedErr.Error(), err.Error())
Expand Down Expand Up @@ -447,7 +447,7 @@ func testAutodeployCredentialHandling(t *testing.T, useManagedIdentity bool, cli
// cleanup, since auto-populations creates dirs and saves the SSH private key that it might create
defer os.RemoveAll(deployCmd.outputDirectory)

cs, _, err = validateApimodel(apiloader, cs, ver)
cs, _, err = deployCmd.validateApimodel()
if err != nil {
t.Fatalf("unexpected error validating apimodel after populating defaults: %s", err)
}
Expand All @@ -464,3 +464,62 @@ func testAutodeployCredentialHandling(t *testing.T, useManagedIdentity bool, cli
}
}
}

func testDeployCmdMergeAPIModel(t *testing.T) {
d := &deployCmd{}
d.apimodelPath = "../pkg/acsengine/testdata/simple/kubernetes.json"
err := d.mergeAPIModel()
if err != nil {
t.Fatalf("unexpected error calling mergeAPIModel with no --set flag defined: %s", err.Error())
}

d = &deployCmd{}
d.apimodelPath = "../pkg/acsengine/testdata/simple/kubernetes.json"
d.set = []string{"masterProfile.count=3,linuxProfile.adminUsername=testuser"}
err = d.mergeAPIModel()
if err != nil {
t.Fatalf("unexpected error calling mergeAPIModel with one --set flag: %s", err.Error())
}

d = &deployCmd{}
d.apimodelPath = "../pkg/acsengine/testdata/simple/kubernetes.json"
d.set = []string{"masterProfile.count=3", "linuxProfile.adminUsername=testuser"}
err = d.mergeAPIModel()
if err != nil {
t.Fatalf("unexpected error calling mergeAPIModel with multiple --set flags: %s", err.Error())
}

d = &deployCmd{}
d.apimodelPath = "../pkg/acsengine/testdata/simple/kubernetes.json"
d.set = []string{"agentPoolProfiles[0].count=1"}
err = d.mergeAPIModel()
if err != nil {
t.Fatalf("unexpected error calling mergeAPIModel with one --set flag to override an array property: %s", err.Error())
}
}

func testDeployCmdMLoadAPIModel(t *testing.T) {
d := &deployCmd{}
r := &cobra.Command{}
f := r.Flags()

addAuthFlags(&d.authArgs, f)

fakeRawSubscriptionID := "6dc93fae-9a76-421f-bbe5-cc6460ea81cb"
fakeSubscriptionID, err := uuid.FromString(fakeRawSubscriptionID)
if err != nil {
t.Fatalf("Invalid SubscriptionId in Test: %s", err)
}

d.apimodelPath = "../pkg/acsengine/testdata/simple/kubernetes.json"
d.set = []string{"agentPoolProfiles[0].count=1"}
d.SubscriptionID = fakeSubscriptionID
d.rawSubscriptionID = fakeRawSubscriptionID

d.validateArgs(r, []string{"../pkg/acsengine/testdata/simple/kubernetes.json"})
d.mergeAPIModel()
err = d.loadAPIModel(r, []string{"../pkg/acsengine/testdata/simple/kubernetes.json"})
if err != nil {
t.Fatalf("unexpected error loading api model: %s", err.Error())
}
}
16 changes: 16 additions & 0 deletions docs/kubernetes/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,22 @@ Administrative note: By default, the directory where acs-engine stores cluster c

**Note**: If the cluster is using an existing VNET please see the [Custom VNET](features.md#feat-custom-vnet) feature documentation for additional steps that must be completed after cluster provisioning.

The deploy command lets you override any values under the properties tag (even in arrays) from the cluster definition file without having to update the file. You can use the `--set` flag to do that. For example:

```bash
acs-engine deploy --resource-group "your-resource-group" \
--location "westeurope" \
--subscription-id "your-subscription-id" \
--api-model "./apimodel.json" \
--set masterProfile.dnsPrefix="your-dns-prefix-override" \
--set agentPoolProfiles[0].name="your-agentpool-0-name-override" \
--set agentPoolProfiles[0].count=1 \
--set linuxProfile.ssh.publicKeys[0].keyData="ssh-rsa PUBLICKEY azureuser@linuxvm" \
--set servicePrincipalProfile.clientId="spn-client-id" \
--set servicePrincipalProfile.secret="spn-client-secret"
```


<a href="#the-long-way"></a>

## ACS Engine the Long Way
Expand Down