Skip to content

Commit 1eda380

Browse files
authored
fix(#241): add destroy-if-non-empty flag (#257)
Signed-off-by: Lucas Tesson <lucastesson@protonmail.com>
1 parent f2e2454 commit 1eda380

File tree

6 files changed

+80
-43
lines changed

6 files changed

+80
-43
lines changed

action.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ inputs:
2424
description: 'List of configurations tuples (<key> <value>) to pass to the Pulumi entrypoint. Does not support secrets yet.'
2525
outputs:
2626
description: 'Where to write the Pulumi stack outputs. If set to "-" will write to stdout, else to the given filename.'
27+
destroy-if-non-empty:
28+
description: 'If set to true, and the update plan is non-empty, destroy EVERYHTING first. Use with caution.'
2729

2830
runs:
2931
using: docker
@@ -39,3 +41,4 @@ runs:
3941
RESOURCES: ${{ inputs.resources }}
4042
CONFIGURATION: ${{ inputs.configuration }}
4143
OUTPUTS: ${{ inputs.outputs }}
44+
DESTROY_IF_NON_EMPTY: ${{ inputs.destroy-if-non-empty }}

cmd/victor/main.go

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,13 @@ func main() {
104104
Required: false,
105105
Sources: cli.EnvVars("OUTPUTS", "PLUGIN_OUTPUTS"),
106106
},
107+
&cli.BoolFlag{
108+
Name: "destroy-if-non-empty",
109+
Category: catPulumi,
110+
Usage: "If set to true, and the update plan is non-empty, destroy EVERYHTING first. Use with caution.",
111+
Required: false,
112+
Sources: cli.EnvVars("DESTROY_IF_NON_EMPTY", "PLUGIN_DESTROY_IF_NON_EMPTY"),
113+
},
107114
},
108115
Action: run,
109116
Authors: []any{
@@ -133,17 +140,18 @@ func main() {
133140
func run(ctx context.Context, cmd *cli.Command) error {
134141
// Build SDK arguments
135142
args := &victor.VictorArgs{
136-
Verbose: cmd.Bool("verbose"),
137-
Version: version,
138-
Statefile: cmd.String("statefile"),
139-
Username: ptrCli(cmd, "username"),
140-
Password: ptrCli(cmd, "password"),
141-
Passphrase: cmd.String("passphrase"),
142-
Context: cmd.String("context"),
143-
Server: ptrCli(cmd, "server"),
144-
Resources: cmd.StringSlice("resources"),
145-
Configuration: cmd.StringSlice("configuration"),
146-
Outputs: ptrCli(cmd, "outputs"),
143+
Verbose: cmd.Bool("verbose"),
144+
Version: version,
145+
Statefile: cmd.String("statefile"),
146+
Username: ptrCli(cmd, "username"),
147+
Password: ptrCli(cmd, "password"),
148+
Passphrase: cmd.String("passphrase"),
149+
Context: cmd.String("context"),
150+
Server: ptrCli(cmd, "server"),
151+
Resources: cmd.StringSlice("resources"),
152+
Configuration: cmd.StringSlice("configuration"),
153+
Outputs: ptrCli(cmd, "outputs"),
154+
DestroyIfNonEmpty: cmd.Bool("destroy-if-non-empty"),
147155
}
148156
return victor.Victor(ctx, args)
149157
}

http.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,15 @@ func (client *Client) Do(req *http.Request) (*http.Response, error) {
3636
)
3737
}
3838

39-
req.Header.Set("User-Agent", fmt.Sprintf("CTFer.io Victor (%s)", client.version))
39+
req.Header.Set("User-Agent", client.UserAgent())
4040
if client.username != nil && client.password != nil {
4141
bauth := fmt.Sprintf("%s:%s", *client.username, *client.password)
4242
bauth = base64.StdEncoding.EncodeToString([]byte(bauth))
4343
req.Header.Set("Authorization", fmt.Sprintf("Basic %s", bauth))
4444
}
4545
return client.sub.Do(req)
4646
}
47+
48+
func (client *Client) UserAgent() string {
49+
return fmt.Sprintf("CTFer.io Victor (%s)", client.version)
50+
}

outputs.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,25 @@ import (
66
"os"
77

88
"github.com/pulumi/pulumi/sdk/v3/go/auto"
9+
"go.uber.org/zap"
910
)
1011

1112
// ExportOutputs writes the Pulumi stack outputs to stdout or in a file.
12-
func ExportOutputs(outputs auto.OutputMap, dst string) error {
13+
func (args *VictorArgs) Export(outputs auto.OutputMap) error {
14+
if args.Outputs == nil {
15+
return nil
16+
}
17+
1318
out, _ := json.Marshal(outputs)
14-
if dst == "-" {
19+
if *args.Outputs == "-" {
1520
fmt.Printf("%s\n", out)
1621
return nil
1722
}
1823

19-
fmt.Printf("Writing outputs to %s", dst)
20-
return os.WriteFile(dst, out, 0644)
24+
if args.Verbose {
25+
Log().Info("writing outputs",
26+
zap.String("destination", *args.Outputs),
27+
)
28+
}
29+
return os.WriteFile(*args.Outputs, out, 0644)
2130
}

stack.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313

1414
// GetStack fetches the Pulumi stack state file from the provided url,
1515
// and if it does not exist, create a brand new one.
16-
func GetStack(ctx context.Context, client *Client, ws auto.Workspace, url string, verbose bool) (auto.Stack, error) {
16+
func GetStack(ctx context.Context, cli *Client, ws auto.Workspace, url string, verbose bool) (auto.Stack, error) {
1717
// TODO extract the stack from the url ? or the workspace ?
1818
stackName := "victor"
1919
logger := Log()
@@ -26,20 +26,21 @@ func GetStack(ctx context.Context, client *Client, ws auto.Workspace, url string
2626

2727
// Get state from webserver if exist
2828
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
29-
res, err := client.Do(req)
29+
res, err := cli.Do(req)
3030
if err != nil {
3131
return auto.Stack{}, errors.Wrapf(err, "while fetching %s", url)
3232
}
3333
defer func() {
3434
_ = res.Body.Close()
35-
3635
}()
36+
3737
if res.StatusCode == http.StatusNotFound {
3838
if verbose {
3939
logger.Info("stack file not found, starting from a brand new one")
4040
}
4141
return stack, nil
4242
}
43+
4344
if verbose {
4445
logger.Info("loading existing stack")
4546
}

victor.go

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,26 @@ import (
77

88
"github.com/pkg/errors"
99
"github.com/pulumi/pulumi/sdk/v3/go/auto"
10+
"github.com/pulumi/pulumi/sdk/v3/go/auto/optdestroy"
11+
"github.com/pulumi/pulumi/sdk/v3/go/auto/optrefresh"
1012
"github.com/pulumi/pulumi/sdk/v3/go/auto/optup"
1113
"go.uber.org/multierr"
1214
"go.uber.org/zap"
1315
)
1416

1517
type VictorArgs struct {
16-
Verbose bool
17-
Version string
18-
Statefile string
19-
Username *string
20-
Password *string
21-
Passphrase string
22-
Context string
23-
Server *string
24-
Resources []string
25-
Configuration []string
26-
Outputs *string
18+
Verbose bool
19+
Version string
20+
Statefile string
21+
Username *string
22+
Password *string
23+
Passphrase string
24+
Context string
25+
Server *string
26+
Resources []string
27+
Configuration []string
28+
Outputs *string
29+
DestroyIfNonEmpty bool
2730
}
2831

2932
func Victor(ctx context.Context, args *VictorArgs) error {
@@ -34,8 +37,8 @@ func Victor(ctx context.Context, args *VictorArgs) error {
3437
}
3538
logger := Log()
3639

37-
// Build Victor's client
38-
client := NewClient(args.Version, args.Verbose, args.Username, args.Password)
40+
// Build Victor's cli
41+
cli := NewClient(args.Version, args.Verbose, args.Username, args.Password)
3942

4043
// Create the workspace
4144
if args.Verbose {
@@ -69,7 +72,7 @@ func Victor(ctx context.Context, args *VictorArgs) error {
6972
if args.Verbose {
7073
logger.Info("getting the stack")
7174
}
72-
stack, err := GetStack(ctx, client, ws, args.Statefile, args.Verbose)
75+
stack, err := GetStack(ctx, cli, ws, args.Statefile, args.Verbose)
7376
if err != nil {
7477
return err
7578
}
@@ -86,35 +89,44 @@ func Victor(ctx context.Context, args *VictorArgs) error {
8689
}
8790

8891
// Refresh and update
89-
upopts := []optup.Option{}
92+
upopts := []optup.Option{
93+
optup.UserAgent(cli.UserAgent()),
94+
}
9095
if args.Verbose {
9196
logger.Info("refreshing and updating Pulumi stack")
9297
zw := &ZapWriter{logger: logger}
9398
upopts = append(upopts, optup.ProgressStreams(zw))
9499
}
95-
_, err = stack.Refresh(ctx)
100+
101+
// Make sure to properly track resources and changes
102+
refRes, err := stack.Refresh(ctx, optrefresh.UserAgent(cli.UserAgent()))
96103
if err != nil {
97104
return errors.Wrap(err, "refreshing Pulumi stack")
98105
}
106+
if refRes.Summary.ResourceChanges != nil && len(*refRes.Summary.ResourceChanges) != 0 {
107+
if _, err := stack.Destroy(ctx, optdestroy.UserAgent(cli.UserAgent())); err != nil {
108+
return errors.Wrap(err, "destroying Pulumi stack ahead of update")
109+
}
110+
}
111+
112+
// Up(date) the stack
99113
res, err := stack.Up(ctx, upopts...)
100114
if err != nil {
101115
return errors.Wrap(err, "stack up")
102116
}
103117

104118
// Export outputs
105-
if args.Outputs != nil {
106-
if err := ExportOutputs(res.Outputs, *args.Outputs); err != nil {
107-
logger.Error("exporting outputs",
108-
zap.Error(err),
109-
)
110-
}
119+
if err := args.Export(res.Outputs); err != nil {
120+
logger.Error("exporting outputs",
121+
zap.Error(err),
122+
)
111123
}
112124

113125
// Export stack
114126
if args.Verbose {
115-
logger.Info("pushing the stack")
127+
logger.Info("pushing stack")
116128
}
117-
return PushStack(ctx, client, stack, args.Statefile)
129+
return PushStack(ctx, cli, stack, args.Statefile)
118130
}
119131

120132
type ZapWriter struct {

0 commit comments

Comments
 (0)