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

Commit

Permalink
feat: autofill client Id and client secret for generate command
Browse files Browse the repository at this point in the history
  • Loading branch information
honcao committed Aug 12, 2019
1 parent 0b22197 commit be97c1a
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 8 deletions.
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)
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)
}
}

0 comments on commit be97c1a

Please sign in to comment.