diff --git a/CHANGELOG.md b/CHANGELOG.md index fb81c010..83f33943 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# v1.2.0 (2018-11-05) + +New features: +- Users can manually overwrite the OpenStack environment variables by providing them as command-line flags. + +Changes: +- For the `--cluster` flag, the domain/project must be identified by ID. Specifiying a domain/project name will not work. + +Bugfixes: +- `--cluster` flag now works as expected. + + # v1.1.0 (2018-10-29) New features: diff --git a/README.md b/README.md index 92c4ef64..8b11c4fa 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/sapcc/limesctl)](https://goreportcard.com/report/github.com/sapcc/limesctl) [![GoDoc](https://godoc.org/github.com/sapcc/limesctl?status.svg)](https://godoc.org/github.com/sapcc/limesctl) -limesctl is the CLI client for [Limes](limes). +limesctl is the CLI client for [Limes][limes]. ## Installation diff --git a/main.go b/main.go index d687d43b..e3973529 100644 --- a/main.go +++ b/main.go @@ -36,10 +36,20 @@ var ( clusterCmd = app.Command("cluster", "Do some action on cluster(s).") domainCmd = app.Command("domain", "Do some action on domain(s).") - domainCluster = domainCmd.Flag("cluster", "Cluster ID.").Short('c').String() + domainCluster = domainCmd.Flag("cluster", "Cluster ID. When this option is given, the domain must be identified by ID. Specifiying a domain name will not work.").Short('c').String() projectCmd = app.Command("project", "Do some action on project(s).") - projectCluster = projectCmd.Flag("cluster", "Cluster ID.").Short('c').String() + projectCluster = projectCmd.Flag("cluster", "Cluster ID. When this option is given, the domain/project must be identified by ID. Specifiying a domain/project name will not work.").Short('c').String() + + osAuthURL = app.Flag("os-auth-url", "Authentication URL.").PlaceHolder("OS_AUTH_URL").String() + osUsername = app.Flag("os-username", "Username").PlaceHolder("OS_USERNAME").String() + osPassword = app.Flag("os-password", "User's Password").PlaceHolder("OS_PASSWORD").String() + osUserDomainID = app.Flag("os-user-domain-name", "User's domain ID.").PlaceHolder("OS_USER_DOMAIN_ID").String() + osUserDomainName = app.Flag("os-user-domain-name", "User's domain name.").PlaceHolder("OS_USER_DOMAIN_NAME").String() + osProjectID = app.Flag("os-project-id", "Project ID to scope to.").PlaceHolder("OS_PROJECT_ID").String() + osProjectName = app.Flag("os-project-name", "Project name to scope to.").PlaceHolder("OS_PROJECT_NAME").String() + osProjectDomainID = app.Flag("os-project-domain-ID", "Domain ID containing project to scope to.").PlaceHolder("OS_PROJECT_DOMAIN_ID").String() + osProjectDomainName = app.Flag("os-project-domain-name", "Domain name containing project to scope to.").PlaceHolder("OS_PROJECT_DOMAIN_NAME").String() area = app.Flag("area", "Resource area.").String() service = app.Flag("service", "Service type.").String() @@ -90,30 +100,50 @@ func main() { app.VersionFlag.Short('v') app.HelpFlag.Short('h') - switch kingpin.MustParse(app.Parse(os.Args[1:])) { + // parse all command-line args and flags + cmdString := kingpin.MustParse(app.Parse(os.Args[1:])) + + // overwrite OpenStack variables + setEnvUnlessEmpty("OS_AUTH_URL", *osAuthURL) + setEnvUnlessEmpty("OS_USERNAME", *osUsername) + setEnvUnlessEmpty("OS_PASSWORD", *osPassword) + setEnvUnlessEmpty("OS_USER_DOMAIN_ID", *osUserDomainID) + setEnvUnlessEmpty("OS_USER_DOMAIN_NAME", *osUserDomainName) + setEnvUnlessEmpty("OS_PROJECT_ID", *osProjectID) + setEnvUnlessEmpty("OS_PROJECT_NAME", *osProjectName) + setEnvUnlessEmpty("OS_PROJECT_DOMAIN_ID", *osProjectDomainID) + setEnvUnlessEmpty("OS_PROJECT_DOMAIN_NAME", *osProjectDomainName) + + // output and filter are initialized in advance with values that were provided + // at the command-line. Later, we pass only the specific information that + // is required by the operation + filter := cli.Filter{ + Area: *area, + Service: *service, + Resource: *resource, + } + output := cli.Output{ + Names: *namesOutput, + Long: *longOutput, + HumanReadable: *humanReadableVals, + } + + switch cmdString { case clusterListCmd.FullCommand(): c := &cli.Cluster{ - Opts: cli.Options{ - Long: *longOutput, - HumanReadable: *humanReadableVals, - Area: *area, - Service: *service, - Resource: *resource, - }, + Filter: filter, + Output: output, } cli.RunListTask(c, *outputFmt) + case clusterShowCmd.FullCommand(): c := &cli.Cluster{ - ID: *clusterShowID, - Opts: cli.Options{ - Long: *longOutput, - HumanReadable: *humanReadableVals, - Area: *area, - Service: *service, - Resource: *resource, - }, + ID: *clusterShowID, + Filter: filter, + Output: output, } cli.RunGetTask(c, *outputFmt) + case clusterSetCmd.FullCommand(): // this manual check is required due to the order of the Args. // If the ID is not provided then the capacities get interpreted @@ -121,98 +151,128 @@ func main() { if strings.Contains(*clusterSetID, "=") { kingpin.Fatalf("required argument 'cluster-id' not provided, try --help") } - c := &cli.Cluster{ - ID: *clusterSetID, - } + c := &cli.Cluster{ID: *clusterSetID} cli.RunSetTask(c, clusterSetCaps) + case domainListCmd.FullCommand(): d := &cli.Domain{ - Opts: cli.Options{ - Names: *namesOutput, - Long: *longOutput, - HumanReadable: *humanReadableVals, - Cluster: *domainCluster, - Area: *area, - Service: *service, - Resource: *resource, - }, + Filter: filter, + Output: output, } + d.Filter.Cluster = *domainCluster cli.RunListTask(d, *outputFmt) + case domainShowCmd.FullCommand(): - d, err := cli.FindDomain(*domainShowID) - if err != nil { - kingpin.Fatalf(err.Error()) - } - d.Opts = cli.Options{ - Names: *namesOutput, - Long: *longOutput, - HumanReadable: *humanReadableVals, - Cluster: *domainCluster, - Area: *area, - Service: *service, - Resource: *resource, + // since gophercloud does not allow domain listing across + // different clusters therefore we skip FindDomain(), if a cluster + // was provided at the command-line + var d *cli.Domain + if *domainCluster == "" { + var err error + d, err = cli.FindDomain(*domainShowID) + fatalIfErr(err) + } else { + d = &cli.Domain{ID: *domainShowID} } + + d.Filter = filter + d.Filter.Cluster = *domainCluster + d.Output = output cli.RunGetTask(d, *outputFmt) + case domainSetCmd.FullCommand(): if strings.Contains(*domainSetID, "=") { kingpin.Fatalf("required argument 'domain-id' not provided, try --help") } - d, err := cli.FindDomain(*domainSetID) - if err != nil { - kingpin.Fatalf(err.Error()) + var d *cli.Domain + if *domainCluster == "" { + var err error + d, err = cli.FindDomain(*domainSetID) + fatalIfErr(err) + } else { + d = &cli.Domain{ID: *domainSetID} } - d.Opts.Cluster = *domainCluster + + d.Filter.Cluster = *domainCluster cli.RunSetTask(d, domainSetQuotas) + case projectListCmd.FullCommand(): - d, err := cli.FindDomain(*projectListDomain) - if err != nil { - kingpin.Fatalf(err.Error()) + var d *cli.Domain + var err error + if *projectCluster == "" { + d, err = cli.FindDomain(*projectListDomain) + } else { + d, err = cli.FindDomainInCluster(*projectListDomain, *projectCluster) } + fatalIfErr(err) + p := &cli.Project{ DomainID: d.ID, DomainName: d.Name, - Opts: cli.Options{ - Names: *namesOutput, - Long: *longOutput, - HumanReadable: *humanReadableVals, - Cluster: *projectCluster, - Area: *area, - Service: *service, - Resource: *resource, - }, + Filter: filter, + Output: output, } + p.Filter.Cluster = *projectCluster cli.RunListTask(p, *outputFmt) + case projectShowCmd.FullCommand(): - p, err := cli.FindProject(*projectShowID, *projectShowDomain) - if err != nil { - kingpin.Fatalf(err.Error()) - } - p.Opts = cli.Options{ - Names: *namesOutput, - Long: *longOutput, - HumanReadable: *humanReadableVals, - Cluster: *projectCluster, - Area: *area, - Service: *service, - Resource: *resource, + var p *cli.Project + var err error + if *projectCluster == "" { + p, err = cli.FindProject(*projectShowID, *projectShowDomain) + } else { + p, err = cli.FindProjectInCluster(*projectShowID, *projectShowDomain, *projectCluster) } + fatalIfErr(err) + + p.Filter = filter + p.Filter.Cluster = *projectCluster + p.Output = output cli.RunGetTask(p, *outputFmt) + case projectSetCmd.FullCommand(): if strings.Contains(*projectSetID, "=") { kingpin.Fatalf("required argument 'project-id' not provided, try --help") } - p, err := cli.FindProject(*projectSetID, *projectSetDomain) - if err != nil { - kingpin.Fatalf(err.Error()) + var p *cli.Project + var err error + if *projectCluster == "" { + p, err = cli.FindProject(*projectSetID, *projectSetDomain) + } else { + p, err = cli.FindProjectInCluster(*projectSetID, *projectSetDomain, *projectCluster) } - p.Opts.Cluster = *projectCluster + fatalIfErr(err) + + p.Filter.Cluster = *projectCluster cli.RunSetTask(p, projectSetQuotas) + case projectSyncCmd.FullCommand(): - p, err := cli.FindProject(*projectSyncID, *projectSyncDomain) - if err != nil { - kingpin.Fatalf(err.Error()) + var p *cli.Project + var err error + if *projectCluster == "" { + p, err = cli.FindProject(*projectSyncID, *projectSyncDomain) + } else { + p, err = cli.FindProjectInCluster(*projectSyncID, *projectSyncDomain, *projectCluster) } - p.Opts.Cluster = *projectCluster + fatalIfErr(err) + + p.Filter.Cluster = *projectCluster cli.RunSyncTask(p) } } + +func setEnvUnlessEmpty(env, val string) { + if val == "" { + return + } + + os.Setenv(env, val) +} + +func fatalIfErr(err error) { + if err == nil { + return + } + + kingpin.Fatalf(err.Error()) +} diff --git a/pkg/cli/find.go b/pkg/cli/find.go index 24b9c028..169371ff 100644 --- a/pkg/cli/find.go +++ b/pkg/cli/find.go @@ -131,3 +131,57 @@ func FindProject(userInputProject, userInputDomain string) (*Project, error) { return p, nil } + +// FindDomainInCluster finds a specific domain in a Cluster. +func FindDomainInCluster(domainID, clusterID string) (*Domain, error) { + tmp := &Domain{ + ID: domainID, + Filter: Filter{ + Cluster: clusterID, + }, + } + tmp.get() + + tmpDomain, err := tmp.Result.Extract() + if err != nil { + return nil, err + } + + d := &Domain{ + ID: tmpDomain.UUID, + Name: tmpDomain.Name, + } + + return d, nil +} + +// FindProjectInCluster finds a specific project in a Cluster. +func FindProjectInCluster(projectID, domainID, clusterID string) (*Project, error) { + tmpDomain, err := FindDomainInCluster(domainID, clusterID) + if err != nil { + return nil, err + } + + tmp := &Project{ + ID: projectID, + DomainID: tmpDomain.ID, + Filter: Filter{ + Cluster: clusterID, + }, + } + tmp.get() + + tmpProject, err := tmp.Result.Extract() + if err != nil { + return nil, err + } + + p := &Project{ + ID: tmpProject.UUID, + Name: tmpProject.Name, + DomainID: tmpDomain.ID, + DomainName: tmpDomain.Name, + } + + return p, nil +} diff --git a/pkg/cli/get.go b/pkg/cli/get.go index 45263942..9532922d 100644 --- a/pkg/cli/get.go +++ b/pkg/cli/get.go @@ -30,9 +30,9 @@ func (c *Cluster) get() { _, limesV1 := getServiceClients() c.Result = clusters.Get(limesV1, c.ID, clusters.GetOpts{ - Area: c.Opts.Area, - Service: c.Opts.Service, - Resource: c.Opts.Resource, + Area: c.Filter.Area, + Service: c.Filter.Service, + Resource: c.Filter.Resource, }) handleError("could not get cluster", c.Result.Err) } @@ -42,10 +42,10 @@ func (d *Domain) get() { _, limesV1 := getServiceClients() d.Result = domains.Get(limesV1, d.ID, domains.GetOpts{ - Cluster: d.Opts.Cluster, - Area: d.Opts.Area, - Service: d.Opts.Service, - Resource: d.Opts.Resource, + Cluster: d.Filter.Cluster, + Area: d.Filter.Area, + Service: d.Filter.Service, + Resource: d.Filter.Resource, }) handleError("could not get domain", d.Result.Err) } @@ -55,10 +55,10 @@ func (p *Project) get() { _, limesV1 := getServiceClients() p.Result = projects.Get(limesV1, p.DomainID, p.ID, projects.GetOpts{ - Cluster: p.Opts.Cluster, - Area: p.Opts.Area, - Service: p.Opts.Service, - Resource: p.Opts.Resource, + Cluster: p.Filter.Cluster, + Area: p.Filter.Area, + Service: p.Filter.Service, + Resource: p.Filter.Resource, }) handleError("could not get project", p.Result.Err) } diff --git a/pkg/cli/interface.go b/pkg/cli/interface.go index 3a355ae8..b6911a1f 100644 --- a/pkg/cli/interface.go +++ b/pkg/cli/interface.go @@ -38,9 +38,10 @@ import ( // Call its appropriate method to get/list/update a Cluster. type Cluster struct { ID string - Opts Options Result clusters.CommonResult IsList bool + Filter Filter + Output Output } // Domain contains information regarding a domain(s). @@ -49,9 +50,10 @@ type Cluster struct { type Domain struct { ID string Name string - Opts Options Result domains.CommonResult IsList bool + Filter Filter + Output Output } // Project contains information regarding a project(s). @@ -62,20 +64,25 @@ type Project struct { Name string DomainID string DomainName string - Opts Options Result projects.CommonResult IsList bool + Filter Filter + Output Output } -// Options contains different options that affect the output of a get/list/update operation. -type Options struct { +// Filter contains different parameters for filtering a get/list/update operation. +type Filter struct { + Cluster string + Area string + Service string + Resource string +} + +// Output contains different options that affect the output of a get/list operation. +type Output struct { Names bool Long bool HumanReadable bool - Cluster string - Area string - Service string - Resource string } // GetTask is the interface type that abstracts a get operation. @@ -149,7 +156,7 @@ func RunSyncTask(p *Project) { _, limesV1 := getServiceClients() err := projects.Sync(limesV1, p.DomainID, p.ID, projects.SyncOpts{ - Cluster: p.Opts.Cluster, + Cluster: p.Filter.Cluster, }) handleError("could not sync project", err) } diff --git a/pkg/cli/list.go b/pkg/cli/list.go index 234185ef..de80c67a 100644 --- a/pkg/cli/list.go +++ b/pkg/cli/list.go @@ -31,9 +31,9 @@ func (c *Cluster) list() { c.IsList = true c.Result = clusters.List(limesV1, clusters.ListOpts{ - Area: c.Opts.Area, - Service: c.Opts.Service, - Resource: c.Opts.Resource, + Area: c.Filter.Area, + Service: c.Filter.Service, + Resource: c.Filter.Resource, }) handleError("could not list clusters", c.Result.Err) } @@ -44,10 +44,10 @@ func (d *Domain) list() { d.IsList = true d.Result = domains.List(limesV1, domains.ListOpts{ - Cluster: d.Opts.Cluster, - Area: d.Opts.Area, - Service: d.Opts.Service, - Resource: d.Opts.Resource, + Cluster: d.Filter.Cluster, + Area: d.Filter.Area, + Service: d.Filter.Service, + Resource: d.Filter.Resource, }) handleError("could not list domains", d.Result.Err) } @@ -58,10 +58,10 @@ func (p *Project) list() { p.IsList = true p.Result = projects.List(limesV1, p.DomainID, projects.ListOpts{ - Cluster: p.Opts.Cluster, - Area: p.Opts.Area, - Service: p.Opts.Service, - Resource: p.Opts.Resource, + Cluster: p.Filter.Cluster, + Area: p.Filter.Area, + Service: p.Filter.Service, + Resource: p.Filter.Resource, }) handleError("could not list projects", p.Result.Err) } diff --git a/pkg/cli/render_csv.go b/pkg/cli/render_csv.go index dfec7e04..39a48b59 100644 --- a/pkg/cli/render_csv.go +++ b/pkg/cli/render_csv.go @@ -38,7 +38,7 @@ func (c *Cluster) renderCSV() *csvData { var labels []string switch { - case c.Opts.Long: + case c.Output.Long: labels = []string{"cluster id", "area", "service", "category", "resource", "capacity", "domains quota", "usage", "unit", "comment", "scraped at (UTC)"} default: @@ -69,14 +69,14 @@ func (d *Domain) renderCSV() *csvData { var data csvData var labels []string - if d.Opts.Names && d.Opts.Long { + if d.Output.Names && d.Output.Long { handleError("", errors.New("'--names' and '--long' can not be used together")) } switch { - case d.Opts.Names: + case d.Output.Names: labels = []string{"domain name", "service", "resource", "quota", "projects quota", "usage", "unit"} - case d.Opts.Long: + case d.Output.Long: labels = []string{"domain id", "domain name", "area", "service", "category", "resource", "quota", "projects quota", "usage", "unit", "scraped at (UTC)"} default: @@ -107,14 +107,14 @@ func (p *Project) renderCSV() *csvData { var data csvData var labels []string - if p.Opts.Names && p.Opts.Long { + if p.Output.Names && p.Output.Long { handleError("", errors.New("'--names' and '--long' can not be used together")) } switch { - case p.Opts.Names: + case p.Output.Names: labels = []string{"domain name", "project name", "service", "resource", "quota", "usage", "unit"} - case p.Opts.Long: + case p.Output.Long: labels = []string{"domain id", "domain name", "project id", "project name", "area", "service", "category", "resource", "quota", "usage", "unit", "scraped at (UTC)"} default: @@ -170,14 +170,14 @@ func (c *Cluster) parseToCSV(cluster *reports.Cluster, data *csvData) { cap = *tmp } - unit, val := humanReadable(c.Opts.HumanReadable, cSrvRes.ResourceInfo.Unit, rawValues{ + unit, val := humanReadable(c.Output.HumanReadable, cSrvRes.ResourceInfo.Unit, rawValues{ "capacity": cap, "domainsQuota": cSrvRes.DomainsQuota, "usage": cSrvRes.Usage, }) switch { - case c.Opts.Long: + case c.Output.Long: csvRecord = append(csvRecord, cluster.ID, cSrv.ServiceInfo.Area, cSrv.ServiceInfo.Type, cSrvRes.ResourceInfo.Category, cSrvRes.ResourceInfo.Name, val["capacity"], val["domainsQuota"], val["usage"], unit, cSrvRes.Comment, time.Unix(cSrv.MinScrapedAt, 0).UTC().Format(time.RFC3339), @@ -217,18 +217,18 @@ func (d *Domain) parseToCSV(domain *reports.Domain, data *csvData) { dSrv := domain.Services[srv] dSrvRes := domain.Services[srv].Resources[res] - unit, val := humanReadable(d.Opts.HumanReadable, dSrvRes.ResourceInfo.Unit, rawValues{ + unit, val := humanReadable(d.Output.HumanReadable, dSrvRes.ResourceInfo.Unit, rawValues{ "domainQuota": dSrvRes.DomainQuota, "projectsQuota": dSrvRes.ProjectsQuota, "usage": dSrvRes.Usage, }) switch { - case d.Opts.Names: + case d.Output.Names: csvRecord = append(csvRecord, domain.Name, dSrv.ServiceInfo.Type, dSrvRes.ResourceInfo.Name, val["domainQuota"], val["projectsQuota"], val["usage"], unit, ) - case d.Opts.Long: + case d.Output.Long: csvRecord = append(csvRecord, domain.UUID, domain.Name, dSrv.ServiceInfo.Area, dSrv.ServiceInfo.Type, dSrvRes.ResourceInfo.Category, dSrvRes.ResourceInfo.Name, val["domainQuota"], val["projectsQuota"], val["usage"], unit, time.Unix(dSrv.MinScrapedAt, 0).UTC().Format(time.RFC3339), @@ -268,17 +268,17 @@ func (p *Project) parseToCSV(project *reports.Project, data *csvData) { pSrv := project.Services[srv] pSrvRes := project.Services[srv].Resources[res] - unit, val := humanReadable(p.Opts.HumanReadable, pSrvRes.ResourceInfo.Unit, rawValues{ + unit, val := humanReadable(p.Output.HumanReadable, pSrvRes.ResourceInfo.Unit, rawValues{ "quota": pSrvRes.Quota, "usage": pSrvRes.Usage, }) switch { - case p.Opts.Names: + case p.Output.Names: csvRecord = append(csvRecord, p.DomainName, project.Name, pSrv.ServiceInfo.Type, pSrvRes.ResourceInfo.Name, val["quota"], val["usage"], unit, ) - case p.Opts.Long: + case p.Output.Long: csvRecord = append(csvRecord, p.DomainID, p.DomainName, project.UUID, project.Name, pSrv.ServiceInfo.Area, pSrv.ServiceInfo.Type, pSrvRes.ResourceInfo.Category, pSrvRes.ResourceInfo.Name, val["quota"], val["usage"], unit, time.Unix(pSrv.ScrapedAt, 0).UTC().Format(time.RFC3339), diff --git a/pkg/cli/set.go b/pkg/cli/set.go index 5c7d7d24..c50ca99c 100644 --- a/pkg/cli/set.go +++ b/pkg/cli/set.go @@ -44,7 +44,7 @@ func (d *Domain) set(q *Quotas) { sq := makeServiceQuotas(q) err := domains.Update(limesV1, d.ID, domains.UpdateOpts{ - Cluster: d.Opts.Cluster, + Cluster: d.Filter.Cluster, Services: sq, }) handleError("could not set new quota(s) for domain", err) @@ -57,7 +57,7 @@ func (p *Project) set(q *Quotas) { sq := makeServiceQuotas(q) respBody, err := projects.Update(limesV1, p.DomainID, p.ID, projects.UpdateOpts{ - Cluster: p.Opts.Cluster, + Cluster: p.Filter.Cluster, Services: sq, }) handleError("could not set new quota(s) for project", err) diff --git a/pkg/cli/unit_test.go b/pkg/cli/unit_test.go index 7832a34c..29fed9dd 100644 --- a/pkg/cli/unit_test.go +++ b/pkg/cli/unit_test.go @@ -119,12 +119,14 @@ func TestRenderClusterCSV(t *testing.T) { // filtered get c, err = makeMockCluster("./fixtures/cluster-get-filtered.json") th.AssertNoErr(t, err) - c.Opts = Options{ + c.Output = Output{ HumanReadable: true, Long: true, - Area: "shared", - Service: "shared", - Resource: "capacity", + } + c.Filter = Filter{ + Area: "shared", + Service: "shared", + Resource: "capacity", } actual, err = captureOutput(func() { c.renderCSV().writeCSV() }) @@ -150,8 +152,10 @@ func TestRenderClusterCSV(t *testing.T) { c, err = makeMockCluster("./fixtures/cluster-list-filtered.json") th.AssertNoErr(t, err) c.IsList = true - c.Opts = Options{ - Long: true, + c.Output = Output{ + Long: true, + } + c.Filter = Filter{ Area: "shared", Service: "shared", Resource: "capacity", @@ -180,12 +184,14 @@ func TestRenderDomainCSV(t *testing.T) { // filtered get d, err = makeMockDomain("./fixtures/domain-get-filtered.json") th.AssertNoErr(t, err) - d.Opts = Options{ + d.Output = Output{ HumanReadable: true, Names: true, - Area: "shared", - Service: "shared", - Resource: "capacity", + } + d.Filter = Filter{ + Area: "shared", + Service: "shared", + Resource: "capacity", } actual, err = captureOutput(func() { d.renderCSV().writeCSV() }) @@ -211,8 +217,10 @@ func TestRenderDomainCSV(t *testing.T) { d, err = makeMockDomain("./fixtures/domain-list-filtered.json") th.AssertNoErr(t, err) d.IsList = true - d.Opts = Options{ - Long: true, + d.Output = Output{ + Long: true, + } + d.Filter = Filter{ Area: "shared", Service: "shared", Resource: "things", @@ -241,12 +249,14 @@ func TestRenderProjectCSV(t *testing.T) { // filtered get p, err = makeMockProject("./fixtures/project-get-filtered.json") th.AssertNoErr(t, err) - p.Opts = Options{ + p.Output = Output{ HumanReadable: true, Names: true, - Area: "shared", - Service: "shared", - Resource: "capacity", + } + p.Filter = Filter{ + Area: "shared", + Service: "shared", + Resource: "capacity", } actual, err = captureOutput(func() { p.renderCSV().writeCSV() }) @@ -272,8 +282,10 @@ func TestRenderProjectCSV(t *testing.T) { p, err = makeMockProject("./fixtures/project-list-filtered.json") th.AssertNoErr(t, err) p.IsList = true - p.Opts = Options{ - Long: true, + p.Output = Output{ + Long: true, + } + p.Filter = Filter{ Area: "shared", Service: "shared", Resource: "things",