Skip to content

Commit 2937061

Browse files
committed
Adding fixes after review
LMCROSSITXSADEPLOY-2301
1 parent 9b5f948 commit 2937061

9 files changed

Lines changed: 206 additions & 130 deletions

clients/cfrestclient/cloud_foundry_operations_extended.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ type CloudFoundryOperationsExtended interface {
1010
GetApplicationRoutes(appGuid string) ([]models.ApplicationRoute, error)
1111
GetServiceInstances(mtaId, mtaNamespace, spaceGuid string) ([]models.CloudFoundryServiceInstance, error)
1212
GetServiceBindings(serviceName string) ([]models.ServiceBinding, error)
13+
GetServiceInstanceByName(serviceName, spaceGuid string) (models.CloudFoundryServiceInstance, error)
1314
}

clients/cfrestclient/fakes/fake_cloud_foundry_client.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,7 @@ func (f FakeCloudFoundryClient) GetServiceInstances(mtaId, mtaNamespace, spaceGu
3434
func (f FakeCloudFoundryClient) GetServiceBindings(serviceName string) ([]models.ServiceBinding, error) {
3535
return f.ServiceBindings, f.ServiceBindingsErr
3636
}
37+
38+
func (f FakeCloudFoundryClient) GetServiceInstanceByName(serviceName, spaceGuid string) (models.CloudFoundryServiceInstance, error) {
39+
return f.Services[0], f.ServiceBindingsErr
40+
}

clients/cfrestclient/resilient/resilient_rest_cloud_foundry_client_extended.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package resilient
22

33
import (
4+
"time"
5+
46
"github.com/cloudfoundry-incubator/multiapps-cli-plugin/clients/cfrestclient"
57
"github.com/cloudfoundry-incubator/multiapps-cli-plugin/clients/models"
6-
"time"
78
)
89

910
type ResilientCloudFoundryRestClient struct {
@@ -46,6 +47,12 @@ func (c ResilientCloudFoundryRestClient) GetServiceBindings(serviceName string)
4647
}, c.MaxRetriesCount, c.RetryInterval)
4748
}
4849

50+
func (c ResilientCloudFoundryRestClient) GetServiceInstanceByName(serviceName, spaceGuid string) (models.CloudFoundryServiceInstance, error) {
51+
return retryOnError(func() (models.CloudFoundryServiceInstance, error) {
52+
return c.CloudFoundryRestClient.GetServiceInstanceByName(serviceName, spaceGuid)
53+
}, c.MaxRetriesCount, c.RetryInterval)
54+
}
55+
4956
func retryOnError[T any](operation func() (T, error), retries int, retryInterval time.Duration) (T, error) {
5057
result, err := operation()
5158
for shouldRetry(retries, err) {

clients/cfrestclient/rest_cloud_foundry_client_extended.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,27 @@ func (c CloudFoundryRestClient) GetServiceBindings(serviceName string) ([]models
114114
return getPaginatedResourcesWithIncluded(getServiceBindingsUrl, token, c.isSslDisabled, buildServiceBinding)
115115
}
116116

117+
func (c CloudFoundryRestClient) GetServiceInstanceByName(serviceName, spaceGuid string) (models.CloudFoundryServiceInstance, error) {
118+
token, err := c.cliConn.AccessToken()
119+
if err != nil {
120+
return models.CloudFoundryServiceInstance{}, fmt.Errorf("failed to retrieve access token: %s", err)
121+
}
122+
apiEndpoint, _ := c.cliConn.ApiEndpoint()
123+
124+
getServicesUrl := fmt.Sprintf("%s/%sservice_instances?names=%s&space_guids=%s",
125+
apiEndpoint, cfBaseUrl, serviceName, spaceGuid)
126+
services, err := getPaginatedResourcesWithIncluded(getServicesUrl, token, c.isSslDisabled, buildServiceInstance)
127+
if err != nil {
128+
return models.CloudFoundryServiceInstance{}, err
129+
}
130+
if len(services) == 0 {
131+
return models.CloudFoundryServiceInstance{}, fmt.Errorf("service instance not found")
132+
}
133+
134+
resultService := services[0]
135+
return resultService, nil
136+
}
137+
117138
func getPaginatedResources[T any](url, token string, isSslDisabled bool) ([]T, error) {
118139
var result []T
119140
for url != "" {

commands/blue_green_deploy_command.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type BlueGreenDeployCommand struct {
2020
// NewBlueGreenDeployCommand creates a new BlueGreenDeployCommand.
2121
func NewBlueGreenDeployCommand() *BlueGreenDeployCommand {
2222
baseCmd := &BaseCommand{flagsParser: deployCommandLineArgumentsParser{}, flagsValidator: deployCommandFlagsValidator{}}
23-
deployCmd := &DeployCommand{baseCmd, blueGreenDeployProcessParametersSetter(), &blueGreenDeployCommandProcessTypeProvider{}, os.Stdin, 30 * time.Second}
23+
deployCmd := &DeployCommand{baseCmd, blueGreenDeployProcessParametersSetter(), &blueGreenDeployCommandProcessTypeProvider{}, os.Stdin, 30 * time.Second, nil}
2424
bgDeployCmd := &BlueGreenDeployCommand{deployCmd}
2525
baseCmd.Command = bgDeployCmd
2626
return bgDeployCmd

commands/deploy_command.go

Lines changed: 59 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"code.cloudfoundry.org/cli/v8/cf/terminal"
2121
"code.cloudfoundry.org/cli/v8/plugin"
2222
"github.com/cloudfoundry-incubator/multiapps-cli-plugin/clients/baseclient"
23+
"github.com/cloudfoundry-incubator/multiapps-cli-plugin/clients/cfrestclient"
24+
"github.com/cloudfoundry-incubator/multiapps-cli-plugin/clients/cfrestclient/resilient"
2325
"github.com/cloudfoundry-incubator/multiapps-cli-plugin/clients/models"
2426
"github.com/cloudfoundry-incubator/multiapps-cli-plugin/clients/mtaclient"
2527
"github.com/cloudfoundry-incubator/multiapps-cli-plugin/commands/retrier"
@@ -91,16 +93,23 @@ type DeployCommand struct {
9193

9294
FileUrlReader fs.File
9395
FileUrlReadTimeout time.Duration
96+
CfClient cfrestclient.CloudFoundryOperationsExtended
9497
}
9598

9699
// NewDeployCommand creates a new deploy command.
97100
func NewDeployCommand() *DeployCommand {
98101
baseCmd := &BaseCommand{flagsParser: deployCommandLineArgumentsParser{}, flagsValidator: deployCommandFlagsValidator{}}
99-
deployCmd := &DeployCommand{baseCmd, deployProcessParametersSetter(), &deployCommandProcessTypeProvider{}, os.Stdin, 30 * time.Second}
102+
deployCmd := &DeployCommand{baseCmd, deployProcessParametersSetter(), &deployCommandProcessTypeProvider{}, os.Stdin, 30 * time.Second, nil}
100103
baseCmd.Command = deployCmd
101104
return deployCmd
102105
}
103106

107+
func (c *DeployCommand) Initialize(name string, cliConnection plugin.CliConnection) {
108+
c.BaseCommand.Initialize(name, cliConnection)
109+
delegate := cfrestclient.NewCloudFoundryRestClient(cliConnection)
110+
c.CfClient = resilient.NewResilientCloudFoundryClient(delegate, maxRetriesCount, retryIntervalInSeconds)
111+
}
112+
104113
// GetPluginCommand returns the plugin command details
105114
func (c *DeployCommand) GetPluginCommand() plugin.Command {
106115
return plugin.Command{
@@ -114,7 +123,7 @@ func (c *DeployCommand) GetPluginCommand() plugin.Command {
114123
Perform action on an active deploy operation
115124
cf deploy -i OPERATION_ID -a ACTION [-u URL]
116125
117-
(EXPERIMENTAL) Deploy a multi-target app archive referenced by a remote URL
126+
Deploy a multi-target app archive referenced by a remote URL
118127
<write MTA archive URL to STDOUT> | cf deploy [-e EXT_DESCRIPTOR[,...]] [-t TIMEOUT] [--version-rule VERSION_RULE] [-u MTA_CONTROLLER_URL] [--retries RETRIES] [--no-start] [--namespace NAMESPACE] [--apply-namespace-app-names true/false] [--apply-namespace-service-names true/false] [--apply-namespace-app-routes true/false] [--apply-namespace-as-suffix true/false ] [--delete-services] [--delete-service-keys] [--delete-service-brokers] [--keep-files] [--no-restart-subscribed-apps] [--do-not-fail-on-missing-permissions] [--abort-on-error] [--strategy STRATEGY] [--skip-testing-phase] [--skip-idle-start] [require-secure-parameters] [--apps-start-timeout TIMEOUT] [--apps-stage-timeout TIMEOUT] [--apps-upload-timeout TIMEOUT] [--apps-task-execution-timeout TIMEOUT]` + util.UploadEnvHelpText,
119128

120129
Options: map[string]string{
@@ -151,7 +160,7 @@ func (c *DeployCommand) GetPluginCommand() plugin.Command {
151160
util.CombineFullAndShortParameters(startTimeoutOpt, timeoutOpt): "Start app timeout in seconds",
152161
util.GetShortOption(shouldBackupPreviousVersionOpt): "(EXPERIMENTAL) (STRATEGY: BLUE-GREEN, INCREMENTAL-BLUE-GREEN) Backup previous version of applications, use new cli command \"rollback-mta\" to rollback to the previous version",
153162
util.GetShortOption(dependencyAwareStopOrderOpt): "(EXPERIMENTAL) (STRATEGY: BLUE-GREEN, INCREMENTAL-BLUE-GREEN) Stop apps in a dependency-aware order during the resume phase of a blue-green deployment",
154-
util.GetShortOption(requireSecureParameters): "Pass secrets to the deploy service in a secure way",
163+
util.GetShortOption(requireSecureParameters): "(EXPERIMENTAL) Pass secrets to the deploy service in a secure way",
155164
},
156165
},
157166
}
@@ -280,6 +289,7 @@ func (c *DeployCommand) executeInternal(positionalArgs []string, dsHost string,
280289
sequentialUpload := conf.GetUploadChunksSequentially()
281290
disableProgressBar := conf.GetDisableUploadProgressBar()
282291
fileUploader := NewFileUploader(mtaClient, namespace, uploadChunkSize, sequentialUpload, disableProgressBar)
292+
var yamlBytes []byte
283293

284294
if isUrl {
285295
var fileId string
@@ -330,6 +340,41 @@ func (c *DeployCommand) executeInternal(positionalArgs []string, dsHost string,
330340
return Failure
331341
}
332342

343+
if GetBoolOpt(requireSecureParameters, flags) {
344+
// Collect special ENVs: __MTA___<name>, __MTA_JSON___<name>, __MTA_CERT___<name>
345+
parameters, err := secure_parameters.CollectFromEnv("__MTA")
346+
if err != nil {
347+
ui.Failed("Secure parameters error: %s", err)
348+
return Failure
349+
}
350+
351+
if len(parameters) == 0 {
352+
ui.Failed("No secure parameters found in environment. Set variables like __MTA___<name>, __MTA_JSON___<name>, or __MTA_CERT___<name>.")
353+
return Failure
354+
}
355+
356+
userProvidedServiceName := getUpsName(mtaId, namespace)
357+
358+
isUpsCreated, _, err := c.validateUpsExistsOrElseCreateIt(userProvidedServiceName, "v1")
359+
if err != nil {
360+
ui.Failed("Could not ensure user-provided service %s: %v", userProvidedServiceName, err)
361+
return Failure
362+
}
363+
364+
if isUpsCreated {
365+
ui.Say("Created user-provided service %s for secure parameters.", terminal.EntityNameColor(userProvidedServiceName))
366+
} else {
367+
ui.Say("Using existing user-provided service %s for secure parameters.", terminal.EntityNameColor(userProvidedServiceName))
368+
}
369+
370+
schemaVer := descriptor.SchemaVersion
371+
yamlBytes, err = secure_parameters.BuildSecureExtension(parameters, mtaId, schemaVer)
372+
if err != nil {
373+
ui.Failed("Could not build secure extension: %s", err)
374+
return Failure
375+
}
376+
}
377+
333378
// Upload the MTA archive file
334379
uploadedArchivePartIds, uploadStatus = c.uploadFiles([]string{mtaArchivePath}, fileUploader)
335380
if uploadStatus == Failure {
@@ -358,39 +403,6 @@ func (c *DeployCommand) executeInternal(positionalArgs []string, dsHost string,
358403
}
359404

360405
if GetBoolOpt(requireSecureParameters, flags) {
361-
// Collect special ENVs: __MTA___<name>, __MTA_JSON___<name>, __MTA_CERT___<name>
362-
parameters, err := secure_parameters.CollectFromEnv("__MTA")
363-
if err != nil {
364-
ui.Failed("Secure parameters error: %s", err)
365-
return Failure
366-
}
367-
368-
if len(parameters) == 0 {
369-
ui.Failed("No secure parameters found in environment. Set variables like __MTA___<name>, __MTA_JSON___<name>, or __MTA_CERT___<name>.")
370-
return Failure
371-
}
372-
373-
userProvidedServiceName := getUpsName(mtaId, namespace)
374-
375-
isUpsCreated, _, err := c.validateUpsExistsOrElseCreateIt(userProvidedServiceName, "v1")
376-
if err != nil {
377-
ui.Failed("Could not ensure user-provided service %s: %v", userProvidedServiceName, err)
378-
return Failure
379-
}
380-
381-
if isUpsCreated {
382-
ui.Say("Created user-provided service %s for secure parameters.", terminal.EntityNameColor(userProvidedServiceName))
383-
} else {
384-
ui.Say("Using existing user-provided service %s for secure parameters.", terminal.EntityNameColor(userProvidedServiceName))
385-
}
386-
387-
schemaVer := ""
388-
yamlBytes, err := secure_parameters.BuildSecureExtension(parameters, mtaId, schemaVer)
389-
if err != nil {
390-
ui.Failed("Could not build secure extension: %s", err)
391-
return Failure
392-
}
393-
394406
secureFileID, err := fileUploader.UploadBytes("__mta.secure.mtaext", yamlBytes)
395407
if err != nil {
396408
ui.Failed("Could not upload secure extension: %s", err)
@@ -474,26 +486,21 @@ func getRandomEncryptionKey() (string, error) {
474486
}
475487

476488
func (c *DeployCommand) doesUpsExist(userProvidedServiceName string) (bool, error) {
477-
servicesOutput, err := c.cliConnection.CliCommandWithoutTerminalOutput("services")
478-
if err != nil {
479-
return false, fmt.Errorf("Error while checking if the UPS for secure encryption exists: %w", err)
489+
space, errSpace := c.cliConnection.GetCurrentSpace()
490+
if errSpace != nil {
491+
return false, fmt.Errorf("Cannot determine the current space")
480492
}
481-
stringTable := strings.Join(servicesOutput, "\n")
482-
return findServiceName(stringTable, userProvidedServiceName), nil
483-
}
493+
spaceGuid := space.Guid
484494

485-
func findServiceName(servicesOutput, userProvidedServiceName string) bool {
486-
userProvidedServiceNameToLower := strings.ToLower(userProvidedServiceName)
487-
for _, currentLine := range strings.Split(servicesOutput, "\n") {
488-
fields := strings.Fields(currentLine)
489-
if len(fields) == 0 {
490-
continue
491-
}
492-
if strings.ToLower(fields[0]) == userProvidedServiceNameToLower {
493-
return true
495+
_, errServiceInstance := c.CfClient.GetServiceInstanceByName(userProvidedServiceName, spaceGuid)
496+
if errServiceInstance != nil {
497+
if errServiceInstance.Error() == "service instance not found" {
498+
return false, nil
494499
}
500+
return false, fmt.Errorf("Error while checking if the UPS for secure encryption exists: %w", errServiceInstance)
495501
}
496-
return false
502+
503+
return true, nil
497504
}
498505

499506
func parseMtaArchiveArgument(rawMtaArchive interface{}) (bool, string) {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package secure_parameters
2+
3+
import (
4+
"errors"
5+
6+
"gopkg.in/yaml.v3"
7+
)
8+
9+
func BuildSecureExtension(parameters map[string]ParameterValue, mtaID string, schemaVersion string) ([]byte, error) {
10+
if len(parameters) == 0 {
11+
return nil, errors.New("no secure parameters collected")
12+
}
13+
14+
if mtaID == "" {
15+
return nil, errors.New("mtaID is required for the secure extension descriptor's field 'extends'")
16+
}
17+
18+
if schemaVersion == "" {
19+
return nil, errors.New("schemaVersion is required for the secure extension descriptor")
20+
}
21+
22+
secureExtensionDescriptor := map[string]interface{}{
23+
"_schema-version": schemaVersion,
24+
"ID": "__mta.secure",
25+
"extends": mtaID,
26+
"parameters": map[string]interface{}{},
27+
}
28+
29+
parametersDescriptor := secureExtensionDescriptor["parameters"].(map[string]interface{})
30+
for name, currentParameterValue := range parameters {
31+
parametersDescriptor[name] = getValue(&currentParameterValue)
32+
}
33+
34+
return yaml.Marshal(secureExtensionDescriptor)
35+
}

secure_parameters/secure_parameters_test.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,17 @@ func TestCollectFromEnv(t *testing.T) {
4444
t.Fatalf("The value of 'fakeJson' key is not correct")
4545
}
4646

47-
if firstJsonValue, ok := jsonValue.ObjectContent["a"].(float64); !ok || firstJsonValue != 1 {
48-
t.Fatalf("The first value of the json is not what it should be: %v", jsonValue.ObjectContent["a"])
47+
castedValue, ok := jsonValue.JSONContent.(map[string]interface{})
48+
if !ok {
49+
t.Fatal("fakeJson is not an Object")
50+
}
51+
52+
if firstJsonValue, ok := castedValue["a"].(float64); !ok || firstJsonValue != 1 {
53+
t.Fatalf("The first value of the json is not what it should be: %v", castedValue["a"])
4954
}
5055

51-
if jsonValue.ObjectContent["b"] != "secretValueJson" {
52-
t.Fatalf("The second value of the json is not what it should be: %v", jsonValue.ObjectContent["b"])
56+
if castedValue["b"] != "secretValueJson" {
57+
t.Fatalf("The second value of the json is not what it should be: %v", castedValue["b"])
5358
}
5459

5560
certificateValue, ok := resultToTest["fakeCertificate"]
@@ -121,7 +126,7 @@ func TestCollectFromEnvWhenDifferentPrefix(t *testing.T) {
121126
func TestBuildSecureExtension(t *testing.T) {
122127
parameters := map[string]ParameterValue{
123128
"password": {Type: typeString, StringContent: "secretValue"},
124-
"fakeJson": {Type: typeJSON, ObjectContent: map[string]interface{}{"secretParameterFirst": "secretValueOne", "secretParameterSecond": "secretValueTwo"}},
129+
"fakeJson": {Type: typeJSON, JSONContent: map[string]interface{}{"secretParameterFirst": "secretValueOne", "secretParameterSecond": "secretValueTwo"}},
125130
"fakeCertificate": {Type: typeMultiline, StringContent: "-----BEGIN CERTIFICATE-----\nMIBgNVBAYTAPXwBc63heW9WrP3qnDEm+UZE4V0Au7OWnOeiobq\n-----END CERTIFICATE-----\n"},
126131
}
127132

0 commit comments

Comments
 (0)