Skip to content

Commit

Permalink
feat: improve textual output (#89)
Browse files Browse the repository at this point in the history
* feat: improve textual output

[#185716266](https://www.pivotaltracker.com/story/show/185716266)

This commit doesn't add any of the required improvements.
It focuses on modifying function signatures to accommodate
the required improvements more cleanly in subsequent commits.

This commit alone is able to build but fails to run the tests
because autogenerated fakes got out of sync. I'll regenerate
these fakes in the following commit to simplify revision.

* feat: run go generate ./... and fix tests

[185716266](https://www.pivotaltracker.com/story/show/185716266)

* feat: improve output information

[#185716266](https://www.pivotaltracker.com/story/show/185716266)

* fix: use fullInstance helper to set upgradeAvailable: true
  • Loading branch information
fnaranjo-vmw committed Sep 19, 2023
1 parent 3b1b03a commit 6e48c57
Show file tree
Hide file tree
Showing 7 changed files with 339 additions and 136 deletions.
98 changes: 92 additions & 6 deletions internal/ccapi/service_instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,65 @@ type ServiceInstance struct {
Name string `json:"name"`
UpgradeAvailable bool `json:"upgrade_available"`
PlanGUID string `jsonry:"relationships.service_plan.data.guid"`
LastOperation struct {
Type string `json:"type"`
State string `json:"state"`
Description string `json:"description"`
} `json:"last_operation"`
SpaceGUID string `jsonry:"relationships.space.data.guid"`

LastOperation LastOperation `json:"last_operation"`

MaintenanceInfoVersion string `jsonry:"maintenance_info.version"`
Included EmbeddedInclude

// Can't be retrieves from CF API using `fields` query parameter
// We populate this field in Upgrade function in internal/upgrader/upgrader.go
PlanMaintenanceInfoVersion string
}

type LastOperation struct {
Type string `json:"type"`
State string `json:"state"`
Description string `json:"description"`
}

type serviceInstances struct {
Instances []ServiceInstance `json:"resources"`
Included struct {
Plans []IncludedPlan `json:"service_plans"`
Spaces []Space `json:"spaces"`
Organizations []Organization `json:"organizations"`
ServiceOfferings []ServiceOffering `json:"service_offerings"`
} `json:"included"`
}

type EmbeddedInclude struct {
Plan IncludedPlan
ServiceOffering ServiceOffering
Space Space
Organization Organization
}

type IncludedPlan struct {
GUID string `json:"guid"`
Name string `json:"name"`
ServiceOfferingGUID string `jsonry:"relationships.service_offering.data.guid"`
}

type Space struct {
GUID string `json:"guid"`
Name string `json:"name"`
OrganizationGUID string `jsonry:"relationships.organization.data.guid"`
}

type Organization struct {
GUID string `json:"guid"`
Name string `json:"name"`
}

type ServiceOffering struct {
GUID string `json:"guid"`
Name string `json:"name"`
}

func BuildQueryParams(planGUIDs []string) string {
return fmt.Sprintf("per_page=5000&fields[space]=name,guid,relationships.organization&fields[space.organization]=name,guid&fields[service_plan]=name,guid,relationships.service_offering&fields[service_plan.service_offering]=guid,name&service_plan_guids=%s", strings.Join(planGUIDs, ","))
}

func (c CCAPI) GetServiceInstances(planGUIDs []string) ([]ServiceInstance, error) {
Expand All @@ -29,12 +79,48 @@ func (c CCAPI) GetServiceInstances(planGUIDs []string) ([]ServiceInstance, error
}

var si serviceInstances
if err := c.requester.Get(fmt.Sprintf("v3/service_instances?per_page=5000&service_plan_guids=%s", strings.Join(planGUIDs, ",")), &si); err != nil {
if err := c.requester.Get("v3/service_instances?"+BuildQueryParams(planGUIDs), &si); err != nil {
return nil, fmt.Errorf("error getting service instances: %s", err)
}
embedIncludes(si)
return si.Instances, nil
}

func (s *ServiceInstance) UnmarshalJSON(b []byte) error {
return jsonry.Unmarshal(b, s)
}

func (s *serviceInstances) UnmarshalJSON(b []byte) error {
return jsonry.Unmarshal(b, s)
}

func embedIncludes(si serviceInstances) []ServiceInstance {
orgs := make(map[string]Organization, len(si.Included.Organizations))
for _, org := range si.Included.Organizations {
orgs[org.GUID] = org
}
spaces := make(map[string]Space, len(si.Included.Spaces))
for _, space := range si.Included.Spaces {
spaces[space.GUID] = space
}
soffers := make(map[string]ServiceOffering, len(si.Included.ServiceOfferings))
for _, soffer := range si.Included.ServiceOfferings {
soffers[soffer.GUID] = soffer
}
plans := make(map[string]IncludedPlan, len(si.Included.Plans))
for _, plan := range si.Included.Plans {
plans[plan.GUID] = plan
}

for i, instance := range si.Instances {
emb := EmbeddedInclude{}
emb.Plan = plans[instance.PlanGUID]
emb.Space = spaces[instance.SpaceGUID]
emb.ServiceOffering = soffers[plans[instance.PlanGUID].ServiceOfferingGUID]
emb.Organization = orgs[spaces[instance.SpaceGUID].OrganizationGUID]

si.Instances[i].Included = emb
}

return si.Instances
}
4 changes: 2 additions & 2 deletions internal/ccapi/service_instances_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ var _ = Describe("GetServiceInstances", func() {
fakeServer.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyHeaderKV("Authorization", "fake-token"),
ghttp.VerifyRequest("GET", "/v3/service_instances", "per_page=5000&service_plan_guids=test-plan-guid,another-test-guid"),
ghttp.VerifyRequest("GET", "/v3/service_instances", ccapi.BuildQueryParams([]string{"test-plan-guid", "another-test-guid"})),
ghttp.RespondWith(http.StatusOK, responseServiceInstances),
),
)
Expand All @@ -81,7 +81,7 @@ var _ = Describe("GetServiceInstances", func() {
By("making the appending the plan guids")
Expect(requests[0].Method).To(Equal("GET"))
Expect(requests[0].URL.Path).To(Equal("/v3/service_instances"))
Expect(requests[0].URL.RawQuery).To(Equal("per_page=5000&service_plan_guids=test-plan-guid,another-test-guid"))
Expect(requests[0].URL.RawQuery).To(Equal("per_page=5000&fields[space]=name,guid,relationships.organization&fields[space.organization]=name,guid&fields[service_plan]=name,guid,relationships.service_offering&fields[service_plan.service_offering]=guid,name&service_plan_guids=test-plan-guid,another-test-guid"))
})
})

Expand Down
75 changes: 60 additions & 15 deletions internal/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"sync"
"text/tabwriter"
"time"

"upgrade-all-services-cli-plugin/internal/ccapi"
)

func New(period time.Duration) *Logger {
Expand All @@ -24,9 +26,8 @@ func New(period time.Duration) *Logger {
}

type failure struct {
name string
guid string
err error
instance ccapi.ServiceInstance
err error
}

type Logger struct {
Expand All @@ -46,41 +47,40 @@ func (l *Logger) Printf(format string, a ...any) {
l.printf(format, a...)
}

func (l *Logger) SkippingInstance(name, guid string, upgradeAvailable bool, lastOperationType, lastOperationState string) {
func (l *Logger) SkippingInstance(instance ccapi.ServiceInstance) {
l.lock.Lock()
defer l.lock.Unlock()

l.skipped++
l.printf("skipping instance: %q guid: %q Upgrade Available: %v Last Operation: Type: %q State: %q", name, guid, upgradeAvailable, lastOperationType, lastOperationState)
l.printf("skipping instance: %q guid: %q Upgrade Available: %v Last Operation: Type: %q State: %q", instance.Name, instance.GUID, instance.UpgradeAvailable, instance.LastOperation.Type, instance.LastOperation.State)
}

func (l *Logger) UpgradeStarting(name, guid string) {
func (l *Logger) UpgradeStarting(instance ccapi.ServiceInstance) {
l.lock.Lock()
defer l.lock.Unlock()

l.printf("starting to upgrade instance: %q guid: %q", name, guid)
l.printf("starting to upgrade instance: %q guid: %q", instance.Name, instance.GUID)
}

func (l *Logger) UpgradeSucceeded(name, guid string, duration time.Duration) {
func (l *Logger) UpgradeSucceeded(instance ccapi.ServiceInstance, duration time.Duration) {
l.lock.Lock()
defer l.lock.Unlock()

l.successes++
l.complete++
l.printf("finished upgrade of instance: %q guid: %q successfully after %s", name, guid, duration)
l.printf("finished upgrade of instance: %q guid: %q successfully after %s", instance.Name, instance.GUID, duration)
}

func (l *Logger) UpgradeFailed(name, guid string, duration time.Duration, err error) {
func (l *Logger) UpgradeFailed(instance ccapi.ServiceInstance, duration time.Duration, err error) {
l.lock.Lock()
defer l.lock.Unlock()

l.failures = append(l.failures, failure{
name: name,
guid: guid,
err: err,
instance: instance,
err: err,
})
l.complete++
l.printf("upgrade of instance: %q guid: %q failed after %s: %s", name, guid, duration, err)
l.printf("upgrade of instance: %q guid: %q failed after %s: %s", instance.Name, instance.GUID, duration, err)
}

func (l *Logger) InitialTotals(totalServiceInstances, totalUpgradableServiceInstances int) {
Expand All @@ -105,6 +105,11 @@ func (l *Logger) FinalTotals() {
l.printf("skipped %d instances", l.skipped)
l.printf("successfully upgraded %d instances", l.successes)

logRowFormatTotals(l)
}

//lint:ignore U1000 Ignore unused function temporarily for better code review
func logOldFormatTotals(l *Logger) {
if len(l.failures) > 0 {
l.printf("failed to upgrade %d instances", len(l.failures))
l.printf("")
Expand All @@ -115,7 +120,7 @@ func (l *Logger) FinalTotals() {
fmt.Fprintln(tw, "---------------------\t---------------------\t -------")

for _, failure := range l.failures {
fmt.Fprintf(tw, "%s\t %s\t %s\n", failure.name, failure.guid, failure.err)
fmt.Fprintf(tw, "%s\t %s\t %s\n", failure.instance.Name, failure.instance.GUID, failure.err)
}
tw.Flush()

Expand All @@ -125,6 +130,46 @@ func (l *Logger) FinalTotals() {
}
}

func logRowFormatTotals(l *Logger) {
if len(l.failures) > 0 {
l.printf("failed to upgrade %d instances", len(l.failures))
l.printf("")
for _, failure := range l.failures {
fmt.Printf(`
Service Instance Name: %q
Service Instance GUID: %q
Service Version: %q
Details: %q
Org Name: %q
Org GUID: %q
Space Name: %q
Space GUID: %q
Plan Name: %q
Plan GUID: %q
Plan Version: %q
Service Offering Name: %q
Service Offering GUID: %q
`,
failure.instance.Name,
failure.instance.GUID,
failure.instance.MaintenanceInfoVersion,

string(failure.err.Error()),
failure.instance.Included.Organization.Name,
failure.instance.Included.Organization.GUID,
failure.instance.Included.Space.Name,
failure.instance.Included.Space.GUID,
failure.instance.Included.Plan.Name,
failure.instance.Included.Plan.GUID,
failure.instance.PlanMaintenanceInfoVersion,
failure.instance.Included.ServiceOffering.Name,
failure.instance.Included.ServiceOffering.GUID,
)
}
}
}

func (l *Logger) Cleanup() {
l.ticker.Stop()
}
Expand Down
Loading

0 comments on commit 6e48c57

Please sign in to comment.