Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve textual output #89

Merged
merged 4 commits into from
Sep 19, 2023
Merged
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
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
Loading