Change backend.ListStacks to return a new StackSummary interface (#1931)
* Have backend.ListStacks return a new StackSummary interface * Update filestake backend to use new type * Update httpstate backend to use new type * Update commands to use new type * lint * Address PR feedback * Lint
This commit is contained in:
parent
3642cca98f
commit
792c316e5e
|
@ -23,13 +23,11 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/pulumi/pulumi/pkg/backend"
|
|
||||||
"github.com/pulumi/pulumi/pkg/backend/display"
|
"github.com/pulumi/pulumi/pkg/backend/display"
|
||||||
"github.com/pulumi/pulumi/pkg/backend/httpstate"
|
"github.com/pulumi/pulumi/pkg/backend/httpstate"
|
||||||
"github.com/pulumi/pulumi/pkg/backend/state"
|
"github.com/pulumi/pulumi/pkg/backend/state"
|
||||||
"github.com/pulumi/pulumi/pkg/tokens"
|
"github.com/pulumi/pulumi/pkg/tokens"
|
||||||
"github.com/pulumi/pulumi/pkg/util/cmdutil"
|
"github.com/pulumi/pulumi/pkg/util/cmdutil"
|
||||||
"github.com/pulumi/pulumi/pkg/util/contract"
|
|
||||||
"github.com/pulumi/pulumi/pkg/workspace"
|
"github.com/pulumi/pulumi/pkg/workspace"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,7 +55,7 @@ func newStackLsCmd() *cobra.Command {
|
||||||
Color: cmdutil.GetGlobalColorization(),
|
Color: cmdutil.GetGlobalColorization(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a list of all known backends, as we will query them all.
|
// Get the current backend.
|
||||||
b, err := currentBackend(opts)
|
b, err := currentBackend(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -75,93 +73,77 @@ func newStackLsCmd() *cobra.Command {
|
||||||
packageFilter = &proj.Name
|
packageFilter = &proj.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now produce a list of summaries, and enumerate them sorted by name.
|
// List all of the stacks available.
|
||||||
var result error
|
stackSummaries, err := b.ListStacks(commandContext(), packageFilter)
|
||||||
var stackNames []string
|
|
||||||
stacks := make(map[string]backend.Stack)
|
|
||||||
bs, err := b.ListStacks(commandContext(), packageFilter)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Sort by stack name.
|
||||||
|
sort.Slice(stackSummaries, func(i, j int) bool {
|
||||||
|
return stackSummaries[i].Name().String() < stackSummaries[j].Name().String()
|
||||||
|
})
|
||||||
|
|
||||||
_, showURLColumn := b.(httpstate.Backend)
|
_, showURLColumn := b.(httpstate.Backend)
|
||||||
|
|
||||||
for _, stack := range bs {
|
|
||||||
name := stack.Ref().String()
|
|
||||||
stacks[name] = stack
|
|
||||||
stackNames = append(stackNames, name)
|
|
||||||
}
|
|
||||||
sort.Strings(stackNames)
|
|
||||||
|
|
||||||
// Devote 48 characters to the name width, unless there is a longer name.
|
// Devote 48 characters to the name width, unless there is a longer name.
|
||||||
maxname := 48
|
maxName := 47
|
||||||
for _, name := range stackNames {
|
for _, summary := range stackSummaries {
|
||||||
if len(name) > maxname {
|
name := summary.Name().String()
|
||||||
maxname = len(name)
|
if len(name) > maxName {
|
||||||
|
maxName = len(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
maxName++ // Account for adding the '*' to the currently selected stack.
|
||||||
|
|
||||||
// We have to fault in snapshots for all the stacks we are going to list here, because that's the easiest
|
// Header string and formatting options to align columns.
|
||||||
// way to get the last update time and the resource count. Since this is an expensive operation, we'll
|
formatDirective := "%-" + strconv.Itoa(maxName) + "s %-24s %-18s"
|
||||||
// do it before printing any output so the latency happens all at once instead of line by line.
|
|
||||||
//
|
|
||||||
// TODO[pulumi/pulumi-service#1530]: We need a lighterweight way of fetching just the specific information
|
|
||||||
// we want to display here.
|
|
||||||
for _, name := range stackNames {
|
|
||||||
stack := stacks[name]
|
|
||||||
_, err := stack.Snapshot(commandContext())
|
|
||||||
contract.IgnoreError(err) // If we couldn't get snapshot for the stack don't fail the overall listing.
|
|
||||||
}
|
|
||||||
|
|
||||||
formatDirective := "%-" + strconv.Itoa(maxname) + "s %-24s %-18s"
|
|
||||||
headers := []interface{}{"NAME", "LAST UPDATE", "RESOURCE COUNT"}
|
headers := []interface{}{"NAME", "LAST UPDATE", "RESOURCE COUNT"}
|
||||||
|
|
||||||
if showURLColumn {
|
if showURLColumn {
|
||||||
formatDirective += " %s"
|
formatDirective += " %s"
|
||||||
headers = append(headers, "URL")
|
headers = append(headers, "URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
formatDirective = formatDirective + "\n"
|
formatDirective = formatDirective + "\n"
|
||||||
|
|
||||||
fmt.Printf(formatDirective, headers...)
|
fmt.Printf(formatDirective, headers...)
|
||||||
for _, name := range stackNames {
|
for _, summary := range stackSummaries {
|
||||||
// Mark the name as current '*' if we've selected it.
|
const none = "n/a"
|
||||||
stack := stacks[name]
|
|
||||||
|
// Name column
|
||||||
|
name := summary.Name().String()
|
||||||
if name == current {
|
if name == current {
|
||||||
name += "*"
|
name += "*"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get last deployment info, provided that it exists.
|
// Last update column
|
||||||
none := "n/a"
|
|
||||||
lastUpdate := none
|
lastUpdate := none
|
||||||
resourceCount := none
|
if stackLastUpdate := summary.LastUpdate(); stackLastUpdate != nil {
|
||||||
snap, err := stack.Snapshot(commandContext())
|
lastUpdate = humanize.Time(*stackLastUpdate)
|
||||||
contract.IgnoreError(err) // If we couldn't get snapshot for the stack don't fail the overall listing.
|
|
||||||
|
|
||||||
if snap != nil {
|
|
||||||
if t := snap.Manifest.Time; !t.IsZero() {
|
|
||||||
lastUpdate = humanize.Time(t)
|
|
||||||
}
|
|
||||||
resourceCount = strconv.Itoa(len(snap.Resources))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResourceCount column
|
||||||
|
resourceCount := none
|
||||||
|
if stackResourceCount := summary.ResourceCount(); stackResourceCount != nil {
|
||||||
|
resourceCount = strconv.Itoa(*stackResourceCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the columns.
|
||||||
values := []interface{}{name, lastUpdate, resourceCount}
|
values := []interface{}{name, lastUpdate, resourceCount}
|
||||||
if showURLColumn {
|
if showURLColumn {
|
||||||
var url string
|
var url string
|
||||||
if cs, ok := stack.(httpstate.Stack); ok {
|
if httpBackend, ok := b.(httpstate.Backend); ok {
|
||||||
if u, urlErr := cs.ConsoleURL(); urlErr == nil {
|
if nameSuffix, err := httpBackend.StackConsoleURL(summary.Name()); err != nil {
|
||||||
url = u
|
url = none
|
||||||
|
} else {
|
||||||
|
url = fmt.Sprintf("%s/%s", httpBackend.CloudURL(), nameSuffix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if url == "" {
|
|
||||||
url = none
|
|
||||||
}
|
|
||||||
values = append(values, url)
|
values = append(values, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf(formatDirective, values...)
|
fmt.Printf(formatDirective, values...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return nil
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
cmd.PersistentFlags().BoolVarP(
|
cmd.PersistentFlags().BoolVarP(
|
||||||
|
|
40
cmd/util.go
40
cmd/util.go
|
@ -163,7 +163,7 @@ func requireCurrentStack(offerNew bool, opts display.Options, setCurrent bool) (
|
||||||
return chooseStack(b, offerNew, opts, setCurrent)
|
return chooseStack(b, offerNew, opts, setCurrent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// chooseStack will prompt the user to choose amongst the full set of stacks in the given backends. If offerNew is
|
// chooseStack will prompt the user to choose amongst the full set of stacks in the given backend. If offerNew is
|
||||||
// true, then the option to create an entirely new stack is provided and will create one as desired.
|
// true, then the option to create an entirely new stack is provided and will create one as desired.
|
||||||
func chooseStack(
|
func chooseStack(
|
||||||
b backend.Backend, offerNew bool, opts display.Options, setCurrent bool) (backend.Stack, error) {
|
b backend.Backend, offerNew bool, opts display.Options, setCurrent bool) (backend.Stack, error) {
|
||||||
|
@ -183,22 +183,20 @@ func chooseStack(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// First create a list and map of stack names.
|
// List stacks as available options.
|
||||||
var options []string
|
var options []string
|
||||||
stacks := make(map[string]backend.Stack)
|
summaries, err := b.ListStacks(commandContext(), &proj.Name)
|
||||||
allStacks, err := b.ListStacks(commandContext(), &proj.Name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "could not query backend for stacks")
|
return nil, errors.Wrapf(err, "could not query backend for stacks")
|
||||||
}
|
}
|
||||||
for _, stack := range allStacks {
|
for _, summary := range summaries {
|
||||||
name := stack.Ref().String()
|
name := summary.Name().String()
|
||||||
options = append(options, name)
|
options = append(options, name)
|
||||||
stacks[name] = stack
|
|
||||||
}
|
}
|
||||||
sort.Strings(options)
|
sort.Strings(options)
|
||||||
|
|
||||||
// If we are offering to create a new stack, add that to the end of the list.
|
// If we are offering to create a new stack, add that to the end of the list.
|
||||||
newOption := "<create a new stack>"
|
const newOption = "<create a new stack>"
|
||||||
if offerNew {
|
if offerNew {
|
||||||
options = append(options, newOption)
|
options = append(options, newOption)
|
||||||
} else if len(options) == 0 {
|
} else if len(options) == 0 {
|
||||||
|
@ -227,7 +225,7 @@ func chooseStack(
|
||||||
message = opts.Color.Colorize(colors.BrightWhite + message + colors.Reset)
|
message = opts.Color.Colorize(colors.BrightWhite + message + colors.Reset)
|
||||||
|
|
||||||
var option string
|
var option string
|
||||||
if err := survey.AskOne(&survey.Select{
|
if err = survey.AskOne(&survey.Select{
|
||||||
Message: message,
|
Message: message,
|
||||||
Options: options,
|
Options: options,
|
||||||
Default: current,
|
Default: current,
|
||||||
|
@ -236,20 +234,30 @@ func chooseStack(
|
||||||
}
|
}
|
||||||
|
|
||||||
if option == newOption {
|
if option == newOption {
|
||||||
stackName, err := cmdutil.ReadConsole("Please enter your desired stack name")
|
stackName, readErr := cmdutil.ReadConsole("Please enter your desired stack name")
|
||||||
if err != nil {
|
if readErr != nil {
|
||||||
return nil, err
|
return nil, readErr
|
||||||
}
|
}
|
||||||
|
|
||||||
stackRef, err := b.ParseStackReference(stackName)
|
stackRef, parseErr := b.ParseStackReference(stackName)
|
||||||
if err != nil {
|
if parseErr != nil {
|
||||||
return nil, err
|
return nil, parseErr
|
||||||
}
|
}
|
||||||
|
|
||||||
return createStack(b, stackRef, nil, setCurrent)
|
return createStack(b, stackRef, nil, setCurrent)
|
||||||
}
|
}
|
||||||
|
|
||||||
return stacks[option], nil
|
// With the stack name selected, look it up from the backend.
|
||||||
|
stackRef, err := b.ParseStackReference(option)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "parsing selected stack")
|
||||||
|
}
|
||||||
|
stack, err := b.GetStack(commandContext(), stackRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "getting selected stack")
|
||||||
|
}
|
||||||
|
|
||||||
|
return stack, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readProject attempts to detect and read the project for the current workspace. If an error occurs, it will be
|
// readProject attempts to detect and read the project for the current workspace. If an error occurs, it will be
|
||||||
|
|
|
@ -18,6 +18,7 @@ package backend
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
@ -58,6 +59,16 @@ type StackReference interface {
|
||||||
Name() tokens.QName
|
Name() tokens.QName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StackSummary provides a basic description of a stack, without the ability to inspect its resources or make changes.
|
||||||
|
type StackSummary interface {
|
||||||
|
Name() StackReference
|
||||||
|
|
||||||
|
// LastUpdate returns when the stack was last updated, as applicable.
|
||||||
|
LastUpdate() *time.Time
|
||||||
|
// ResourceCount returns the stack's resource count, as applicable.
|
||||||
|
ResourceCount() *int
|
||||||
|
}
|
||||||
|
|
||||||
// Backend is an interface that represents actions the engine will interact with to manage stacks of cloud resources.
|
// Backend is an interface that represents actions the engine will interact with to manage stacks of cloud resources.
|
||||||
// It can be implemented any number of ways to provide pluggable backend implementations of the Pulumi Cloud.
|
// It can be implemented any number of ways to provide pluggable backend implementations of the Pulumi Cloud.
|
||||||
type Backend interface {
|
type Backend interface {
|
||||||
|
@ -79,7 +90,7 @@ type Backend interface {
|
||||||
// first boolean return value will be set to true.
|
// first boolean return value will be set to true.
|
||||||
RemoveStack(ctx context.Context, stackRef StackReference, force bool) (bool, error)
|
RemoveStack(ctx context.Context, stackRef StackReference, force bool) (bool, error)
|
||||||
// ListStacks returns a list of stack summaries for all known stacks in the target backend.
|
// ListStacks returns a list of stack summaries for all known stacks in the target backend.
|
||||||
ListStacks(ctx context.Context, projectFilter *tokens.PackageName) ([]Stack, error)
|
ListStacks(ctx context.Context, projectFilter *tokens.PackageName) ([]StackSummary, error)
|
||||||
|
|
||||||
// GetStackCrypter returns an encrypter/decrypter for the given stack's secret config values.
|
// GetStackCrypter returns an encrypter/decrypter for the given stack's secret config values.
|
||||||
GetStackCrypter(stackRef StackReference) (config.Crypter, error)
|
GetStackCrypter(stackRef StackReference) (config.Crypter, error)
|
||||||
|
|
|
@ -176,19 +176,22 @@ func (b *localBackend) GetStack(ctx context.Context, stackRef backend.StackRefer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *localBackend) ListStacks(ctx context.Context, projectFilter *tokens.PackageName) ([]backend.Stack, error) {
|
func (b *localBackend) ListStacks(
|
||||||
|
ctx context.Context, projectFilter *tokens.PackageName) ([]backend.StackSummary, error) {
|
||||||
stacks, err := b.getLocalStacks()
|
stacks, err := b.getLocalStacks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var results []backend.Stack
|
var results []backend.StackSummary
|
||||||
for _, stackName := range stacks {
|
for _, stackName := range stacks {
|
||||||
stack, err := b.GetStack(ctx, localBackendReference{name: stackName})
|
stack, err := b.GetStack(ctx, localBackendReference{name: stackName})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
results = append(results, stack)
|
localStack, ok := stack.(*localStack)
|
||||||
|
contract.Assertf(ok, "localBackend GetStack returned non-localStack")
|
||||||
|
results = append(results, newLocalStackSummary(localStack))
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
return results, nil
|
||||||
|
|
|
@ -16,6 +16,7 @@ package filestate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pulumi/pulumi/pkg/apitype"
|
"github.com/pulumi/pulumi/pkg/apitype"
|
||||||
"github.com/pulumi/pulumi/pkg/backend"
|
"github.com/pulumi/pulumi/pkg/backend"
|
||||||
|
@ -88,3 +89,34 @@ func (s *localStack) ExportDeployment(ctx context.Context) (*apitype.UntypedDepl
|
||||||
func (s *localStack) ImportDeployment(ctx context.Context, deployment *apitype.UntypedDeployment) error {
|
func (s *localStack) ImportDeployment(ctx context.Context, deployment *apitype.UntypedDeployment) error {
|
||||||
return backend.ImportStackDeployment(ctx, s, deployment)
|
return backend.ImportStackDeployment(ctx, s, deployment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type localStackSummary struct {
|
||||||
|
s *localStack
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLocalStackSummary(s *localStack) localStackSummary {
|
||||||
|
return localStackSummary{s}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lss localStackSummary) Name() backend.StackReference {
|
||||||
|
return lss.s.Ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lss localStackSummary) LastUpdate() *time.Time {
|
||||||
|
snap := lss.s.snapshot
|
||||||
|
if snap != nil {
|
||||||
|
if t := snap.Manifest.Time; !t.IsZero() {
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lss localStackSummary) ResourceCount() *int {
|
||||||
|
snap := lss.s.snapshot
|
||||||
|
if snap != nil {
|
||||||
|
count := len(snap.Resources)
|
||||||
|
return &count
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -416,7 +416,7 @@ func serveBrowserLoginServer(l net.Listener, expectedNonce string, destinationUR
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloudConsoleStackPath returns the stack path components for getting to a stack in the cloud console. This path
|
// CloudConsoleStackPath returns the stack path components for getting to a stack in the cloud console. This path
|
||||||
// must, of coursee, be combined with the actual console base URL by way of the CloudConsoleURL function above.
|
// must, of course, be combined with the actual console base URL by way of the CloudConsoleURL function above.
|
||||||
func (b *cloudBackend) cloudConsoleStackPath(stackID client.StackIdentifier) string {
|
func (b *cloudBackend) cloudConsoleStackPath(stackID client.StackIdentifier) string {
|
||||||
return path.Join(stackID.Owner, stackID.Stack)
|
return path.Join(stackID.Owner, stackID.Stack)
|
||||||
}
|
}
|
||||||
|
@ -529,19 +529,24 @@ func (b *cloudBackend) CreateStack(ctx context.Context, stackRef backend.StackRe
|
||||||
return stack, nil
|
return stack, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *cloudBackend) ListStacks(ctx context.Context, projectFilter *tokens.PackageName) ([]backend.Stack, error) {
|
func (b *cloudBackend) ListStacks(
|
||||||
stacks, err := b.client.ListStacks(ctx, projectFilter)
|
ctx context.Context, projectFilter *tokens.PackageName) ([]backend.StackSummary, error) {
|
||||||
|
apiSummaries, err := b.client.ListStacks(ctx, projectFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map to a summary slice.
|
// Convert []apitype.StackSummary into []backend.StackSummary.
|
||||||
var results []backend.Stack
|
var backendSummaries []backend.StackSummary
|
||||||
for _, stack := range stacks {
|
for _, apiSummary := range apiSummaries {
|
||||||
results = append(results, newStack(stack, b))
|
backendSummary := cloudStackSummary{
|
||||||
|
summary: apiSummary,
|
||||||
|
b: b,
|
||||||
|
}
|
||||||
|
backendSummaries = append(backendSummaries, backendSummary)
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
return backendSummaries, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *cloudBackend) RemoveStack(ctx context.Context, stackRef backend.StackReference, force bool) (bool, error) {
|
func (b *cloudBackend) RemoveStack(ctx context.Context, stackRef backend.StackReference, force bool) (bool, error) {
|
||||||
|
|
|
@ -124,6 +124,8 @@ func pulumiAPICall(ctx context.Context, cloudAPI, method, path string, body []by
|
||||||
// backwards compatibility.
|
// backwards compatibility.
|
||||||
userAgent := fmt.Sprintf("pulumi-cli/1 (%s; %s)", version.Version, runtime.GOOS)
|
userAgent := fmt.Sprintf("pulumi-cli/1 (%s; %s)", version.Version, runtime.GOOS)
|
||||||
req.Header.Set("User-Agent", userAgent)
|
req.Header.Set("User-Agent", userAgent)
|
||||||
|
// Specify the specific API version we accept.
|
||||||
|
req.Header.Set("Accept", "application/vnd.pulumi+1")
|
||||||
|
|
||||||
// Apply credentials if provided.
|
// Apply credentials if provided.
|
||||||
if tok.String() != "" {
|
if tok.String() != "" {
|
||||||
|
|
|
@ -149,11 +149,10 @@ func (pc *Client) GetCLIVersionInfo(ctx context.Context) (semver.Version, semver
|
||||||
return latestSem, oldestSem, nil
|
return latestSem, oldestSem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListStacks lists all stacks for the indicated project.
|
// ListStacks lists all stacks the current user has access to, optionally filtered by project.
|
||||||
func (pc *Client) ListStacks(ctx context.Context, projectFilter *tokens.PackageName) ([]apitype.Stack, error) {
|
func (pc *Client) ListStacks(ctx context.Context, projectFilter *tokens.PackageName) ([]apitype.StackSummary, error) {
|
||||||
|
|
||||||
// Query all stacks for the project on Pulumi.
|
var resp apitype.ListStacksResponse
|
||||||
var stacks []apitype.Stack
|
|
||||||
var queryFilter interface{}
|
var queryFilter interface{}
|
||||||
if projectFilter != nil {
|
if projectFilter != nil {
|
||||||
queryFilter = struct {
|
queryFilter = struct {
|
||||||
|
@ -161,11 +160,11 @@ func (pc *Client) ListStacks(ctx context.Context, projectFilter *tokens.PackageN
|
||||||
}{ProjectFilter: string(*projectFilter)}
|
}{ProjectFilter: string(*projectFilter)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pc.restCall(ctx, "GET", "/api/user/stacks", queryFilter, nil, &stacks); err != nil {
|
if err := pc.restCall(ctx, "GET", "/api/user/stacks", queryFilter, nil, &resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return stacks, nil
|
return resp.Stacks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLatestConfiguration returns the configuration for the latest deployment of a given stack.
|
// GetLatestConfiguration returns the configuration for the latest deployment of a given stack.
|
||||||
|
|
|
@ -17,6 +17,7 @@ package httpstate
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pulumi/pulumi/pkg/apitype"
|
"github.com/pulumi/pulumi/pkg/apitype"
|
||||||
|
@ -153,3 +154,30 @@ func (s *cloudStack) ConsoleURL() (string, error) {
|
||||||
}
|
}
|
||||||
return url, nil
|
return url, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cloudStackSummary implements the backend.StackSummary interface, by wrapping
|
||||||
|
// an apitype.StackSummary struct.
|
||||||
|
type cloudStackSummary struct {
|
||||||
|
summary apitype.StackSummary
|
||||||
|
b *cloudBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
func (css cloudStackSummary) Name() backend.StackReference {
|
||||||
|
return cloudBackendReference{
|
||||||
|
owner: css.summary.OrgName,
|
||||||
|
name: tokens.QName(css.summary.StackName),
|
||||||
|
b: css.b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (css cloudStackSummary) LastUpdate() *time.Time {
|
||||||
|
if css.summary.LastUpdate == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t := time.Unix(*css.summary.LastUpdate, 0)
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (css cloudStackSummary) ResourceCount() *int {
|
||||||
|
return css.summary.ResourceCount
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue