Skip to content

Commit

Permalink
Add force-latest-variables flag to apply-changes command
Browse files Browse the repository at this point in the history
OpsManager now supports a flag to the apply changes endpoint that forces
BOSH to use the latest version of all variables.  This is an escape
hatch for the feature of automatically rotating certain certificates
each time a new stemcell is deployed.

[#186967065]
  • Loading branch information
selzoc authored and wayneadams committed Apr 11, 2024
1 parent 36320d4 commit e8d7e78
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 65 deletions.
5 changes: 4 additions & 1 deletion acceptance/apply_changes_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package acceptance

import (
"github.com/onsi/gomega/ghttp"
"net/http"
"os/exec"

"github.com/onsi/gomega/ghttp"

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

Expand Down Expand Up @@ -49,6 +50,7 @@ var _ = Describe("apply-changes command", func() {
ghttp.VerifyRequest("POST", "/api/v0/installations"),
ghttp.VerifyJSON(`{
"ignore_warnings": "false",
"force_latest_variables": false,
"deploy_products": "all"
}`),
ghttp.RespondWith(http.StatusOK, `{"install": {"id": 42}}`),
Expand Down Expand Up @@ -130,6 +132,7 @@ var _ = Describe("apply-changes command", func() {
ghttp.VerifyRequest("POST", "/api/v0/installations"),
ghttp.VerifyJSON(`{
"ignore_warnings": "false",
"force_latest_variables": false,
"deploy_products": "all"
}`),
ghttp.RespondWith(http.StatusOK, `{"install": {"id": 42}}`),
Expand Down
16 changes: 9 additions & 7 deletions api/installations_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func (a Api) ListInstallations() ([]InstallationsServiceOutput, error) {
return responseStruct.Installations, nil
}

func (a Api) CreateInstallation(ignoreWarnings bool, deployProducts bool, productNames []string, errands ApplyErrandChanges) (InstallationsServiceOutput, error) {
func (a Api) CreateInstallation(ignoreWarnings bool, deployProducts bool, forceLatestVariables bool, productNames []string, errands ApplyErrandChanges) (InstallationsServiceOutput, error) {
productGuidMapping, err := a.fetchProductGUID()
if err != nil {
return InstallationsServiceOutput{}, fmt.Errorf("failed to list staged and/or deployed products: %w", err)
Expand Down Expand Up @@ -110,13 +110,15 @@ func (a Api) CreateInstallation(ignoreWarnings bool, deployProducts bool, produc
}

data, err := json.Marshal(&struct {
IgnoreWarnings string `json:"ignore_warnings"`
DeployProducts interface{} `json:"deploy_products"`
Errands map[string]ProductErrand `json:"errands,omitempty"`
IgnoreWarnings string `json:"ignore_warnings"`
ForceLatestVariables bool `json:"force_latest_variables"`
DeployProducts interface{} `json:"deploy_products"`
Errands map[string]ProductErrand `json:"errands,omitempty"`
}{
IgnoreWarnings: fmt.Sprintf("%t", ignoreWarnings),
DeployProducts: deployProductsVal,
Errands: errandsPayload,
IgnoreWarnings: fmt.Sprintf("%t", ignoreWarnings),
ForceLatestVariables: forceLatestVariables,
DeployProducts: deployProductsVal,
Errands: errandsPayload,
})
if err != nil {
return InstallationsServiceOutput{}, err
Expand Down
62 changes: 36 additions & 26 deletions api/installations_service_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package api_test

import (
"github.com/onsi/gomega/gbytes"
"github.com/onsi/gomega/ghttp"
"log"
"net/http"
"time"

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

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

"github.com/pivotal-cf/om/api"
)

Expand Down Expand Up @@ -108,12 +110,12 @@ var _ = Describe("InstallationsService", func() {
),
ghttp.CombineHandlers(
ghttp.VerifyRequest("POST", "/api/v0/installations"),
ghttp.VerifyJSON(`{"ignore_warnings":"false", "deploy_products":"all"}`),
ghttp.VerifyJSON(`{"ignore_warnings":"false","force_latest_variables":false, "deploy_products":"all"}`),
ghttp.RespondWith(http.StatusOK, `{"install": {"id":1}}`),
),
)

output, err := service.CreateInstallation(false, true, nil, api.ApplyErrandChanges{})
output, err := service.CreateInstallation(false, true, false, nil, api.ApplyErrandChanges{})

Expect(err).ToNot(HaveOccurred())
Expect(output.ID).To(Equal(1))
Expand All @@ -133,12 +135,12 @@ var _ = Describe("InstallationsService", func() {
),
ghttp.CombineHandlers(
ghttp.VerifyRequest("POST", "/api/v0/installations"),
ghttp.VerifyJSON(`{"ignore_warnings":"false", "deploy_products":"none"}`),
ghttp.VerifyJSON(`{"ignore_warnings":"false","force_latest_variables":false, "deploy_products":"none"}`),
ghttp.RespondWith(http.StatusOK, `{"install": {"id":1}}`),
),
)

output, err := service.CreateInstallation(false, false, nil, api.ApplyErrandChanges{})
output, err := service.CreateInstallation(false, false, false, nil, api.ApplyErrandChanges{})

Expect(err).ToNot(HaveOccurred())
Expect(output.ID).To(Equal(1))
Expand All @@ -158,30 +160,38 @@ var _ = Describe("InstallationsService", func() {
),
ghttp.CombineHandlers(
ghttp.VerifyRequest("POST", "/api/v0/installations"),
ghttp.VerifyJSON(`{"ignore_warnings":"false","deploy_products":["guid2"]}`),
ghttp.VerifyJSON(`{"ignore_warnings":"false","force_latest_variables":false,"deploy_products":["guid2"]}`),
ghttp.RespondWith(http.StatusOK, `{"install": {"id":1}}`),
),
)

output, err := service.CreateInstallation(false, true, []string{"product2"}, api.ApplyErrandChanges{})
output, err := service.CreateInstallation(false, true, false, []string{"product2"}, api.ApplyErrandChanges{})
Expect(err).ToNot(HaveOccurred())
Expect(output.ID).To(Equal(1))
})
})

It("errors when the product does not exist", func() {
When("forcing latest variables", func() {
It("triggers an installation on an Ops Manager, forcing latest variables", func() {
client.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/api/v0/staged/products"),
ghttp.RespondWith(http.StatusOK, `[{"guid": "guid1", "type": "product1"}]`),
ghttp.RespondWith(http.StatusOK, `[{"guid": "guid1", "type": "product1"}, {"guid": "guid2", "type": "product2"}]`),
),
ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/api/v0/deployed/products"),
ghttp.RespondWith(http.StatusOK, `[{"guid": "guid1", "type": "product1"}]`),
ghttp.RespondWith(http.StatusOK, `[{"guid": "guid1", "type": "product1"}, {"guid": "guid2", "type": "product2"}]`),
),
ghttp.CombineHandlers(
ghttp.VerifyRequest("POST", "/api/v0/installations"),
ghttp.VerifyJSON(`{"ignore_warnings":"false","force_latest_variables":true,"deploy_products":["guid2"]}`),
ghttp.RespondWith(http.StatusOK, `{"install": {"id":1}}`),
),
)

_, err := service.CreateInstallation(false, true, []string{"product2"}, api.ApplyErrandChanges{})
Expect(err).To(HaveOccurred())
output, err := service.CreateInstallation(false, true, true, []string{"product2"}, api.ApplyErrandChanges{})
Expect(err).ToNot(HaveOccurred())
Expect(output.ID).To(Equal(1))
})
})

Expand All @@ -199,12 +209,12 @@ var _ = Describe("InstallationsService", func() {
),
ghttp.CombineHandlers(
ghttp.VerifyRequest("POST", "/api/v0/installations"),
ghttp.VerifyJSON(`{"ignore_warnings": "false", "deploy_products": ["guid1"], "errands": {"guid1": {"run_post_deploy": {"errand1": "default"}}}}`),
ghttp.VerifyJSON(`{"ignore_warnings": "false", "force_latest_variables": false, "deploy_products": ["guid1"], "errands": {"guid1": {"run_post_deploy": {"errand1": "default"}}}}`),
ghttp.RespondWith(http.StatusOK, `{"install": {"id":1}}`),
),
)

output, err := service.CreateInstallation(false, true, []string{"product1"}, api.ApplyErrandChanges{
output, err := service.CreateInstallation(false, true, false, []string{"product1"}, api.ApplyErrandChanges{
Errands: map[string]api.ProductErrand{
"product1": {
RunPostDeploy: map[string]interface{}{
Expand All @@ -229,12 +239,12 @@ var _ = Describe("InstallationsService", func() {
),
ghttp.CombineHandlers(
ghttp.VerifyRequest("POST", "/api/v0/installations"),
ghttp.VerifyJSON(`{"ignore_warnings": "false", "deploy_products": ["guid2"]}`),
ghttp.VerifyJSON(`{"ignore_warnings": "false", "force_latest_variables": false, "deploy_products": ["guid2"]}`),
ghttp.RespondWith(http.StatusOK, `{"install": {"id":1}}`),
),
)

_, err := service.CreateInstallation(false, true, []string{"product2"}, api.ApplyErrandChanges{
_, err := service.CreateInstallation(false, true, false, []string{"product2"}, api.ApplyErrandChanges{
Errands: map[string]api.ProductErrand{
"product3": {
RunPostDeploy: map[string]interface{}{
Expand All @@ -261,12 +271,12 @@ var _ = Describe("InstallationsService", func() {
),
ghttp.CombineHandlers(
ghttp.VerifyRequest("POST", "/api/v0/installations"),
ghttp.VerifyJSON(`{"ignore_warnings": "false", "deploy_products": "all", "errands": {"guid1": {"run_post_deploy": {"errand1": "default"}}}}`),
ghttp.VerifyJSON(`{"ignore_warnings": "false", "force_latest_variables": false, "deploy_products": "all", "errands": {"guid1": {"run_post_deploy": {"errand1": "default"}}}}`),
ghttp.RespondWith(http.StatusOK, `{"install": {"id":1}}`),
),
)

output, err := service.CreateInstallation(false, true, []string{}, api.ApplyErrandChanges{
output, err := service.CreateInstallation(false, true, false, []string{}, api.ApplyErrandChanges{
Errands: map[string]api.ProductErrand{
"product1": {
RunPostDeploy: map[string]interface{}{
Expand All @@ -291,12 +301,12 @@ var _ = Describe("InstallationsService", func() {
),
ghttp.CombineHandlers(
ghttp.VerifyRequest("POST", "/api/v0/installations"),
ghttp.VerifyJSON(`{"ignore_warnings": "false", "deploy_products": ["guid2"], "errands": {"guid1": {"run_post_deploy": {"errand1": "default"}}}}`),
ghttp.VerifyJSON(`{"ignore_warnings": "false", "force_latest_variables": false, "deploy_products": ["guid2"], "errands": {"guid1": {"run_post_deploy": {"errand1": "default"}}}}`),
ghttp.RespondWith(http.StatusOK, `{"install": {"id":1}}`),
),
)

_, err := service.CreateInstallation(false, true, []string{}, api.ApplyErrandChanges{
_, err := service.CreateInstallation(false, true, false, []string{}, api.ApplyErrandChanges{
Errands: map[string]api.ProductErrand{
"product1": {
RunPostDeploy: map[string]interface{}{
Expand Down Expand Up @@ -330,7 +340,7 @@ var _ = Describe("InstallationsService", func() {
),
)

_, err := service.CreateInstallation(false, true, nil, api.ApplyErrandChanges{})
_, err := service.CreateInstallation(false, true, false, nil, api.ApplyErrandChanges{})
Expect(err).To(MatchError(ContainSubstring("could not make api request to installations endpoint: could not send api request to POST /api/v0/installations")))
})
})
Expand All @@ -348,7 +358,7 @@ var _ = Describe("InstallationsService", func() {
),
ghttp.CombineHandlers(
ghttp.VerifyRequest("POST", "/api/v0/installations"),
ghttp.VerifyJSON(`{"ignore_warnings":"false","deploy_products":["guid2"]}`),
ghttp.VerifyJSON(`{"ignore_warnings":"false","force_latest_variables":false,"deploy_products":["guid2"]}`),
ghttp.RespondWith(http.StatusUnprocessableEntity, `{
"errors": ["'Some IAAS Error', type: SomeVerifier""],
"deployment_errors": {
Expand All @@ -365,7 +375,7 @@ var _ = Describe("InstallationsService", func() {
),
)

_, err := service.CreateInstallation(false, true, []string{"product2"}, api.ApplyErrandChanges{})
_, err := service.CreateInstallation(false, true, false, []string{"product2"}, api.ApplyErrandChanges{})
Expect(err).To(MatchError(ContainSubstring("request failed: unexpected response")))
Expect(err).To(MatchError(ContainSubstring("Tip: In Ops Manager 2.6 or newer, you can use `om pre-deploy-check` to get a complete list of failed verifiers and om commands to disable them.")))
})
Expand All @@ -380,7 +390,7 @@ var _ = Describe("InstallationsService", func() {
),
)

_, err := service.CreateInstallation(false, true, nil, api.ApplyErrandChanges{})
_, err := service.CreateInstallation(false, true, false, nil, api.ApplyErrandChanges{})
Expect(err).To(MatchError(ContainSubstring("request failed: unexpected response")))
})
})
Expand All @@ -402,7 +412,7 @@ var _ = Describe("InstallationsService", func() {
),
)

_, err := service.CreateInstallation(false, true, nil, api.ApplyErrandChanges{})
_, err := service.CreateInstallation(false, true, false, nil, api.ApplyErrandChanges{})
Expect(err).To(MatchError(ContainSubstring("failed to decode response: invalid character")))
})
})
Expand Down
17 changes: 9 additions & 8 deletions commands/apply_changes.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,19 @@ type ApplyChanges struct {
logWriter logWriter
waitDuration time.Duration
Options struct {
Config string `short:"c" long:"config" description:"path to yml file containing errand configuration (see docs/apply-changes/README.md for format)"`
IgnoreWarnings bool `short:"i" long:"ignore-warnings" description:"For convenience. Use other commands to disable particular verifiers if they are inappropriate."`
Reattach bool `long:"reattach" description:"reattach to an already running apply changes (if available)"`
RecreateVMs bool `long:"recreate-vms" description:"recreate all vms"`
SkipDeployProducts bool `short:"s" long:"skip-deploy-products" description:"skip deploying products when applying changes - just update the director"`
ProductNames []string `short:"n" long:"product-name" description:"name of the product(s) to deploy, cannot be used in conjunction with --skip-deploy-products (OM 2.2+)"`
Config string `short:"c" long:"config" description:"path to yml file containing errand configuration (see docs/apply-changes/README.md for format)"`
IgnoreWarnings bool `short:"i" long:"ignore-warnings" description:"For convenience. Use other commands to disable particular verifiers if they are inappropriate."`
Reattach bool `long:"reattach" description:"reattach to an already running apply changes (if available)"`
RecreateVMs bool `long:"recreate-vms" description:"recreate all vms"`
SkipDeployProducts bool `short:"s" long:"skip-deploy-products" description:"skip deploying products when applying changes - just update the director"`
ForceLatestVariables bool `long:"force-latest-variables" description:"force any certificates or other BOSH variables to use their latest version even when a stemcell is not being upgraded"`
ProductNames []string `short:"n" long:"product-name" description:"name of the product(s) to deploy, cannot be used in conjunction with --skip-deploy-products (OM 2.2+)"`
}
}

//counterfeiter:generate -o ./fakes/apply_changes_service.go --fake-name ApplyChangesService . applyChangesService
type applyChangesService interface {
CreateInstallation(bool, bool, []string, api.ApplyErrandChanges) (api.InstallationsServiceOutput, error)
CreateInstallation(bool, bool, bool, []string, api.ApplyErrandChanges) (api.InstallationsServiceOutput, error)
GetInstallation(id int) (api.InstallationsServiceOutput, error)
GetInstallationLogs(id int) (api.InstallationsServiceOutput, error)
Info() (api.Info, error)
Expand Down Expand Up @@ -158,7 +159,7 @@ func (ac ApplyChanges) Execute(args []string) error {
}

ac.logger.Printf("attempting to apply changes to the targeted Ops Manager")
installation, err = ac.service.CreateInstallation(ac.Options.IgnoreWarnings, !ac.Options.SkipDeployProducts, changedProducts, errands)
installation, err = ac.service.CreateInstallation(ac.Options.IgnoreWarnings, !ac.Options.SkipDeployProducts, ac.Options.ForceLatestVariables, changedProducts, errands)
if err != nil {
return fmt.Errorf("installation failed to trigger: %s", err)
}
Expand Down
31 changes: 24 additions & 7 deletions commands/apply_changes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package commands_test
import (
"errors"
"fmt"
"github.com/onsi/gomega/gbytes"
"gopkg.in/yaml.v2"
"io/ioutil"
"log"
"os"
"regexp"
"time"

"github.com/onsi/gomega/gbytes"
"gopkg.in/yaml.v2"

"github.com/pivotal-cf/om/api"
"github.com/pivotal-cf/om/commands"
"github.com/pivotal-cf/om/commands/fakes"
Expand Down Expand Up @@ -60,9 +61,10 @@ var _ = Describe("ApplyChanges", func() {

Expect(service.CreateInstallationCallCount()).To(Equal(1))

ignoreWarnings, deployProducts, _, _ := service.CreateInstallationArgsForCall(0)
ignoreWarnings, deployProducts, forceLatestVariables, _, _ := service.CreateInstallationArgsForCall(0)
Expect(ignoreWarnings).To(Equal(false))
Expect(deployProducts).To(Equal(true))
Expect(forceLatestVariables).To(Equal(false))

Expect(stderr).To(gbytes.Say("attempting to apply changes to the targeted Ops Manager"))

Expand Down Expand Up @@ -116,19 +118,33 @@ var _ = Describe("ApplyChanges", func() {
err := executeCommand(command, []string{"--ignore-warnings"})
Expect(err).ToNot(HaveOccurred())

ignoreWarnings, _, _, _ := service.CreateInstallationArgsForCall(0)
ignoreWarnings, _, _, _, _ := service.CreateInstallationArgsForCall(0)
Expect(ignoreWarnings).To(Equal(true))
})
})

When("passed the force-latest-variables flag", func() {
It("applies changes while forcing the latest variable versions to be used", func() {
service.InfoReturns(api.Info{Version: "2.3-build43"}, nil)

command := commands.NewApplyChanges(service, pendingService, writer, logger, 1)

err := executeCommand(command, []string{"--force-latest-variables"})
Expect(err).ToNot(HaveOccurred())

_, _, forceLatestVariables, _, _ := service.CreateInstallationArgsForCall(0)
Expect(forceLatestVariables).To(Equal(true))
})
})

When("passed the skip-deploy-products flag", func() {
It("applies changes while not deploying products", func() {
command := commands.NewApplyChanges(service, pendingService, writer, logger, 1)

err := executeCommand(command, []string{"--skip-deploy-products"})
Expect(err).ToNot(HaveOccurred())

_, deployProducts, _, _ := service.CreateInstallationArgsForCall(0)
_, _, deployProducts, _, _ := service.CreateInstallationArgsForCall(0)
Expect(deployProducts).To(Equal(false))
})

Expand All @@ -149,7 +165,7 @@ var _ = Describe("ApplyChanges", func() {
err := executeCommand(command, []string{"--product-name", "product1", "--product-name", "product2"})
Expect(err).To(HaveOccurred())

_, _, productNames, _ := service.CreateInstallationArgsForCall(0)
_, _, _, productNames, _ := service.CreateInstallationArgsForCall(0)
Expect(productNames).To(ConsistOf("product1", "product2"))
})
})
Expand Down Expand Up @@ -382,9 +398,10 @@ errands:

Expect(service.CreateInstallationCallCount()).To(Equal(1))

ignoreWarnings, deployProducts, _, errands := service.CreateInstallationArgsForCall(0)
ignoreWarnings, deployProducts, forceLatestVariables, _, errands := service.CreateInstallationArgsForCall(0)
Expect(ignoreWarnings).To(Equal(false))
Expect(deployProducts).To(Equal(true))
Expect(forceLatestVariables).To(Equal(false))
Expect(errands).To(Equal(api.ApplyErrandChanges{
Errands: map[string]api.ProductErrand{
"product1_name": {
Expand Down
Loading

0 comments on commit e8d7e78

Please sign in to comment.