Skip to content

Commit

Permalink
Support multiple cmk config files
Browse files Browse the repository at this point in the history
  • Loading branch information
wongni committed Jun 15, 2022
1 parent 17e5e35 commit a97faea
Show file tree
Hide file tree
Showing 19 changed files with 177 additions and 111 deletions.
47 changes: 29 additions & 18 deletions pkg/executables/cmk.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var cmkConfigTemplate string

const (
cmkPath = "cmk"
cmkConfigFileName = "cmk_tmp.ini"
cmkConfigFileNameTemplate = "cmk_%s.ini"
Shared = "Shared"
defaultCloudStackPreflightTimeout = "30"
rootDomain = "ROOT"
Expand Down Expand Up @@ -432,39 +432,50 @@ func (c *Cmk) CleanupVms(ctx context.Context, clusterName string, dryRun bool) e

func (c *Cmk) exec(ctx context.Context, args ...string) (stdout bytes.Buffer, err error) {
if err != nil {
return bytes.Buffer{}, fmt.Errorf("failed get environment map: %v", err)
return stdout, fmt.Errorf("failed get environment map: %v", err)
}
configFile, err := c.buildCmkConfigFile()

configFiles, err := c.buildCmkConfigFiles()
if err != nil {
return bytes.Buffer{}, fmt.Errorf("failed cmk validations: %v", err)
return stdout, fmt.Errorf("failed cmk validations: %v", err)
}
argsWithConfigFile := append([]string{"-c", configFile}, args...)

return c.executable.Execute(ctx, argsWithConfigFile...)
for _, configFile := range configFiles {
argsWithConfigFile := append([]string{"-c", configFile}, args...)
output, err := c.executable.Execute(ctx, argsWithConfigFile...)
stdout.Write(output.Bytes())
if err != nil {
return stdout, err
}
}

return stdout, nil
}

func (c *Cmk) buildCmkConfigFile() (configFile string, err error) {
func (c *Cmk) buildCmkConfigFiles() (configFiles []string, err error) {
t := templater.New(c.writer)

cloudstackPreflightTimeout := defaultCloudStackPreflightTimeout
if timeout, isSet := os.LookupEnv("CLOUDSTACK_PREFLIGHT_TIMEOUT"); isSet {
if _, err := strconv.ParseUint(timeout, 10, 16); err != nil {
return "", fmt.Errorf("CLOUDSTACK_PREFLIGHT_TIMEOUT must be a number: %v", err)
return []string{}, fmt.Errorf("CLOUDSTACK_PREFLIGHT_TIMEOUT must be a number: %v", err)
}
cloudstackPreflightTimeout = timeout
}

c.config.Timeout = cloudstackPreflightTimeout
writtenFileName, err := t.WriteToFile(cmkConfigTemplate, c.config, cmkConfigFileName)
if err != nil {
return "", fmt.Errorf("creating file for cmk config: %v", err)
}
configFile, err = filepath.Abs(writtenFileName)
if err != nil {
return "", fmt.Errorf("failed to generate absolute filepath for generated config file at %s", writtenFileName)
for _, profile := range c.config.Profiles {
profile.Timeout = cloudstackPreflightTimeout
writtenFileName, err := t.WriteToFile(cmkConfigTemplate, profile, fmt.Sprintf(cmkConfigFileNameTemplate, profile.Name))
if err != nil {
return []string{}, fmt.Errorf("creating file for cmk config: %v", err)
}
configFile, err := filepath.Abs(writtenFileName)
if err != nil {
return []string{}, fmt.Errorf("failed to generate absolute filepath for generated config file at %s", writtenFileName)
}
configFiles = append(configFiles, configFile)
}

return configFile, nil
return configFiles, nil
}

type cmkTemplate struct {
Expand Down
63 changes: 51 additions & 12 deletions pkg/executables/cmk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,48 @@ import (
)

const (
cmkConfigFileName = "cmk_tmp.ini"
accountName = "account1"
rootDomain = "ROOT"
rootDomainId = "5300cdac-74d5-11ec-8696-c81f66d3e965"
domain = "foo/domain1"
domainName = "domain1"
domainId = "7700cdac-74d5-11ec-8696-c81f66d3e965"
domain2 = "foo/bar/domain1"
domain2Name = "domain1"
domain2Id = "8800cdac-74d5-11ec-8696-c81f66d3e965"
zoneId = "4e3b338d-87a6-4189-b931-a1747edeea8f"
cmkConfigFileName = "cmk_test_name.ini"
cmkConfigFileName2 = "cmk_test_name_2.ini"
accountName = "account1"
rootDomain = "ROOT"
rootDomainId = "5300cdac-74d5-11ec-8696-c81f66d3e965"
domain = "foo/domain1"
domainName = "domain1"
domainId = "7700cdac-74d5-11ec-8696-c81f66d3e965"
domain2 = "foo/bar/domain1"
domain2Name = "domain1"
domain2Id = "8800cdac-74d5-11ec-8696-c81f66d3e965"
zoneId = "4e3b338d-87a6-4189-b931-a1747edeea8f"
)

var execConfig = decoder.CloudStackExecConfig{
Instances: []decoder.CloudStackInstanceConfig{
Profiles: []decoder.CloudStackProfileConfig{
{
Name: "test_name",
ApiKey: "test",
SecretKey: "test",
ManagementUrl: "http://1.1.1.1:8080/client/api",
},
},
}

var execConfigWithMultipleProfiles = decoder.CloudStackExecConfig{
Profiles: []decoder.CloudStackProfileConfig{
{
Name: "test_name",
ApiKey: "test",
SecretKey: "test",
ManagementUrl: "http://1.1.1.1:8080/client/api",
},
{
Name: "test_name_2",
ApiKey: "test_2",
SecretKey: "test_2",
ManagementUrl: "http://1.1.1.1:8080/client/api_2",
},
},
}

var zones = []v1alpha1.CloudStackZone{
{Name: "TEST_RESOURCE", Network: v1alpha1.CloudStackResourceIdentifier{Name: "TEST_RESOURCE"}},
{Name: "TEST_RESOURCE", Network: v1alpha1.CloudStackResourceIdentifier{Id: "TEST_RESOURCE"}},
Expand Down Expand Up @@ -104,6 +123,26 @@ func TestValidateCloudStackConnectionSuccess(t *testing.T) {
}
}

func TestValidateMultipleCloudStackProfiles(t *testing.T) {
_, writer := test.NewWriter(t)
ctx := context.Background()
mockCtrl := gomock.NewController(t)

executable := mockexecutables.NewMockExecutable(mockCtrl)
configFilePath, _ := filepath.Abs(filepath.Join(writer.Dir(), "generated", cmkConfigFileName))
expectedArgs := []string{"-c", configFilePath, "sync"}
executable.EXPECT().Execute(ctx, expectedArgs).Return(bytes.Buffer{}, nil)
configFilePath2, _ := filepath.Abs(filepath.Join(writer.Dir(), "generated", cmkConfigFileName2))
expectedArgs2 := []string{"-c", configFilePath2, "sync"}
executable.EXPECT().Execute(ctx, expectedArgs2).Return(bytes.Buffer{}, nil)

c := executables.NewCmk(executable, writer, execConfigWithMultipleProfiles)
err := c.ValidateCloudStackConnection(ctx)
if err != nil {
t.Fatalf("Cmk.ValidateCloudStackConnection() error = %v, want nil", err)
}
}

func TestValidateCloudStackConnectionError(t *testing.T) {
_, writer := test.NewWriter(t)
ctx := context.Background()
Expand Down
12 changes: 5 additions & 7 deletions pkg/executables/config/cmk.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ asyncblock = true
timeout = {{ .Timeout }}
output = json
verifycert = {{ .VerifySsl }}
profile =
profile = {{ .Name }}
autocomplete = true

{{ range $instance := .Instances }}
[{{ $instance.Name }}]
url = {{ $instance.ManagementUrl }}
apikey = {{ $instance.ApiKey }}
secretkey = {{ $instance.SecretKey }}
{{- end}}
[{{ .Name }}]
url = {{ .ManagementUrl }}
apikey = {{ .ApiKey }}
secretkey = {{ .SecretKey }}
4 changes: 2 additions & 2 deletions pkg/providers/cloudstack/cloudstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,11 +361,11 @@ func (p *cloudstackProvider) validateEnv(ctx context.Context) error {
if err != nil {
return fmt.Errorf("failed to parse environment variable exec config: %v", err)
}
if len(execConfig.Instances) <= 0 {
if len(execConfig.Profiles) <= 0 {
return errors.New("cloudstack instances are not defined")
}

for _, instance := range execConfig.Instances {
for _, instance := range execConfig.Profiles {
if err := p.validateManagementApiEndpoint(instance.ManagementUrl); err != nil {
return fmt.Errorf("CloudStack instance %s's managementApiEndpoint %s is invalid",
instance.Name, instance.ManagementUrl)
Expand Down
4 changes: 1 addition & 3 deletions pkg/providers/cloudstack/cloudstack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,11 @@ const (
/* Generated from ini file (like the following) then b64 encoded: `cat fake-cloud-config.ini | base64 | tr -d '\n'`
[Global]
verify-ssl = false
[Instance1]
api-key = test-key1
secret-key = test-secret1
api-url = http://127.16.0.1:8080/client/api
*/
expectedCloudStackCloudConfig = "W0dsb2JhbF0KdmVyaWZ5LXNzbCA9IGZhbHNlCgpbSW5zdGFuY2UxXQphcGkta2V5ID0gdGVzdC1rZXkxCnNlY3JldC1rZXkgPSB0ZXN0LXNlY3JldDEKYXBpLXVybCA9IGh0dHA6Ly8xMjcuMTYuMC4xOjgwODAvY2xpZW50L2FwaQ=="
expectedCloudStackCloudConfig = "W0dsb2JhbF0KdmVyaWZ5LXNzbCA9IGZhbHNlCmFwaS1rZXkgPSB0ZXN0LWtleTEKc2VjcmV0LWtleSA9IHRlc3Qtc2VjcmV0MQphcGktdXJsID0gaHR0cDovLzEyNy4xNi4wLjE6ODA4MC9jbGllbnQvYXBpCg=="
)

func givenClusterConfig(t *testing.T, fileName string) *v1alpha1.Cluster {
Expand Down
42 changes: 18 additions & 24 deletions pkg/providers/cloudstack/decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ const (
EksacloudStackCloudConfigB64SecretKey = "EKSA_CLOUDSTACK_B64ENCODED_SECRET"
CloudStackCloudConfigB64SecretKey = "CLOUDSTACK_B64ENCODED_SECRET"
EksaCloudStackHostPathToMount = "EKSA_CLOUDSTACK_HOST_PATHS_TO_MOUNT"
globalSectionName = "Global"
defaultSectionName = "DEFAULT"
)

// ParseCloudStackSecret parses the input b64 string into the ini object to extract out the api key, secret key, and url
Expand All @@ -31,23 +29,11 @@ func ParseCloudStackSecret() (*CloudStackExecConfig, error) {
if err != nil {
return nil, fmt.Errorf("failed to extract values from %s with ini: %v", EksacloudStackCloudConfigB64SecretKey, err)
}
globalSection, err := cfg.GetSection(globalSectionName)
if err != nil {
return nil, fmt.Errorf("failed to extract section '%s' from %s: %v", globalSectionName, EksacloudStackCloudConfigB64SecretKey, err)
}
verifySsl, err := globalSection.GetKey("verify-ssl")
verifySslValue := "true"
if err == nil {
verifySslValue = verifySsl.Value()
if _, err := strconv.ParseBool(verifySslValue); err != nil {
return nil, fmt.Errorf("'verify-ssl' has invalid boolean string %s: %v", verifySslValue, err)
}
}

cloudstackInstances := []CloudStackInstanceConfig{}
cloudstackProfiles := []CloudStackProfileConfig{}
sections := cfg.Sections()
for _, section := range sections {
if section.Name() == globalSectionName || section.Name() == defaultSectionName {
if section.Name() == "DEFAULT" {
continue
}

Expand All @@ -63,33 +49,41 @@ func ParseCloudStackSecret() (*CloudStackExecConfig, error) {
if err != nil {
return nil, fmt.Errorf("failed to extract value of 'api-url' from %s: %v", EksacloudStackCloudConfigB64SecretKey, err)
}
cloudstackInstances = append(cloudstackInstances, CloudStackInstanceConfig{
verifySsl, err := section.GetKey("verify-ssl")
verifySslValue := "true"
if err == nil {
verifySslValue = verifySsl.Value()
if _, err := strconv.ParseBool(verifySslValue); err != nil {
return nil, fmt.Errorf("'verify-ssl' has invalid boolean string %s: %v", verifySslValue, err)
}
}
cloudstackProfiles = append(cloudstackProfiles, CloudStackProfileConfig{
Name: section.Name(),
ApiKey: apiKey.Value(),
SecretKey: secretKey.Value(),
ManagementUrl: apiUrl.Value(),
VerifySsl: verifySslValue,
})
}

if len(cloudstackInstances) == 0 {
if len(cloudstackProfiles) == 0 {
return nil, fmt.Errorf("no instance found from %s", EksacloudStackCloudConfigB64SecretKey)
}

return &CloudStackExecConfig{
Instances: cloudstackInstances,
VerifySsl: verifySslValue,
Profiles: cloudstackProfiles,
}, nil
}

type CloudStackExecConfig struct {
Instances []CloudStackInstanceConfig
VerifySsl string
Timeout string
Profiles []CloudStackProfileConfig
}

type CloudStackInstanceConfig struct {
type CloudStackProfileConfig struct {
Name string
ApiKey string
SecretKey string
ManagementUrl string
VerifySsl string
Timeout string
}
Loading

0 comments on commit a97faea

Please sign in to comment.