Skip to content

Commit 59b96fa

Browse files
author
Viet-Anh Duong
authored
Feat: support GCS bucket (#1)
* wip: write output state to gcs bucket Signed-off-by: vietanhduong <anh.duong@kyber.network> * supported gcs Signed-off-by: vietanhduong <anh.duong@kyber.network> * update README.md Signed-off-by: vietanhduong <anh.duong@kyber.network> --------- Signed-off-by: vietanhduong <anh.duong@kyber.network>
1 parent 4e81290 commit 59b96fa

File tree

7 files changed

+141
-184
lines changed

7 files changed

+141
-184
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,5 @@ go.work
2626
.idea/
2727
.backup-state/
2828
*.state.json
29+
30+
.vscode

README.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,78 @@
33
A tool support you pause/unpause your GCP resources which save your cost. This tool is designed for running on CI.
44

55
Basically, Pause GCP rely-on **Gcloud CLI** that means, you must install the `gcloud` command first.
6+
7+
Currently, we support `gke`, `vm` and `cloud sql` resources.
8+
9+
## Usage
10+
11+
### Pause a GKE cluster
12+
```console
13+
$ pause-gcp gke pause --help
14+
15+
Pause a GKE cluster.
16+
This command require '--location' and '--project' flags.
17+
18+
Usage:
19+
pause-gcp gke pause [CLUSTER_NAME] [flags]
20+
21+
Examples:
22+
# write output from stdout
23+
$ pause-gcp gke pause dev-cluster -l asia-southeast1 -p develop-project > output_state.json
24+
25+
# write output to gcs bucket
26+
$ pause-gcp gke pause dev-cluster -l asia-southeast -p develop-project --output-dir=gs://bucket-name/gke-states
27+
28+
# write output to a directory, pause-gcp will try to create the output dir if it not exists
29+
$ pause-gcp gke pause dev-cluster -p project --output-dir=output_states
30+
31+
# pause cluster with some except pools
32+
$ pause-gcp gke pause dev-cluster -p project --except-pools=critical-pool
33+
34+
35+
Flags:
36+
--except-pools strings except node pools
37+
-h, --help help for pause
38+
-l, --location string the cluster location (default "asia-southeast1")
39+
--output-dir gke_<project>_<location>_<cluster_name>.json the output directory to write the cluster state. If no path is specified, this will skip the write-to-file process. The output state file has named by format gke_<project>_<location>_<cluster_name>.json
40+
-p, --project string the project where contain the cluster
41+
```
42+
43+
This command will print the privous state of the input cluster after it is paused. This state is used to recover the cluster in the unpause command.
44+
45+
If the `--output-dir` is a GCS bucket (start with `gs://`), this tool will push the state file to the destination directly.
46+
47+
### Unpause a GKE cluster
48+
49+
```console
50+
$ pause-gcp gke unpause --help
51+
52+
Unpause a GKE cluster.
53+
This command requires a GKE state file which is created when you pause the cluster.
54+
55+
Usage:
56+
pause-gcp gke unpause [STATE_FILE] [flags]
57+
58+
Examples:
59+
60+
# STATE_FILE from local
61+
$ pause-gcp gke unpause ./gke-states/gke_develop_asia-southeast1_dev-cluster.state.json
62+
63+
# STATE_FILE from a gcs bucket
64+
$ pause-gcp gke unpause gs://bucket/path/json_file.json --rm
65+
66+
67+
Flags:
68+
-h, --help help for unpause
69+
--rm Remove the cluster state after complete
70+
```
71+
The input file must be the previos state of a cluster. The input file can be a GCS url (with `gs://` prefix).
72+
73+
## FAQ
74+
75+
### Why do I need this tool?
76+
This will save your money.
77+
78+
### Can I use `gcloud` CLI instead?
79+
Yes, you can. This tool builds on top of `gcloud` CLI. If you only need to turn off a VM or a cloud SQL instance,
80+
you can use cloud CLI instead of this tool. But if you need to turn off a `GKE` cluster, I recommend you use `pause-gcp b ecause turning off a cluster is more complicated than a VM and cloud SQL.

cmd/gke/pause/cmd.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ func NewCommand() *cobra.Command {
1616
Short: "Pause a GKE cluster",
1717
Long: `Pause a GKE cluster.
1818
This command require '--location' and '--project' flags.`,
19+
Example: `# write output from stdout
20+
$ pause-gcp gke pause dev-cluster -l asia-southeast1 -p develop-project > output_state.json
21+
22+
# write output to gcs bucket
23+
$ pause-gcp gke pause dev-cluster -l asia-southeast -p develop-project --output-dir=gs://bucket-name/gke-states
24+
25+
# write output to a directory, pause-gcp will try to create the output dir if it not exists
26+
$ pause-gcp gke pause dev-cluster -p project --output-dir=output_states
27+
28+
# pause cluster with some except pools
29+
$ pause-gcp gke pause dev-cluster -p project --except-pools=critical-pool
30+
`,
1931
Args: func(cmd *cobra.Command, args []string) error {
2032
if len(args) != 1 || len(args[0]) == 0 {
2133
return errors.Errorf("CLUSTER_NAME is required")

cmd/gke/pause/run.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import (
55
"github.com/pkg/errors"
66
apis "github.com/vietanhduong/pause-gcp/apis/v1"
77
"github.com/vietanhduong/pause-gcp/pkg/gcloud/gke"
8+
"github.com/vietanhduong/pause-gcp/pkg/utils/exec"
89
"github.com/vietanhduong/pause-gcp/pkg/utils/sets"
910
"google.golang.org/protobuf/encoding/protojson"
1011
"log"
1112
"os"
12-
"path"
13+
"strings"
1314
)
1415

1516
type runConfig struct {
@@ -46,12 +47,18 @@ func run(cfg runConfig) error {
4647
log.Printf("Recommend: please keep this information. You can use it to restore (unpause) your cluster.")
4748

4849
if cfg.outputDir != "" {
49-
_ = os.MkdirAll(cfg.outputDir, 0755)
50-
dst := path.Join(cfg.outputDir, fmt.Sprintf("gke_%s_%s_%s.state.json", cfg.project, cfg.location, cfg.clusterName))
51-
if err = os.WriteFile(dst, b, 0644); err != nil {
52-
return err
50+
dst := fmt.Sprintf("%s/gke_%s_%s_%s.state.json", strings.TrimSuffix(cfg.outputDir, "/"), cfg.project, cfg.location, cfg.clusterName)
51+
if strings.HasPrefix(strings.ToLower(cfg.outputDir), "gs://") {
52+
_, err = exec.Run(exec.Command("bash", "-c", fmt.Sprintf(`echo '%s' | gsutil cp -L /dev/null - %s`, string(b), dst)))
53+
if err != nil {
54+
return err
55+
}
56+
} else {
57+
_ = os.MkdirAll(cfg.outputDir, 0755)
58+
if err = os.WriteFile(dst, b, 0644); err != nil {
59+
return err
60+
}
5361
}
54-
5562
log.Printf("INFO: Cluster's state has been written to %q", dst)
5663
}
5764
log.Printf("INFO: cluster %q has been paused!", cluster.GetName())

cmd/gke/unpause/cmd.go

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package unpause
22

33
import (
4+
"log"
5+
"os"
6+
"strings"
7+
48
"github.com/pkg/errors"
59
"github.com/spf13/cobra"
610
apis "github.com/vietanhduong/pause-gcp/apis/v1"
11+
"github.com/vietanhduong/pause-gcp/pkg/utils/exec"
712
"github.com/vietanhduong/pause-gcp/pkg/utils/protoutil"
8-
"os"
913
)
1014

1115
func NewCommand() *cobra.Command {
@@ -19,17 +23,36 @@ func NewCommand() *cobra.Command {
1923
Short: "Unpause a GKE cluster",
2024
Long: `Unpause a GKE cluster.
2125
This command requires a GKE state file which is created when you pause the cluster.`,
26+
Example: `
27+
# STATE_FILE from local
28+
$ pause-gcp gke unpause ./gke-states/gke_develop_asia-southeast1_dev-cluster.state.json
29+
30+
# STATE_FILE from a gcs bucket
31+
$ pause-gcp gke unpause gs://bucket/path/json_file.json --rm
32+
`,
2233
Args: func(cmd *cobra.Command, args []string) error {
2334
if len(args) != 1 {
2435
return errors.Errorf("STATE_FILE is missing")
2536
}
2637
return nil
2738
},
2839
RunE: func(cmd *cobra.Command, args []string) error {
29-
b, err := os.ReadFile(args[0])
30-
if err != nil {
31-
return err
40+
var b []byte
41+
var err error
42+
// if the input path starts with 'gs://', that means the state is stored at GCS
43+
// and we can use gsutil cat to retrieve the file content
44+
if strings.HasPrefix(strings.ToLower(args[0]), "gs://") {
45+
var raw string
46+
if raw, err = exec.Run(exec.Command("gsutil", "cat", args[0])); err != nil {
47+
return err
48+
}
49+
b = []byte(raw)
50+
} else {
51+
if b, err = os.ReadFile(args[0]); err != nil {
52+
return err
53+
}
3254
}
55+
log.Printf("INFO: retrieve cluster state completed!\n")
3356
var cluster apis.Cluster
3457
if err = protoutil.Unmarshal(b, &cluster); err != nil {
3558
return err
@@ -38,8 +61,15 @@ This command requires a GKE state file which is created when you pause the clust
3861
if err = run(runCfg); err != nil {
3962
return err
4063
}
64+
// remote the input state file if the --rm flag exists
4165
if rm {
42-
return os.Remove(args[0])
66+
// remove the state file by gsutil if the input path start with gs://
67+
if strings.HasPrefix(strings.ToLower(args[0]), "gs://") {
68+
_, err = exec.Run(exec.Command("gsutil", "rm", args[0]))
69+
return err
70+
} else {
71+
return os.Remove(args[0])
72+
}
4373
}
4474
return nil
4575
},

cmd/main.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package main
22

33
import (
4+
"log"
5+
"os"
6+
47
"github.com/spf13/cobra"
58
"github.com/vietanhduong/pause-gcp/cmd/gke"
69
"github.com/vietanhduong/pause-gcp/cmd/sql"
710
"github.com/vietanhduong/pause-gcp/cmd/version"
811
"github.com/vietanhduong/pause-gcp/cmd/vm"
912
"github.com/vietanhduong/pause-gcp/pkg/utils/exec"
10-
"log"
11-
"os"
1213
)
1314

1415
func newRootCmd() *cobra.Command {
@@ -30,6 +31,7 @@ func main() {
3031
os.Exit(1)
3132
}
3233
}
34+
3335
func testGcloud() error {
3436
_, err := exec.Run(exec.Command("which", "gcloud"))
3537
return err

0 commit comments

Comments
 (0)