Skip to content

Commit

Permalink
Tentative fix for situations where CI has failed, and -venerable apps…
Browse files Browse the repository at this point in the history
… left dangling

May fix some of the edge cases identified in <contraband#37 (comment)>
  • Loading branch information
aeijdenberg committed Nov 2, 2017
1 parent bc1e055 commit ec7f076
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 75 deletions.
125 changes: 87 additions & 38 deletions autopilot.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,63 @@ func venerableAppName(appName string) string {
return fmt.Sprintf("%s-venerable", appName)
}

func getActionsForExistingApp(appRepo *ApplicationRepo, appName, manifestPath, appPath string, showLogs bool) []rewind.Action {
func getActionsForApp(appRepo *ApplicationRepo, appName, manifestPath, appPath string, showLogs bool) []rewind.Action {
venName := venerableAppName(appName)
var err error
var curApp, venApp *AppEntity
var haveVenToCleanup bool

return []rewind.Action{
// rename
// get info about current app
{
Forward: func() error {
curApp, err = appRepo.GetAppMetadata(appName)
if err != ErrAppNotFound {
return err
}
curApp = nil
return nil
},
},
// get info about ven app
{
Forward: func() error {
venApp, err = appRepo.GetAppMetadata(venName)
if err != ErrAppNotFound {
return err
}
venApp = nil
return nil
},
},
// rename any existing app such so that next step can push to a clear space
{
Forward: func() error {
return appRepo.RenameApplication(appName, venerableAppName(appName))
// Unless otherwise specified, go with our start state
haveVenToCleanup = (venApp != nil)

// If there is no current app running, that's great, we're done here
if curApp == nil {
return nil
}

// If current app isn't started, then we'll just delete it, and we're done
if curApp.State != "STARTED" {
return appRepo.DeleteApplication(appName)
}

// Do we have a ven app that will stop a rename?
if venApp != nil {
// Finally, since the current app claims to be healthy, we'll delete the venerable app, and rename the current over the top
err = appRepo.DeleteApplication(venName)
if err != nil {
return err
}
}

// Finally, rename
haveVenToCleanup = true
return appRepo.RenameApplication(appName, venName)
},
},
// push
Expand All @@ -49,17 +100,24 @@ func getActionsForExistingApp(appRepo *ApplicationRepo, appName, manifestPath, a
return appRepo.PushApplication(appName, manifestPath, appPath, showLogs)
},
ReversePrevious: func() error {
if !haveVenToCleanup {
return nil
}

// If the app cannot start we'll have a lingering application
// We delete this application so that the rename can succeed
appRepo.DeleteApplication(appName)

return appRepo.RenameApplication(venerableAppName(appName), appName)
return appRepo.RenameApplication(venName, appName)
},
},
// delete
{
Forward: func() error {
return appRepo.DeleteApplication(venerableAppName(appName))
if !haveVenToCleanup {
return nil
}
return appRepo.DeleteApplication(venName)
},
},
}
Expand All @@ -86,24 +144,10 @@ func (plugin AutopilotPlugin) Run(cliConnection plugin.CliConnection, args []str
appName, manifestPath, appPath, showLogs, err := ParseArgs(args)
fatalIf(err)

appExists, err := appRepo.DoesAppExist(appName)
fatalIf(err)

var actionList []rewind.Action

if appExists {
actionList = getActionsForExistingApp(appRepo, appName, manifestPath, appPath, showLogs)
} else {
actionList = getActionsForNewApp(appRepo, appName, manifestPath, appPath, showLogs)
}

actions := rewind.Actions{
Actions: actionList,
fatalIf((&rewind.Actions{
Actions: getActionsForApp(appRepo, appName, manifestPath, appPath, showLogs),
RewindFailureMessage: "Oh no. Something's gone wrong. I've tried to roll back but you should check to see if everything is OK.",
}

err = actions.Execute()
fatalIf(err)
}).Execute())

fmt.Println()
fmt.Println("A new version of your application has successfully been pushed!")
Expand Down Expand Up @@ -242,39 +286,44 @@ func (repo *ApplicationRepo) ListApplications() error {
return err
}

func (repo *ApplicationRepo) DoesAppExist(appName string) (bool, error) {
type AppEntity struct {
State string `json:"state"`
}

var (
ErrAppNotFound = errors.New("application not found")
)

// GetAppMetadata returns metadata about an app with appName
func (repo *ApplicationRepo) GetAppMetadata(appName string) (*AppEntity, error) {
space, err := repo.conn.GetCurrentSpace()
if err != nil {
return false, err
return nil, err
}

path := fmt.Sprintf(`v2/apps?q=name:%s&q=space_guid:%s`, url.QueryEscape(appName), space.Guid)
result, err := repo.conn.CliCommandWithoutTerminalOutput("curl", path)

if err != nil {
return false, err
return nil, err
}

jsonResp := strings.Join(result, "")

output := make(map[string]interface{})
output := struct {
Resources []struct {
Entity AppEntity `json:"entity"`
} `json:"resources"`
}{}
err = json.Unmarshal([]byte(jsonResp), &output)

if err != nil {
return false, err
return nil, err
}

totalResults, ok := output["total_results"]

if !ok {
return false, errors.New("Missing total_results from api response")
}

count, ok := totalResults.(float64)

if !ok {
return false, fmt.Errorf("total_results didn't have a number %v", totalResults)
if len(output.Resources) == 0 {
return nil, ErrAppNotFound
}

return count == 1, nil
return &output.Resources[0].Entity, nil
}
52 changes: 15 additions & 37 deletions autopilot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ var _ = Describe("ApplicationRepo", func() {
})
})

Describe("DoesAppExist", func() {
Describe("GetAppMetadata", func() {

It("returns an error if the cli returns an error", func() {
cliConn.CliCommandWithoutTerminalOutputReturns([]string{}, errors.New("you shall not curl"))
_, err := repo.DoesAppExist("app-name")
_, err := repo.GetAppMetadata("app-name")

Expect(err).To(MatchError("you shall not curl"))
})
Expand All @@ -93,36 +93,14 @@ var _ = Describe("ApplicationRepo", func() {
}

cliConn.CliCommandWithoutTerminalOutputReturns(response, nil)
_, err := repo.DoesAppExist("app-name")
_, err := repo.GetAppMetadata("app-name")

Expect(err).To(HaveOccurred())
})

It("returns an error if the cli response doesn't contain total_results", func() {
It("returns app data if the app exists", func() {
response := []string{
`{"brutal_results":2}`,
}

cliConn.CliCommandWithoutTerminalOutputReturns(response, nil)
_, err := repo.DoesAppExist("app-name")

Expect(err).To(MatchError("Missing total_results from api response"))
})

It("returns an error if the cli response contains a non-number total_results", func() {
response := []string{
`{"total_results":"sandwich"}`,
}

cliConn.CliCommandWithoutTerminalOutputReturns(response, nil)
_, err := repo.DoesAppExist("app-name")

Expect(err).To(MatchError("total_results didn't have a number sandwich"))
})

It("returns true if the app exists", func() {
response := []string{
`{"total_results":1}`,
`{"resources":[{"entity":{"state":"STARTED"}}]}`,
}
spaceGUID := "4"

Expand All @@ -136,19 +114,19 @@ var _ = Describe("ApplicationRepo", func() {
nil,
)

result, err := repo.DoesAppExist("app-name")
result, err := repo.GetAppMetadata("app-name")

Expect(cliConn.CliCommandWithoutTerminalOutputCallCount()).To(Equal(1))
args := cliConn.CliCommandWithoutTerminalOutputArgsForCall(0)
Expect(args).To(Equal([]string{"curl", "v2/apps?q=name:app-name&q=space_guid:4"}))

Expect(err).ToNot(HaveOccurred())
Expect(result).To(BeTrue())
Expect(result).ToNot(BeNil())
})

It("URL encodes the application name", func() {
response := []string{
`{"total_results":1}`,
`{"resources":[{"entity":{"state":"STARTED"}}]}`,
}
spaceGUID := "4"

Expand All @@ -162,26 +140,26 @@ var _ = Describe("ApplicationRepo", func() {
nil,
)

result, err := repo.DoesAppExist("app name")
result, err := repo.GetAppMetadata("app name")

Expect(cliConn.CliCommandWithoutTerminalOutputCallCount()).To(Equal(1))
args := cliConn.CliCommandWithoutTerminalOutputArgsForCall(0)
Expect(args).To(Equal([]string{"curl", "v2/apps?q=name:app+name&q=space_guid:4"}))

Expect(err).ToNot(HaveOccurred())
Expect(result).To(BeTrue())
Expect(result).ToNot(BeNil())
})

It("returns false if the app does not exist", func() {
It("returns nil if the app does not exist", func() {
response := []string{
`{"total_results":0}`,
`{"resources":[]}`,
}

cliConn.CliCommandWithoutTerminalOutputReturns(response, nil)
result, err := repo.DoesAppExist("app-name")
result, err := repo.GetAppMetadata("app-name")

Expect(err).ToNot(HaveOccurred())
Expect(result).To(BeFalse())
Expect(err).To(Equal(ErrAppNotFound))
Expect(result).To(BeNil())
})

})
Expand Down

0 comments on commit ec7f076

Please sign in to comment.