Skip to content
Open
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ be specified.

* `manifest`: *Required.* Path to a application manifest file.
* `path`: *Optional.* Path to the application to push. If this isn't set then
it will be read from the manifest instead.
it will be read from the manifest instead. When using rolling app deployments, this is required.
* `use_rolling_app_deployment`: *Optional.* Use CC's native rolling deployments feature to upgrade
the app without downtime. Default false.
* `current_app_name`: *Optional.* This should be the name of the application
that this will re-deploy over. If this is set the resource will perform a
zero-downtime deploy.
Expand Down
63 changes: 61 additions & 2 deletions out/cloud_foundry.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ package out

import (
"fmt"
"github.com/concourse/cf-resource/out/zdt"
"os"
"os/exec"

"github.com/concourse/cf-resource/out/zdt"
)

//go:generate counterfeiter . PAAS
type PAAS interface {
ApplyManifest(currentAppName string, manifest string) error
Login(api string, username string, password string, clientID string, clientSecret string, insecure bool) error
Target(organization string, space string) error
PushApp(manifest string, path string, currentAppName string, vars map[string]interface{}, varsFiles []string, dockerUser string, showLogs bool, noStart bool) error
PushAppWithRollingDeployment(path string, currentAppName string, dockerUser string, showLogs bool, noStart bool, manifest string) error
}

type CloudFoundry struct {
Expand Down Expand Up @@ -43,6 +46,22 @@ func (cf *CloudFoundry) Target(organization string, space string) error {
return cf.cf("target", "-o", organization, "-s", space).Run()
}

func (cf *CloudFoundry) PushAppWithRollingDeployment(
path string,
currentAppName string,
dockerUser string,
showLogs bool,
noStart bool,
manifest string,
) error {
cf.cf("version").Run()

if manifest != "" {
cf.ApplyManifest(currentAppName, manifest)
Copy link

@aegershman aegershman Jul 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this order of operations require the app to already exist at least once before a rolling update can be performed? Since it appears in order to do v3-apply-manifest <app-name> you have to already have the application in place?

E.g., if the app doesn't exist yet, should this do cf v3-zdt-push with --no-start, then v3-apply-manifest, and then start application?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this PR has sat long enough that a version of this resource could be built using the v7 cf cli. It has a better UX than the alpha v3- prefixed commands in the v6 CLI and is tolerant to the app not yet existing.

These docs demonstrate that workflow:
https://docs.cloudfoundry.org/devguide/deploy-apps/rolling-deploy.html

}
return cf.simpleRollingDeploymentPush(path, currentAppName, dockerUser, noStart)
}

func (cf *CloudFoundry) PushApp(
manifest string,
path string,
Expand All @@ -53,7 +72,6 @@ func (cf *CloudFoundry) PushApp(
showLogs bool,
noStart bool,
) error {

if zdt.CanPush(cf.cf, currentAppName) {
pushFunction := func() error {
return cf.simplePush(manifest, path, currentAppName, vars, varsFiles, dockerUser, noStart)
Expand Down Expand Up @@ -115,6 +133,47 @@ func (cf *CloudFoundry) simplePush(
return cf.cf(args...).Run()
}

func (cf *CloudFoundry) ApplyManifest(currentAppName, manifest string) error {
return cf.cf("v3-apply-manifest", currentAppName, "-f", manifest).Run()
}

func (cf *CloudFoundry) simpleRollingDeploymentPush(
path string,
currentAppName string,
dockerUser string,
noStart bool,
) error {
args := []string{"v3-zdt-push", "--wait-for-deploy-complete"}

if currentAppName != "" {
args = append(args, currentAppName)
}

if noStart {
args = append(args, "--no-start")
}

if dockerUser != "" {
args = append(args, "--docker-username", dockerUser)
}

if path != "" {
stat, err := os.Stat(path)
if err != nil {
return err
}
if stat.IsDir() {
args = append(args, "-p", ".")
return chdir(path, cf.cf(args...).Run)
}

// path is a zip file, add it to the args
args = append(args, "-p", path)
}

return cf.cf(args...).Run()
}

func chdir(path string, f func() error) error {
oldpath, err := os.Getwd()
if err != nil {
Expand Down
35 changes: 22 additions & 13 deletions out/command.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package out

import (
"time"

"os"
"time"

"github.com/concourse/cf-resource"
)
Expand Down Expand Up @@ -48,17 +47,27 @@ func (command *Command) Run(request Request) (Response, error) {
if request.Params.DockerPassword != "" {
os.Setenv(CfDockerPassword, request.Params.DockerPassword)
}

err = command.paas.PushApp(
request.Params.ManifestPath,
request.Params.Path,
request.Params.CurrentAppName,
request.Params.Vars,
request.Params.VarsFiles,
request.Params.DockerUsername,
request.Params.ShowAppLog,
request.Params.NoStart,
)
if request.Params.UseRollingAppDeployment {
err = command.paas.PushAppWithRollingDeployment(
request.Params.Path,
request.Params.CurrentAppName,
request.Params.DockerUsername,
request.Params.ShowAppLog,
request.Params.NoStart,
request.Params.ManifestPath,
)
} else {
err = command.paas.PushApp(
request.Params.ManifestPath,
request.Params.Path,
request.Params.CurrentAppName,
request.Params.Vars,
request.Params.VarsFiles,
request.Params.DockerUsername,
request.Params.ShowAppLog,
request.Params.NoStart,
)
}
if err != nil {
return Response{}, err
}
Expand Down
55 changes: 55 additions & 0 deletions out/command_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

package out_test

import (
Expand Down Expand Up @@ -44,6 +45,59 @@ var _ = Describe("Out Command", func() {
})

Describe("running the command", func() {
Context("when requesting rolling deployments", func() {
BeforeEach(func() {
request.Params.UseRollingAppDeployment = true
})
It("pushes an application using cf v3-zdt-push", func() {
response, err := command.Run(request)
Expect(err).NotTo(HaveOccurred())

Expect(response.Version.Timestamp).To(BeTemporally("~", time.Now(), time.Second))
Expect(response.Metadata[0]).To(Equal(
resource.MetadataPair{
Name: "organization",
Value: "secret",
},
))
Expect(response.Metadata[1]).To(Equal(
resource.MetadataPair{
Name: "space",
Value: "volcano-base",
},
))

By("logging in")
Expect(cloudFoundry.LoginCallCount()).To(Equal(1))

api, username, password, clientID, clientSecret, insecure := cloudFoundry.LoginArgsForCall(0)
Expect(api).To(Equal("https://api.run.pivotal.io"))
Expect(username).To(Equal("awesome@example.com"))
Expect(password).To(Equal("hunter2"))
Expect(clientID).To(Equal(""))
Expect(clientSecret).To(Equal(""))
Expect(insecure).To(Equal(false))

By("targeting the organization and space")
Expect(cloudFoundry.TargetCallCount()).To(Equal(1))

org, space := cloudFoundry.TargetArgsForCall(0)
Expect(org).To(Equal("secret"))
Expect(space).To(Equal("volcano-base"))

By("pushing the app")
Expect(cloudFoundry.PushAppCallCount()).To(Equal(0))
Expect(cloudFoundry.PushAppWithRollingDeploymentCallCount()).To(Equal(1))

path, currentAppName, dockerUser, showAppLog, noStart, manifest := cloudFoundry.PushAppWithRollingDeploymentArgsForCall(0)
Expect(path).To(Equal(""))
Expect(currentAppName).To(Equal(""))
Expect(dockerUser).To(Equal(""))
Expect(showAppLog).To(Equal(false))
Expect(noStart).To(Equal(false))
Expect(manifest).To(Equal("assets/manifest.yml"))
})
})
It("pushes an application into cloud foundry", func() {
response, err := command.Run(request)
Expect(err).NotTo(HaveOccurred())
Expand Down Expand Up @@ -82,6 +136,7 @@ var _ = Describe("Out Command", func() {

By("pushing the app")
Expect(cloudFoundry.PushAppCallCount()).To(Equal(1))
Expect(cloudFoundry.PushAppWithRollingDeploymentCallCount()).To(Equal(0))

manifest, path, currentAppName, vars, varsFiles, dockerUser, showAppLog, noStart := cloudFoundry.PushAppArgsForCall(0)
Expect(manifest).To(Equal(request.Params.ManifestPath))
Expand Down
2 changes: 2 additions & 0 deletions out/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ var _ = Describe("Out", func() {

Context("when my manifest and paths do not contain a glob", func() {
It("pushes an application to cloud foundry", func() {
fmt.Printf("command: %s\n", binPath)

session, err := gexec.Start(
cmd,
GinkgoWriter,
Expand Down
21 changes: 11 additions & 10 deletions out/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ type Request struct {
}

type Params struct {
ManifestPath string `json:"manifest"`
Path string `json:"path"`
CurrentAppName string `json:"current_app_name"`
Vars map[string]interface{} `json:"vars"`
VarsFiles []string `json:"vars_files"`
EnvironmentVariables map[string]string `json:"environment_variables"`
DockerUsername string `json:"docker_username"`
DockerPassword string `json:"docker_password"`
ShowAppLog bool `json:"show_app_log"`
NoStart bool `json:"no_start"`
ManifestPath string `json:"manifest"`
Path string `json:"path"`
CurrentAppName string `json:"current_app_name"`
Vars map[string]interface{} `json:"vars"`
VarsFiles []string `json:"vars_files"`
EnvironmentVariables map[string]string `json:"environment_variables"`
DockerUsername string `json:"docker_username"`
DockerPassword string `json:"docker_password"`
ShowAppLog bool `json:"show_app_log"`
NoStart bool `json:"no_start"`
UseRollingAppDeployment bool `json:"use_rolling_app_deployment"`
}

type Response struct {
Expand Down
Loading