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

feat: autofill client Id and client secret for generate command #1766

Merged
merged 1 commit into from
Aug 19, 2019
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
8 changes: 4 additions & 4 deletions cmd/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ func getAPIModel(baseAPIModel string, useManagedIdentity bool, clientID, clientS
clientSecret)
}

func getAPIModelWithoutServicePrincipalProfile(baseAPIModel string, useManagedIdentity bool) string {
func getAPIModelWithoutServicePrincipalProfile(useManagedIdentity bool) string {
return fmt.Sprintf(
baseAPIModel,
ExampleAPIModelWithoutServicePrincipalProfile,
strconv.FormatBool(useManagedIdentity))
}

Expand Down Expand Up @@ -268,7 +268,7 @@ func TestAPIModelWithoutServicePrincipalProfileAndClientIdAndSecretInCmd(t *test
Translator: nil,
}

apimodel := getAPIModelWithoutServicePrincipalProfile(ExampleAPIModelWithoutServicePrincipalProfile, false)
apimodel := getAPIModelWithoutServicePrincipalProfile(false)
TestClientIDInCmd, err := uuid.FromString("DEC923E3-1EF1-4745-9516-37906D56DEC4")
if err != nil {
t.Fatalf("Invalid ClientID in Test: %s", err)
Expand Down Expand Up @@ -373,7 +373,7 @@ func TestAPIModelWithoutServicePrincipalProfileAndWithoutClientIdAndSecretInCmd(
Translator: nil,
}

apimodel := getAPIModelWithoutServicePrincipalProfile(ExampleAPIModelWithoutServicePrincipalProfile, false)
apimodel := getAPIModelWithoutServicePrincipalProfile(false)

cs, ver, err := apiloader.DeserializeContainerService([]byte(apimodel), false, false, nil)
if err != nil {
Expand Down
44 changes: 41 additions & 3 deletions cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/Azure/aks-engine/pkg/engine"
"github.com/Azure/aks-engine/pkg/engine/transform"
"github.com/Azure/aks-engine/pkg/i18n"
"github.com/gofrs/uuid"
"github.com/leonelquinteros/gotext"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
Expand All @@ -38,6 +39,11 @@ type generateCmd struct {
containerService *api.ContainerService
apiVersion string
locale *gotext.Locale

rawClientID string

ClientID uuid.UUID
ClientSecret string
}

func newGenerateCmd() *cobra.Command {
Expand All @@ -59,7 +65,13 @@ func newGenerateCmd() *cobra.Command {
if err := gc.loadAPIModel(); err != nil {
return errors.Wrap(err, "loading API model in generateCmd")
}

if gc.apiVersion == "vlabs" {
if err := gc.validateAPIModelAsVLabs(); err != nil {
return errors.Wrap(err, "validating API model after populating values")
}
} else {
log.Warnf("API model validation is only available for \"apiVersion\": \"vlabs\", skipping validation...")
}
return gc.run()
},
}
Expand All @@ -72,7 +84,8 @@ func newGenerateCmd() *cobra.Command {
f.StringArrayVar(&gc.set, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.BoolVar(&gc.noPrettyPrint, "no-pretty-print", false, "skip pretty printing the output")
f.BoolVar(&gc.parametersOnly, "parameters-only", false, "only output parameters files")

f.StringVar(&gc.rawClientID, "client-id", "", "client id")
f.StringVar(&gc.ClientSecret, "client-secret", "", "client secret")
return generateCmd
}

Expand Down Expand Up @@ -100,6 +113,8 @@ func (gc *generateCmd) validate(cmd *cobra.Command, args []string) error {
return errors.Errorf("specified api model does not exist (%s)", gc.apimodelPath)
}

gc.ClientID, _ = uuid.FromString(gc.rawClientID)

return nil
}

Expand Down Expand Up @@ -132,7 +147,7 @@ func (gc *generateCmd) loadAPIModel() error {
Locale: gc.locale,
},
}
gc.containerService, gc.apiVersion, err = apiloader.LoadContainerServiceFromFile(gc.apimodelPath, true, false, nil)
honcao marked this conversation as resolved.
Show resolved Hide resolved
gc.containerService, gc.apiVersion, err = apiloader.LoadContainerServiceFromFile(gc.apimodelPath, false, false, nil)
if err != nil {
return errors.Wrap(err, "error parsing the api model")
}
Expand Down Expand Up @@ -166,9 +181,32 @@ func (gc *generateCmd) loadAPIModel() error {
prop.CertificateProfile.CaPrivateKey = string(caKeyBytes)
}

if err = gc.autofillApimodel(); err != nil {
return err
}
return nil
}

func (gc *generateCmd) autofillApimodel() error {
// set the client id and client secret by command flags
k8sConfig := gc.containerService.Properties.OrchestratorProfile.KubernetesConfig
useManagedIdentity := k8sConfig != nil && k8sConfig.UseManagedIdentity
if !useManagedIdentity {
if (gc.containerService.Properties.ServicePrincipalProfile == nil || ((gc.containerService.Properties.ServicePrincipalProfile.ClientID == "" || gc.containerService.Properties.ServicePrincipalProfile.ClientID == "00000000-0000-0000-0000-000000000000") && gc.containerService.Properties.ServicePrincipalProfile.Secret == "")) && gc.ClientID.String() != "" && gc.ClientSecret != "" {
gc.containerService.Properties.ServicePrincipalProfile = &api.ServicePrincipalProfile{
ClientID: gc.ClientID.String(),
Secret: gc.ClientSecret,
}
}
}
return nil
}

// validateAPIModelAsVLabs converts the ContainerService object to a vlabs ContainerService object and validates it
func (gc *generateCmd) validateAPIModelAsVLabs() error {
return api.ConvertContainerServiceToVLabs(gc.containerService).Validate(false)
}

func (gc *generateCmd) run() error {
log.Infoln(fmt.Sprintf("Generating assets into %s...", gc.outputDirectory))

Expand Down
130 changes: 129 additions & 1 deletion cmd/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
package cmd

import (
"os"
"testing"

"github.com/Azure/aks-engine/pkg/api"
"github.com/gofrs/uuid"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)

Expand All @@ -15,7 +19,7 @@ func TestNewGenerateCmd(t *testing.T) {
t.Fatalf("generate command should have use %s equal %s, short %s equal %s and long %s equal to %s", command.Use, generateName, command.Short, generateShortDescription, command.Long, generateLongDescription)
}

expectedFlags := []string{"api-model", "output-directory", "ca-certificate-path", "ca-private-key-path", "set", "no-pretty-print", "parameters-only"}
expectedFlags := []string{"api-model", "output-directory", "ca-certificate-path", "ca-private-key-path", "set", "no-pretty-print", "parameters-only", "client-id", "client-secret"}
for _, f := range expectedFlags {
if command.Flags().Lookup(f) == nil {
t.Fatalf("generate command should have flag %s", f)
Expand Down Expand Up @@ -123,3 +127,127 @@ func TestGenerateCmdMLoadAPIModel(t *testing.T) {
t.Fatalf("unexpected error loading api model: %s", err.Error())
}
}

func TestAPIModelWithoutServicePrincipalProfileAndClientIdAndSecretInGenerateCmd(t *testing.T) {
apiloader := &api.Apiloader{
Translator: nil,
}

apimodel := getAPIModelWithoutServicePrincipalProfile(false)

cs, ver, err := apiloader.DeserializeContainerService([]byte(apimodel), false, false, nil)
if err != nil {
t.Fatalf("unexpected error deserializing the example apimodel: %s", err)
}
cs.Properties.LinuxProfile.SSH.PublicKeys[0].KeyData = "ssh test"

clientID, _ := uuid.FromString("e810b868-afab-412d-98cc-ce7db5cc840b")
clientSecret := "Test Client secret"
generateCmd := &generateCmd{
apimodelPath: "./this/is/unused.json",
outputDirectory: "_test_output",
ClientID: clientID,
ClientSecret: clientSecret,
containerService: cs,
apiVersion: ver,
}
err = generateCmd.autofillApimodel()
if err != nil {
t.Fatalf("unexpected error autofilling the example apimodel: %s", err)
}

defer os.RemoveAll(generateCmd.outputDirectory)

if generateCmd.containerService.Properties.ServicePrincipalProfile == nil || generateCmd.containerService.Properties.ServicePrincipalProfile.ClientID == "" || generateCmd.containerService.Properties.ServicePrincipalProfile.Secret == "" {
t.Fatalf("expected service principal profile to be populated from deployment command arguments")
}

if generateCmd.containerService.Properties.ServicePrincipalProfile.ClientID != clientID.String() {
t.Fatalf("expected service principal profile client id to be %s but got %s", clientID.String(), generateCmd.containerService.Properties.ServicePrincipalProfile.ClientID)
}

if generateCmd.containerService.Properties.ServicePrincipalProfile.Secret != clientSecret {
t.Fatalf("expected service principal profile client secret to be %s but got %s", clientSecret, generateCmd.containerService.Properties.ServicePrincipalProfile.Secret)
}

err = generateCmd.validateAPIModelAsVLabs()
if err != nil {
t.Fatalf("unexpected error validateAPIModelAsVLabs the example apimodel: %s", err)
}
}

func TestAPIModelWithoutServicePrincipalProfileAndWithoutClientIdAndSecretInGenerateCmd(t *testing.T) {
apiloader := &api.Apiloader{
Translator: nil,
}

apimodel := getAPIModelWithoutServicePrincipalProfile(false)

cs, ver, err := apiloader.DeserializeContainerService([]byte(apimodel), false, false, nil)
if err != nil {
t.Fatalf("unexpected error deserializing the example apimodel: %s", err)
}
cs.Properties.LinuxProfile.SSH.PublicKeys[0].KeyData = "ssh test"
generateCmd := &generateCmd{
apimodelPath: "./this/is/unused.json",
outputDirectory: "_test_output",
containerService: cs,
apiVersion: ver,
}
err = generateCmd.autofillApimodel()
if err != nil {
t.Fatalf("unexpected error autofilling the example apimodel: %s", err)
}

defer os.RemoveAll(generateCmd.outputDirectory)

if generateCmd.containerService.Properties.ServicePrincipalProfile != nil {
t.Fatalf("expected service principal profile to be nil for unmanaged identity, where client id and secret are not supplied in api model and deployment command")
}

err = generateCmd.validateAPIModelAsVLabs()
expectedErr := errors.New("ServicePrincipalProfile must be specified with Orchestrator Kubernetes")

if err.Error() != expectedErr.Error() {
t.Fatalf("expected validate generate command to return error %s, but instead got %s", expectedErr.Error(), err.Error())
}
}

func TestAPIModelWithManagedIdentityWithoutServicePrincipalProfileAndClientIdAndSecretInGenerateCmd(t *testing.T) {
apiloader := &api.Apiloader{
Translator: nil,
}

apimodel := getAPIModelWithoutServicePrincipalProfile(true)

cs, ver, err := apiloader.DeserializeContainerService([]byte(apimodel), false, false, nil)
if err != nil {
t.Fatalf("unexpected error deserializing the example apimodel: %s", err)
}
cs.Properties.LinuxProfile.SSH.PublicKeys[0].KeyData = "ssh test"
clientID, _ := uuid.FromString("e810b868-afab-412d-98cc-ce7db5cc840b")
clientSecret := "Test Client secret"
generateCmd := &generateCmd{
apimodelPath: "./this/is/unused.json",
outputDirectory: "_test_output",
ClientID: clientID,
ClientSecret: clientSecret,
containerService: cs,
apiVersion: ver,
}
err = generateCmd.autofillApimodel()
if err != nil {
t.Fatalf("unexpected error autofilling the example apimodel: %s", err)
}

defer os.RemoveAll(generateCmd.outputDirectory)

if generateCmd.containerService.Properties.ServicePrincipalProfile != nil {
t.Fatalf("expected service principal profile to be nil for managed identity")
}

err = generateCmd.validateAPIModelAsVLabs()
if err != nil {
t.Fatalf("unexpected error validateAPIModelAsVLabs the example apimodel: %s", err)
}
}