Skip to content

Commit cfb1338

Browse files
committed
Adding fixes after review
LMCROSSITXSADEPLOY-2301
1 parent f326e34 commit cfb1338

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{
@@ -150,7 +159,7 @@ func (c *DeployCommand) GetPluginCommand() plugin.Command {
150159
util.GetShortOption(taskExecutionTimeoutOpt): "Task execution timeout in seconds",
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",
153-
util.GetShortOption(requireSecureParameters): "Pass secrets to the deploy service in a secure way",
162+
util.GetShortOption(requireSecureParameters): "(EXPERIMENTAL) Pass secrets to the deploy service in a secure way",
154163
},
155164
},
156165
}
@@ -278,6 +287,7 @@ func (c *DeployCommand) executeInternal(positionalArgs []string, dsHost string,
278287
sequentialUpload := conf.GetUploadChunksSequentially()
279288
disableProgressBar := conf.GetDisableUploadProgressBar()
280289
fileUploader := NewFileUploader(mtaClient, namespace, uploadChunkSize, sequentialUpload, disableProgressBar)
290+
var yamlBytes []byte
281291

282292
if isUrl {
283293
var fileId string
@@ -328,6 +338,41 @@ func (c *DeployCommand) executeInternal(positionalArgs []string, dsHost string,
328338
return Failure
329339
}
330340

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

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

474486
func (c *DeployCommand) doesUpsExist(userProvidedServiceName string) (bool, error) {
475-
servicesOutput, err := c.cliConnection.CliCommandWithoutTerminalOutput("services")
476-
if err != nil {
477-
return false, fmt.Errorf("Error while checking if the UPS for secure encryption exists: %w", err)
487+
space, errSpace := c.cliConnection.GetCurrentSpace()
488+
if errSpace != nil {
489+
return false, fmt.Errorf("Cannot determine the current space")
478490
}
479-
stringTable := strings.Join(servicesOutput, "\n")
480-
return findServiceName(stringTable, userProvidedServiceName), nil
481-
}
491+
spaceGuid := space.Guid
482492

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

497504
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)