[automation/*] Add support for getting stack outputs using Workspace (#6859)
This commit is contained in:
parent
3cbfddf870
commit
daa6045381
|
@ -4,7 +4,8 @@
|
|||
|
||||
### Enhancements
|
||||
|
||||
|
||||
- [automation/*] Add support for getting stack outputs using Workspace
|
||||
[#6859](https://github.com/pulumi/pulumi/pull/6859)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
|
|
|
@ -649,6 +649,31 @@ namespace Pulumi.Automation
|
|||
return plugins.ToImmutableList();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<ImmutableDictionary<string, OutputValue>> GetStackOutputsAsync(string stackName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// TODO: do this in parallel after this is fixed https://github.com/pulumi/pulumi/issues/6050
|
||||
var maskedResult = await this.RunCommandAsync(new[] { "stack", "output", "--json", "--stack", stackName }, cancellationToken).ConfigureAwait(false);
|
||||
var plaintextResult = await this.RunCommandAsync(new[] { "stack", "output", "--json", "--show-secrets", "--stack", stackName }, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var maskedOutput = string.IsNullOrWhiteSpace(maskedResult.StandardOutput)
|
||||
? new Dictionary<string, object>()
|
||||
: _serializer.DeserializeJson<Dictionary<string, object>>(maskedResult.StandardOutput);
|
||||
|
||||
var plaintextOutput = string.IsNullOrWhiteSpace(plaintextResult.StandardOutput)
|
||||
? new Dictionary<string, object>()
|
||||
: _serializer.DeserializeJson<Dictionary<string, object>>(plaintextResult.StandardOutput);
|
||||
|
||||
var output = new Dictionary<string, OutputValue>();
|
||||
foreach (var (key, value) in plaintextOutput)
|
||||
{
|
||||
var secret = maskedOutput[key] is string maskedValue && maskedValue == "[secret]";
|
||||
output[key] = new OutputValue(value, secret);
|
||||
}
|
||||
|
||||
return output.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
abstract Pulumi.Automation.Workspace.GetStackOutputsAsync(string stackName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Immutable.ImmutableDictionary<string, Pulumi.Automation.OutputValue>>
|
||||
override Pulumi.Automation.LocalWorkspace.GetStackOutputsAsync(string stackName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Immutable.ImmutableDictionary<string, Pulumi.Automation.OutputValue>>
|
|
@ -599,6 +599,9 @@
|
|||
<member name="M:Pulumi.Automation.LocalWorkspace.ListPluginsAsync(System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Pulumi.Automation.LocalWorkspace.GetOutputsAsync(System.String,System.Threading.CancellationToken)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="T:Pulumi.Automation.LocalWorkspaceOptions">
|
||||
<summary>
|
||||
Extensibility options to configure a LocalWorkspace; e.g: settings to seed
|
||||
|
@ -1089,6 +1092,13 @@
|
|||
Returns a list of all plugins installed in the Workspace.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Pulumi.Automation.Workspace.GetOutputsAsync(System.String,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Gets the current set of Stack outputs from the last <see cref="M:Pulumi.Automation.WorkspaceStack.UpAsync(Pulumi.Automation.UpOptions,System.Threading.CancellationToken)"/>.
|
||||
</summary>
|
||||
<param name="stackName">The name of the stack.</param>
|
||||
<param name="cancellationToken">A cancellation token.</param>
|
||||
</member>
|
||||
<member name="T:Pulumi.Automation.WorkspaceStack">
|
||||
<summary>
|
||||
<see cref="T:Pulumi.Automation.WorkspaceStack"/> is an isolated, independently configurable instance of a
|
||||
|
|
|
@ -267,6 +267,13 @@ namespace Pulumi.Automation
|
|||
/// </summary>
|
||||
public abstract Task<ImmutableList<PluginInfo>> ListPluginsAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current set of Stack outputs from the last <see cref="WorkspaceStack.UpAsync(UpOptions?, CancellationToken)"/>.
|
||||
/// </summary>
|
||||
/// <param name="stackName">The name of the stack.</param>
|
||||
/// <param name="cancellationToken">A cancellation token.</param>
|
||||
public abstract Task<ImmutableDictionary<string, OutputValue>> GetStackOutputsAsync(string stackName, CancellationToken cancellationToken = default);
|
||||
|
||||
internal async Task<CommandResult> RunStackCommandAsync(
|
||||
string stackName,
|
||||
IEnumerable<string> args,
|
||||
|
|
|
@ -526,30 +526,8 @@ namespace Pulumi.Automation
|
|||
/// <summary>
|
||||
/// Gets the current set of Stack outputs from the last <see cref="UpAsync(UpOptions?, CancellationToken)"/>.
|
||||
/// </summary>
|
||||
public async Task<ImmutableDictionary<string, OutputValue>> GetOutputsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// TODO: do this in parallel after this is fixed https://github.com/pulumi/pulumi/issues/6050
|
||||
var maskedResult = await this.RunCommandAsync(new[] { "stack", "output", "--json" }, null, null, null, cancellationToken).ConfigureAwait(false);
|
||||
var plaintextResult = await this.RunCommandAsync(new[] { "stack", "output", "--json", "--show-secrets" }, null, null, null, cancellationToken).ConfigureAwait(false);
|
||||
var jsonOptions = LocalSerializer.BuildJsonSerializerOptions();
|
||||
|
||||
var maskedOutput = string.IsNullOrWhiteSpace(maskedResult.StandardOutput)
|
||||
? new Dictionary<string, object>()
|
||||
: JsonSerializer.Deserialize<Dictionary<string, object>>(maskedResult.StandardOutput, jsonOptions);
|
||||
|
||||
var plaintextOutput = string.IsNullOrWhiteSpace(plaintextResult.StandardOutput)
|
||||
? new Dictionary<string, object>()
|
||||
: JsonSerializer.Deserialize<Dictionary<string, object>>(plaintextResult.StandardOutput, jsonOptions);
|
||||
|
||||
var output = new Dictionary<string, OutputValue>();
|
||||
foreach (var (key, value) in plaintextOutput)
|
||||
{
|
||||
var secret = maskedOutput[key] is string maskedValue && maskedValue == "[secret]";
|
||||
output[key] = new OutputValue(value, secret);
|
||||
}
|
||||
|
||||
return output.ToImmutableDictionary();
|
||||
}
|
||||
public Task<ImmutableDictionary<string, OutputValue>> GetOutputsAsync(CancellationToken cancellationToken = default)
|
||||
=> this.Workspace.GetStackOutputsAsync(this.Name, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list summarizing all previews and current results from Stack lifecycle operations (up/preview/refresh/destroy).
|
||||
|
|
|
@ -446,6 +446,45 @@ func (l *LocalWorkspace) ImportStack(ctx context.Context, stackName string, stat
|
|||
return nil
|
||||
}
|
||||
|
||||
// Outputs get the current set of Stack outputs from the last Stack.Up().
|
||||
func (l *LocalWorkspace) StackOutputs(ctx context.Context, stackName string) (OutputMap, error) {
|
||||
// standard outputs
|
||||
outStdout, outStderr, code, err := l.runPulumiCmdSync(ctx, "stack", "output", "--json", "--stack", stackName)
|
||||
if err != nil {
|
||||
return nil, newAutoError(errors.Wrap(err, "could not get outputs"), outStdout, outStderr, code)
|
||||
}
|
||||
|
||||
// secret outputs
|
||||
secretStdout, secretStderr, code, err := l.runPulumiCmdSync(ctx,
|
||||
"stack", "output", "--json", "--show-secrets", "--stack", stackName,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, newAutoError(errors.Wrap(err, "could not get secret outputs"), outStdout, outStderr, code)
|
||||
}
|
||||
|
||||
var outputs map[string]interface{}
|
||||
var secrets map[string]interface{}
|
||||
|
||||
if err = json.Unmarshal([]byte(outStdout), &outputs); err != nil {
|
||||
return nil, errors.Wrapf(err, "error unmarshalling outputs: %s", secretStderr)
|
||||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(secretStdout), &secrets); err != nil {
|
||||
return nil, errors.Wrapf(err, "error unmarshalling secret outputs: %s", secretStderr)
|
||||
}
|
||||
|
||||
res := make(OutputMap)
|
||||
for k, v := range secrets {
|
||||
isSecret := outputs[k] == secretSentinel
|
||||
res[k] = OutputValue{
|
||||
Value: v,
|
||||
Secret: isSecret,
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (l *LocalWorkspace) getPulumiVersion(ctx context.Context) (semver.Version, error) {
|
||||
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "version")
|
||||
if err != nil {
|
||||
|
|
|
@ -515,43 +515,7 @@ func (s *Stack) Destroy(ctx context.Context, opts ...optdestroy.Option) (Destroy
|
|||
|
||||
// Outputs get the current set of Stack outputs from the last Stack.Up().
|
||||
func (s *Stack) Outputs(ctx context.Context) (OutputMap, error) {
|
||||
// standard outputs
|
||||
outStdout, outStderr, code, err := s.runPulumiCmdSync(ctx, nil, /* additionalOutputs */
|
||||
"stack", "output", "--json",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, newAutoError(errors.Wrap(err, "could not get outputs"), outStdout, outStderr, code)
|
||||
}
|
||||
|
||||
// secret outputs
|
||||
secretStdout, secretStderr, code, err := s.runPulumiCmdSync(ctx, nil, /* additionalOutputs */
|
||||
"stack", "output", "--json", "--show-secrets",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, newAutoError(errors.Wrap(err, "could not get secret outputs"), outStdout, outStderr, code)
|
||||
}
|
||||
|
||||
var outputs map[string]interface{}
|
||||
var secrets map[string]interface{}
|
||||
|
||||
if err = json.Unmarshal([]byte(outStdout), &outputs); err != nil {
|
||||
return nil, errors.Wrapf(err, "error unmarshalling outputs: %s", secretStderr)
|
||||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(secretStdout), &secrets); err != nil {
|
||||
return nil, errors.Wrapf(err, "error unmarshalling secret outputs: %s", secretStderr)
|
||||
}
|
||||
|
||||
res := make(OutputMap)
|
||||
for k, v := range secrets {
|
||||
isSecret := outputs[k] == secretSentinel
|
||||
res[k] = OutputValue{
|
||||
Value: v,
|
||||
Secret: isSecret,
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
return s.Workspace().StackOutputs(ctx, s.Name())
|
||||
}
|
||||
|
||||
// History returns a list summarizing all previous and current results from Stack lifecycle operations
|
||||
|
|
|
@ -105,6 +105,8 @@ type Workspace interface {
|
|||
// ImportStack imports the specified deployment state into a pre-existing stack.
|
||||
// This can be combined with ExportStack to edit a stack's state (such as recovery from failed deployments).
|
||||
ImportStack(context.Context, string, apitype.UntypedDeployment) error
|
||||
// Outputs get the current set of Stack outputs from the last Stack.Up().
|
||||
StackOutputs(context.Context, string) (OutputMap, error)
|
||||
}
|
||||
|
||||
// ConfigValue is a configuration value used by a Pulumi program.
|
||||
|
|
|
@ -22,7 +22,7 @@ import { CommandResult, runPulumiCmd } from "./cmd";
|
|||
import { ConfigMap, ConfigValue } from "./config";
|
||||
import { minimumVersion } from "./minimumVersion";
|
||||
import { ProjectSettings } from "./projectSettings";
|
||||
import { Stack } from "./stack";
|
||||
import { OutputMap, Stack } from "./stack";
|
||||
import { StackSettings, stackSettingsSerDeKeys } from "./stackSettings";
|
||||
import { Deployment, PluginInfo, PulumiFn, StackSummary, WhoAmIResult, Workspace } from "./workspace";
|
||||
|
||||
|
@ -544,6 +544,26 @@ export class LocalWorkspace implements Workspace {
|
|||
await this.runPulumiCmd(["stack", "import", "--file", filepath, "--stack", stackName]);
|
||||
fs.unlinkSync(filepath);
|
||||
}
|
||||
/**
|
||||
* Gets the current set of Stack outputs from the last Stack.up().
|
||||
* @param stackName the name of the stack.
|
||||
*/
|
||||
async stackOutputs(stackName: string): Promise<OutputMap> {
|
||||
// TODO: do this in parallel after this is fixed https://github.com/pulumi/pulumi/issues/6050
|
||||
const maskedResult = await this.runPulumiCmd(["stack", "output", "--json", "--stack", stackName]);
|
||||
const plaintextResult = await this.runPulumiCmd(["stack", "output", "--json", "--show-secrets", "--stack", stackName]);
|
||||
const maskedOuts = JSON.parse(maskedResult.stdout);
|
||||
const plaintextOuts = JSON.parse(plaintextResult.stdout);
|
||||
const outputs: OutputMap = {};
|
||||
|
||||
for (const [key, value] of Object.entries(plaintextOuts)) {
|
||||
const secret = maskedOuts[key] === "[secret]";
|
||||
outputs[key] = { value, secret };
|
||||
}
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* serializeArgsForOp is hook to provide additional args to every CLI commands before they are executed.
|
||||
* Provided with stack name,
|
||||
|
|
|
@ -30,8 +30,6 @@ import { Deployment, PulumiFn, Workspace } from "./workspace";
|
|||
|
||||
const langrpc = require("../proto/language_grpc_pb.js");
|
||||
|
||||
const secretSentinel = "[secret]";
|
||||
|
||||
/**
|
||||
* Stack is an isolated, independently configurable instance of a Pulumi program.
|
||||
* Stack exposes methods for the full pulumi lifecycle (up/preview/refresh/destroy), as well as managing configuration.
|
||||
|
@ -504,19 +502,7 @@ export class Stack {
|
|||
* Gets the current set of Stack outputs from the last Stack.up().
|
||||
*/
|
||||
async outputs(): Promise<OutputMap> {
|
||||
// TODO: do this in parallel after this is fixed https://github.com/pulumi/pulumi/issues/6050
|
||||
const maskedResult = await this.runPulumiCmd(["stack", "output", "--json"]);
|
||||
const plaintextResult = await this.runPulumiCmd(["stack", "output", "--json", "--show-secrets"]);
|
||||
const maskedOuts = JSON.parse(maskedResult.stdout);
|
||||
const plaintextOuts = JSON.parse(plaintextResult.stdout);
|
||||
const outputs: OutputMap = {};
|
||||
|
||||
for (const [key, value] of Object.entries(plaintextOuts)) {
|
||||
const secret = maskedOuts[key] === secretSentinel;
|
||||
outputs[key] = { value, secret };
|
||||
}
|
||||
|
||||
return outputs;
|
||||
return this.workspace.stackOutputs(this.name);
|
||||
}
|
||||
/**
|
||||
* Returns a list summarizing all previous and current results from Stack lifecycle operations
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
import { ConfigMap, ConfigValue } from "./config";
|
||||
import { ProjectSettings } from "./projectSettings";
|
||||
import { OutputMap } from "./stack";
|
||||
import { StackSettings } from "./stackSettings";
|
||||
|
||||
/**
|
||||
|
@ -206,6 +207,11 @@ export interface Workspace {
|
|||
* @param state the stack state to import.
|
||||
*/
|
||||
importStack(stackName: string, state: Deployment): Promise<void>;
|
||||
/**
|
||||
* Gets the current set of Stack outputs from the last Stack.up().
|
||||
* @param stackName the name of the stack.
|
||||
*/
|
||||
stackOutputs(stackName: string): Promise<OutputMap>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -146,6 +146,11 @@ from ._workspace import (
|
|||
Deployment,
|
||||
)
|
||||
|
||||
from ._output import (
|
||||
OutputMap,
|
||||
OutputValue
|
||||
)
|
||||
|
||||
from ._project_settings import (
|
||||
ProjectSettings,
|
||||
ProjectRuntimeInfo,
|
||||
|
@ -218,6 +223,10 @@ __all__ = [
|
|||
"Deployment",
|
||||
"WhoAmIResult",
|
||||
|
||||
# _output
|
||||
"OutputMap",
|
||||
"OutputValue",
|
||||
|
||||
# _project_settings
|
||||
"ProjectSettings",
|
||||
"ProjectRuntimeInfo",
|
||||
|
|
|
@ -20,11 +20,12 @@ from typing import Optional, List, Mapping, Callable
|
|||
from semver import VersionInfo
|
||||
import yaml
|
||||
|
||||
from ._config import ConfigMap, ConfigValue
|
||||
from ._config import ConfigMap, ConfigValue, _SECRET_SENTINEL
|
||||
from ._project_settings import ProjectSettings
|
||||
from ._stack_settings import StackSettings
|
||||
from ._workspace import Workspace, PluginInfo, StackSummary, WhoAmIResult, PulumiFn, Deployment
|
||||
from ._stack import _DATETIME_FORMAT, Stack
|
||||
from ._output import OutputMap, OutputValue
|
||||
from ._cmd import _run_pulumi_cmd, CommandResult, OnOutput
|
||||
from ._minimum_version import _MINIMUM_VERSION
|
||||
from .errors import InvalidVersionError
|
||||
|
@ -270,6 +271,17 @@ class LocalWorkspace(Workspace):
|
|||
self._run_pulumi_cmd_sync(["stack", "import", "--file", file.name, "--stack", stack_name])
|
||||
os.remove(file.name)
|
||||
|
||||
def stack_outputs(self, stack_name: str) -> OutputMap:
|
||||
masked_result = self._run_pulumi_cmd_sync(["stack", "output", "--json", "--stack", stack_name])
|
||||
plaintext_result = self._run_pulumi_cmd_sync(["stack", "output", "--json", "--show-secrets", "--stack", stack_name])
|
||||
masked_outputs = json.loads(masked_result.stdout)
|
||||
plaintext_outputs = json.loads(plaintext_result.stdout)
|
||||
outputs: OutputMap = {}
|
||||
for key in plaintext_outputs:
|
||||
secret = masked_outputs[key] == _SECRET_SENTINEL
|
||||
outputs[key] = OutputValue(value=plaintext_outputs[key], secret=secret)
|
||||
return outputs
|
||||
|
||||
def _get_pulumi_version(self) -> VersionInfo:
|
||||
result = self._run_pulumi_cmd_sync(["version"])
|
||||
version_string = result.stdout.strip()
|
||||
|
|
31
sdk/python/lib/pulumi/automation/_output.py
Normal file
31
sdk/python/lib/pulumi/automation/_output.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Copyright 2016-2021, Pulumi Corporation.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from typing import Any, MutableMapping
|
||||
from ._config import _SECRET_SENTINEL
|
||||
|
||||
|
||||
class OutputValue:
|
||||
value: Any
|
||||
secret: bool
|
||||
|
||||
def __init__(self, value: Any, secret: bool):
|
||||
self.value = value
|
||||
self.secret = secret
|
||||
|
||||
def __repr__(self):
|
||||
return _SECRET_SENTINEL if self.secret else repr(self.value)
|
||||
|
||||
|
||||
OutputMap = MutableMapping[str, OutputValue]
|
|
@ -24,9 +24,10 @@ from typing import List, Any, Mapping, MutableMapping, Optional, Callable, Tuple
|
|||
import grpc
|
||||
|
||||
from ._cmd import CommandResult, _run_pulumi_cmd, OnOutput
|
||||
from ._config import ConfigValue, ConfigMap, _SECRET_SENTINEL
|
||||
from ._config import ConfigValue, ConfigMap
|
||||
from .errors import StackAlreadyExistsError
|
||||
from .events import OpMap, EngineEvent, SummaryEvent
|
||||
from ._output import OutputMap
|
||||
from ._server import LanguageServer
|
||||
from ._workspace import Workspace, PulumiFn, Deployment
|
||||
from ..runtime.settings import _GRPC_CHANNEL_OPTIONS
|
||||
|
@ -48,21 +49,6 @@ class StackInitMode(Enum):
|
|||
CREATE_OR_SELECT = "create_or_select"
|
||||
|
||||
|
||||
class OutputValue:
|
||||
value: Any
|
||||
secret: bool
|
||||
|
||||
def __init__(self, value: Any, secret: bool):
|
||||
self.value = value
|
||||
self.secret = secret
|
||||
|
||||
def __repr__(self):
|
||||
return _SECRET_SENTINEL if self.secret else repr(self.value)
|
||||
|
||||
|
||||
OutputMap = MutableMapping[str, OutputValue]
|
||||
|
||||
|
||||
class UpdateSummary:
|
||||
def __init__(self,
|
||||
# pre-update info
|
||||
|
@ -515,15 +501,7 @@ class Stack:
|
|||
|
||||
:returns: OutputMap
|
||||
"""
|
||||
masked_result = self._run_pulumi_cmd_sync(["stack", "output", "--json"])
|
||||
plaintext_result = self._run_pulumi_cmd_sync(["stack", "output", "--json", "--show-secrets"])
|
||||
masked_outputs = json.loads(masked_result.stdout)
|
||||
plaintext_outputs = json.loads(plaintext_result.stdout)
|
||||
outputs: OutputMap = {}
|
||||
for key in plaintext_outputs:
|
||||
secret = masked_outputs[key] == _SECRET_SENTINEL
|
||||
outputs[key] = OutputValue(value=plaintext_outputs[key], secret=secret)
|
||||
return outputs
|
||||
return self.workspace.stack_outputs(self.name)
|
||||
|
||||
def history(self,
|
||||
page_size: Optional[int] = None,
|
||||
|
|
|
@ -25,6 +25,7 @@ from typing import (
|
|||
from ._stack_settings import StackSettings
|
||||
from ._project_settings import ProjectSettings
|
||||
from ._config import ConfigMap, ConfigValue
|
||||
from ._output import OutputMap
|
||||
|
||||
PulumiFn = Callable[[], None]
|
||||
|
||||
|
@ -360,3 +361,12 @@ class Workspace(ABC):
|
|||
:param stack_name: The name of the stack to import.
|
||||
:param state: The deployment state to import.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def stack_outputs(self, stack_name: str) -> OutputMap:
|
||||
"""
|
||||
Gets the current set of Stack outputs from the last Stack.up().
|
||||
|
||||
:param stack_name: The name of the stack.
|
||||
:returns: OutputMap
|
||||
"""
|
||||
|
|
Loading…
Reference in a new issue