Skip to content

Commit

Permalink
Merge pull request #72 from platform-acceleration-lab/master
Browse files Browse the repository at this point in the history
Replace autopilot zero down time deploys with own implementation.
  • Loading branch information
vito authored Mar 29, 2019
2 parents 525b689 + 591c4ea commit 6b9ec74
Show file tree
Hide file tree
Showing 9 changed files with 425 additions and 28 deletions.
3 changes: 0 additions & 3 deletions dockerfiles/alpine/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ RUN apk add --no-cache curl jq
RUN mkdir -p /assets
WORKDIR /assets
RUN curl -L "https://packages.cloudfoundry.org/stable?release=linux64-binary&source=github" | tar -xzf -
RUN curl -L "https://github.com/contraband/autopilot/releases/download/0.0.8/autopilot-linux" -o /assets/autopilot
COPY . /go/src/github.com/concourse/cf-resource
ENV CGO_ENABLED 0
RUN go build -o /assets/in github.com/concourse/cf-resource/in/cmd/in
Expand All @@ -19,8 +18,6 @@ RUN apk add --no-cache bash tzdata ca-certificates
COPY --from=builder assets/ /opt/resource/
RUN chmod +x /opt/resource/*
RUN mv /opt/resource/cf /usr/bin/cf
RUN mv /opt/resource/autopilot /usr/bin/autopilot
RUN /usr/bin/cf install-plugin -f /usr/bin/autopilot

FROM resource AS tests
COPY --from=builder /tests /go-tests
Expand Down
5 changes: 5 additions & 0 deletions out/assets/erroringCf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

./$(dirname $0)/cf $*

exit 1
47 changes: 33 additions & 14 deletions out/cloud_foundry.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package out

import (
"fmt"
"github.com/concourse/cf-resource/out/zdt"
"os"
"os/exec"
)
Expand Down Expand Up @@ -44,25 +45,45 @@ func (cf *CloudFoundry) Target(organization string, space string) error {

func (cf *CloudFoundry) PushApp(
manifest string,
path string, currentAppName string,
path string,
currentAppName string,
vars map[string]interface{},
varsFiles []string,
dockerUser string,
showLogs bool,
noStart bool,
) error {
args := []string{}

if currentAppName == "" {
args = append(args, "push", "-f", manifest)
if noStart {
args = append(args, "--no-start")
if zdt.CanPush(cf.cf, currentAppName) {
pushFunction := func() error {
return cf.simplePush(manifest, path, currentAppName, vars, varsFiles, dockerUser, noStart)
}
return zdt.Push(cf.cf, currentAppName, pushFunction, showLogs)
} else {
args = append(args, "zero-downtime-push", currentAppName, "-f", manifest)
if showLogs {
args = append(args, "--show-app-log")
}
return cf.simplePush(manifest, path, currentAppName, vars, varsFiles, dockerUser, noStart)
}
}

func (cf *CloudFoundry) simplePush(
manifest string,
path string,
currentAppName string,
vars map[string]interface{},
varsFiles []string,
dockerUser string,
noStart bool,
) error {

args := []string{"push"}

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

args = append(args, "-f", manifest)

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

for name, value := range vars {
Expand All @@ -83,6 +104,7 @@ func (cf *CloudFoundry) PushApp(
return err
}
if stat.IsDir() {
args = append(args, "-p", ".")
return chdir(path, cf.cf(args...).Run)
}

Expand Down Expand Up @@ -114,10 +136,7 @@ func (cf *CloudFoundry) cf(args ...string) *exec.Cmd {
cmd.Env = append(os.Environ(), "CF_COLOR=true", "CF_DIAL_TIMEOUT=30")

if cf.verbose {
// we have to set CF_TRACE to direct output directly to stderr due to a known issue in the CF CLI
// when used together with cli plugins like cf autopilot (as used by cf-resource)
// see also https://github.com/cloudfoundry/cli/blob/master/README.md#known-issues
cmd.Env = append(cmd.Env, "CF_TRACE=/dev/stderr")
cmd.Env = append(cmd.Env, "CF_TRACE=true")
}

return cmd
Expand Down
31 changes: 20 additions & 11 deletions out/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,17 @@ var _ = Describe("Out", func() {
Expect(session.Err).To(gbytes.Say("cf api https://api.run.pivotal.io --skip-ssl-validation"))
Expect(session.Err).To(gbytes.Say("cf auth [email protected] hunter2"))
Expect(session.Err).To(gbytes.Say("cf target -o org -s space"))
Expect(session.Err).To(gbytes.Say("cf zero-downtime-push awesome-app -f %s",
Expect(session.Err).To(gbytes.Say("cf rename awesome-app awesome-app-venerable"))
Expect(session.Err).To(gbytes.Say("cf push awesome-app -f %s",
filepath.Join(tmpDir, "project/manifest.yml"),
))
Expect(session.Err).To(gbytes.Say(filepath.Join(tmpDir, "another-project")))
Expect(session.Err).To(gbytes.Say("cf delete -f awesome-app-venerable"))

// color should be always
output := string(session.Err.Contents())
Expect(strings.Count(output, "CF_COLOR=true")).To(Equal(4))
Expect(strings.Count(output, "CF_TRACE=/dev/stderr")).To(Equal(4))
Expect(strings.Count(output, "CF_COLOR=true")).To(Equal(7))
Expect(strings.Count(output, "CF_TRACE=true")).To(Equal(7))
})
})

Expand Down Expand Up @@ -150,15 +152,18 @@ var _ = Describe("Out", func() {
Expect(session.Err).To(gbytes.Say("cf api https://api.run.pivotal.io --skip-ssl-validation"))
Expect(session.Err).To(gbytes.Say("cf auth [email protected] hunter2"))
Expect(session.Err).To(gbytes.Say("cf target -o org -s space"))
Expect(session.Err).To(gbytes.Say("cf zero-downtime-push awesome-app -f %s --var foo=bar --vars-file vars.yml",

Expect(session.Err).To(gbytes.Say("cf rename awesome-app awesome-app-venerable"))
Expect(session.Err).To(gbytes.Say("cf push awesome-app -f %s --var foo=bar --vars-file vars.yml",
filepath.Join(tmpDir, "project/manifest.yml"),
))
Expect(session.Err).To(gbytes.Say(filepath.Join(tmpDir, "another-project")))
Expect(session.Err).To(gbytes.Say("cf delete -f awesome-app-venerable"))

// color should be always
output := string(session.Err.Contents())
Expect(strings.Count(output, "CF_COLOR=true")).To(Equal(4))
Expect(strings.Count(output, "CF_TRACE=/dev/stderr")).To(Equal(4))
Expect(strings.Count(output, "CF_COLOR=true")).To(Equal(7))
Expect(strings.Count(output, "CF_TRACE=true")).To(Equal(7))
})
})

Expand Down Expand Up @@ -199,10 +204,12 @@ var _ = Describe("Out", func() {
Expect(session.Err).To(gbytes.Say("cf api https://api.run.pivotal.io --skip-ssl-validation"))
Expect(session.Err).To(gbytes.Say("cf auth [email protected] hunter2"))
Expect(session.Err).To(gbytes.Say("cf target -o org -s space"))
Expect(session.Err).To(gbytes.Say("cf zero-downtime-push awesome-app -f %s -p %s",
Expect(session.Err).To(gbytes.Say("cf rename awesome-app awesome-app-venerable"))
Expect(session.Err).To(gbytes.Say("cf push awesome-app -f %s -p %s",
tmpFileManifest.Name(),
tmpFileSearch.Name(),
))
Expect(session.Err).To(gbytes.Say("cf delete -f awesome-app-venerable"))

// color should be always
Eventually(session.Err).Should(gbytes.Say("CF_COLOR=true"))
Expand Down Expand Up @@ -316,10 +323,12 @@ var _ = Describe("Out", func() {
Expect(session.Err).To(gbytes.Say("cf api https://api.run.pivotal.io --skip-ssl-validation"))
Expect(session.Err).To(gbytes.Say("cf auth [email protected] hunter2"))
Expect(session.Err).To(gbytes.Say("cf target -o org -s space"))
Expect(session.Err).To(gbytes.Say("cf zero-downtime-push awesome-app -f %s --docker-username %s",
Expect(session.Err).To(gbytes.Say("cf rename awesome-app awesome-app-venerable"))
Expect(session.Err).To(gbytes.Say("cf push awesome-app -f %s --docker-username %s",
filepath.Join(tmpDir, "project/manifest.yml"),
request.Params.DockerUsername,
))
Expect(session.Err).To(gbytes.Say("cf delete -f awesome-app-venerable"))
Expect(session.Err).To(gbytes.Say("CF_DOCKER_PASSWORD=DOCKER_PASSWORD"))
})
})
Expand Down Expand Up @@ -348,15 +357,15 @@ var _ = Describe("Out", func() {
Expect(session.Err).To(gbytes.Say("cf api https://api.run.pivotal.io --skip-ssl-validation"))
Expect(session.Err).To(gbytes.Say("cf auth [email protected] hunter2"))
Expect(session.Err).To(gbytes.Say("cf target -o org -s space"))
Expect(session.Err).To(gbytes.Say("cf push -f %s",
Expect(session.Err).To(gbytes.Say("cf push -f %s -p .",
filepath.Join(tmpDir, "project/manifest.yml"),
))
Expect(session.Err).To(gbytes.Say(filepath.Join(tmpDir, "another-project")))

// color should be always
output := string(session.Err.Contents())
Expect(strings.Count(output, "CF_COLOR=true")).To(Equal(4))
Expect(strings.Count(output, "CF_TRACE=/dev/stderr")).To(Equal(4))
Expect(strings.Count(output, "CF_TRACE=true")).To(Equal(4))
})
})
Context("when no_start is specified", func() {
Expand Down Expand Up @@ -391,7 +400,7 @@ var _ = Describe("Out", func() {
// color should be always
output := string(session.Err.Contents())
Expect(strings.Count(output, "CF_COLOR=true")).To(Equal(4))
Expect(strings.Count(output, "CF_TRACE=/dev/stderr")).To(Equal(4))
Expect(strings.Count(output, "CF_TRACE=true")).To(Equal(4))
})
})
})
Expand Down
55 changes: 55 additions & 0 deletions out/zdt/push.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package zdt

import (
"fmt"
"os/exec"
)

func CanPush(
cf func(args ...string) *exec.Cmd,
currentAppName string,
) bool {

if currentAppName == "" {
return false
}

findErr := cf("app", currentAppName).Run()
appExists := findErr == nil

return appExists
}

func Push(
cf func(args ...string) *exec.Cmd,
currentAppName string,
pushFunction func() error,
showLogs bool,
) error {

venerableAppName := fmt.Sprintf("%s-venerable", currentAppName)

actions := []Action{
{
Forward: cf("rename", currentAppName, venerableAppName).Run,
},
{
Forward: pushFunction,
ReversePrevious: func() error {
if showLogs {
_ = cf("logs", currentAppName, "--recent").Run()
}
_ = cf("delete", "-f", currentAppName).Run()
return cf("rename", venerableAppName, currentAppName).Run()
},
},
{
Forward: cf("delete", "-f", venerableAppName).Run,
},
}

return Actions{
Actions: actions,
RewindFailureMessage: "Oh no. Something's gone wrong. I've tried to roll back but you should check to see if everything is OK.",
}.Execute()
}
95 changes: 95 additions & 0 deletions out/zdt/push_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package zdt_test

import (
"errors"
"os/exec"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"

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

var stdout *gbytes.Buffer

func cli(path string) func(args ...string) *exec.Cmd {
return func(args ...string) *exec.Cmd {
cmd := exec.Command(path, args...)
cmd.Stdout = stdout
return cmd
}
}

var _ = Describe("CanPush", func() {
cf := cli("assets/cf")
errCf := cli("assets/erroringCf")

BeforeEach(func() {
stdout = gbytes.NewBuffer()
})

It("needs a currentAppName", func() {
Expect(zdt.CanPush(cf, "")).To(BeFalse())
Expect(stdout.Contents()).To(BeEmpty())
})

It("needs the app to exist", func() {
Expect(zdt.CanPush(errCf, "my-app")).To(BeFalse())
Expect(stdout).To(gbytes.Say("cf app my-app"))
})

It("is ok when app exists", func() {
Expect(zdt.CanPush(cf, "my-app")).To(BeTrue())
Expect(stdout).To(gbytes.Say("cf app my-app"))
})
})

var _ = Describe("Push", func() {
cf := cli("assets/cf")

BeforeEach(func() {
stdout = gbytes.NewBuffer()
})

It("pushes an app with zero downtime", func() {
pushFunction := func() error { return cf("push", "my-app").Run() }
err := zdt.Push(cf, "my-app", pushFunction, false)

Expect(err).NotTo(HaveOccurred())
Expect(stdout).To(gbytes.Say("cf rename my-app my-app-venerable"))
Expect(stdout).To(gbytes.Say("cf push my-app"))
Expect(stdout).To(gbytes.Say("cf delete -f my-app-venerable"))
})

It("rolls back on failed push", func() {
pushErr := errors.New("push failed")
pushFunction := func() error {
_ = cf("push", "my-app").Run()
return pushErr
}
err := zdt.Push(cf, "my-app", pushFunction, false)

Expect(err).To(Equal(pushErr))
Expect(stdout).To(gbytes.Say("cf rename my-app my-app-venerable"))
Expect(stdout).To(gbytes.Say("cf push my-app"))
Expect(stdout).ToNot(gbytes.Say("cf logs"))
Expect(stdout).To(gbytes.Say("cf delete -f my-app"))
Expect(stdout).To(gbytes.Say("cf rename my-app-venerable my-app"))
})

It("shows logs on failure when flag is set", func() {
pushFunction := func() error {
_ = cf("push", "my-app").Run()
return errors.New("push failed")
}
err := zdt.Push(cf, "my-app", pushFunction, true)

Expect(err).To(HaveOccurred())
Expect(stdout).To(gbytes.Say("cf rename my-app my-app-venerable"))
Expect(stdout).To(gbytes.Say("cf push my-app"))
Expect(stdout).To(gbytes.Say("cf logs my-app --recent"))
Expect(stdout).To(gbytes.Say("cf delete -f my-app"))
Expect(stdout).To(gbytes.Say("cf rename my-app-venerable my-app"))
})
})
38 changes: 38 additions & 0 deletions out/zdt/rewind.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package zdt

import "fmt"

type Actions struct {
Actions []Action

RewindFailureMessage string
}

func (actions Actions) Execute() error {
for _, action := range actions.Actions {
err := action.Forward()
if err != nil {
if action.ReversePrevious == nil {
return err
}

reverseError := action.ReversePrevious()
if reverseError != nil {
if actions.RewindFailureMessage != "" {
return fmt.Errorf("%s: %s", actions.RewindFailureMessage, reverseError)
} else {
return reverseError
}
}

return err
}
}

return nil
}

type Action struct {
Forward func() error
ReversePrevious func() error
}
Loading

0 comments on commit 6b9ec74

Please sign in to comment.