Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions cmd/scw/testdata/test-all-usage-rdb-log-download-usage.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲
🟥🟥🟥 STDERR️️ 🟥🟥🟥️
Prepare, wait for, and download logs from a Database Instance. This command automatically prepares the logs, waits for them to be ready, and downloads them to a file.

USAGE:
scw rdb log download <instance-id ...> [arg=value ...]

EXAMPLES:
Download logs from a database instance
scw rdb log download 11111111-1111-1111-1111-111111111111

Download logs with a time range
scw rdb log download 11111111-1111-1111-1111-111111111111 from=2023-01-01T00:00:00Z to=2023-01-02T00:00:00Z

Download logs to a specific file
scw rdb log download 11111111-1111-1111-1111-111111111111 output=myLogs.txt

ARGS:
instance-id UUID of the Database Instance you want logs of
[from] Start datetime of your log. Supports absolute RFC3339 timestamps and relative times (see `scw help date`).
[to] End datetime of your log. Supports absolute RFC3339 timestamps and relative times (see `scw help date`).
[output] Filename to write to
[region=fr-par] Region to target. If none is passed will use default region from the config (fr-par | nl-ams | pl-waw)

FLAGS:
-h, --help help for download

GLOBAL FLAGS:
-c, --config string The path to the config file
-D, --debug Enable debug mode
-o, --output string Output format: json or human, see 'scw help output' for more info (default "human")
-p, --profile string The config profile to use
1 change: 1 addition & 0 deletions cmd/scw/testdata/test-all-usage-rdb-log-usage.golden
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ USAGE:
scw rdb log <command>

AVAILABLE COMMANDS:
download Download logs from a database instance
get Get given logs of a Database Instance
list List available logs of a Database Instance
list-details List remote Database Instance logs details
Expand Down
44 changes: 44 additions & 0 deletions docs/commands/rdb.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ This API allows you to manage your Managed Databases for PostgreSQL and MySQL.
- [Upgrade a Database Instance](#upgrade-a-database-instance)
- [Wait for an instance to reach a stable state](#wait-for-an-instance-to-reach-a-stable-state)
- [Instance logs management commands](#instance-logs-management-commands)
- [Download logs from a database instance](#download-logs-from-a-database-instance)
- [Get given logs of a Database Instance](#get-given-logs-of-a-database-instance)
- [List available logs of a Database Instance](#list-available-logs-of-a-database-instance)
- [List remote Database Instance logs details](#list-remote-database-instance-logs-details)
Expand Down Expand Up @@ -1039,6 +1040,49 @@ scw rdb instance wait 11111111-1111-1111-1111-111111111111
Instance logs management commands.


### Download logs from a database instance

Prepare, wait for, and download logs from a Database Instance. This command automatically prepares the logs, waits for them to be ready, and downloads them to a file.

**Usage:**

```
scw rdb log download <instance-id ...> [arg=value ...]
```


**Args:**

| Name | | Description |
|------|---|-------------|
| instance-id | Required | UUID of the Database Instance you want logs of |
| from | | Start datetime of your log. Supports absolute RFC3339 timestamps and relative times (see `scw help date`). |
| to | | End datetime of your log. Supports absolute RFC3339 timestamps and relative times (see `scw help date`). |
| output | | Filename to write to |
| region | Default: `fr-par`<br />One of: `fr-par`, `nl-ams`, `pl-waw` | Region to target. If none is passed will use default region from the config |


**Examples:**


Download logs from a database instance
```
scw rdb log download 11111111-1111-1111-1111-111111111111
```

Download logs with a time range
```
scw rdb log download 11111111-1111-1111-1111-111111111111 from=2023-01-01T00:00:00Z to=2023-01-02T00:00:00Z
```

Download logs to a specific file
```
scw rdb log download 11111111-1111-1111-1111-111111111111 output=myLogs.txt
```




### Get given logs of a Database Instance

Retrieve information about the logs of a Database Instance. Specify the `instance_log_id` and `region` in your request to get information such as `download_url`, `status`, `expires_at` and `created_at` about your logs in the response.
Expand Down
4 changes: 4 additions & 0 deletions internal/namespaces/rdb/v1/custom.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func GetCommands() *core.Commands {
human.RegisterMarshalerFunc(CreateInstanceResult{}, createInstanceResultMarshalerFunc)
human.RegisterMarshalerFunc(CustomACLResult{}, rdbACLCustomResultMarshalerFunc)
human.RegisterMarshalerFunc(rdbEndpointCustomResult{}, rdbEndpointCustomResultMarshalerFunc)
human.RegisterMarshalerFunc(logDownloadResult{}, logDownloadResultMarshalerFunc)

human.RegisterMarshalerFunc(
rdb.InstanceStatus(""),
Expand Down Expand Up @@ -81,6 +82,9 @@ func GetCommands() *core.Commands {
cmds.MustFind("rdb", "user", "update").Override(userUpdateBuilder)

cmds.MustFind("rdb", "log", "prepare").Override(logPrepareBuilder)
cmds.Merge(core.NewCommands(
logDownloadCommand(),
))

return cmds
}
223 changes: 223 additions & 0 deletions internal/namespaces/rdb/v1/custom_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@ package rdb

import (
"context"
"errors"
"fmt"
"io"
"net/url"
"os"
"path"
"reflect"
"strings"
"time"

"github.com/fatih/color"
"github.com/scaleway/scaleway-cli/v2/core"
"github.com/scaleway/scaleway-cli/v2/core/human"
"github.com/scaleway/scaleway-cli/v2/internal/interactive"
"github.com/scaleway/scaleway-sdk-go/api/rdb/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)
Expand Down Expand Up @@ -44,3 +54,216 @@ func logPrepareBuilder(c *core.Command) *core.Command {

return c
}

type logDownloadArgs struct {
InstanceID string
From *time.Time
To *time.Time
Output string
Region scw.Region
}

type logDownloadResult struct {
Size scw.Size `json:"size"`
FileName string `json:"file_name"`
}

func logDownloadResultMarshalerFunc(i any, _ *human.MarshalOpt) (string, error) {
result := i.(logDownloadResult)
sizeStr, err := human.Marshal(result.Size, nil)
if err != nil {
return "", err
}

return fmt.Sprintf(
"Log downloaded to %s successfully (%s written)",
result.FileName,
sizeStr,
), nil
}

func getLogDefaultFileName(rawURL string) (string, error) {
u, err := url.Parse(rawURL)
if err != nil {
return "", err
}
splitURL := strings.Split(u.Path, "/")
filename := splitURL[len(splitURL)-1]

return filename, nil
}

func logDownloadCommand() *core.Command {
return &core.Command{
Short: `Download logs from a database instance`,
Long: `Prepare, wait for, and download logs from a Database Instance. This command automatically prepares the logs, waits for them to be ready, and downloads them to a file.`,
Namespace: "rdb",
Resource: "log",
Verb: "download",
ArgsType: reflect.TypeOf(logDownloadArgs{}),
ArgSpecs: core.ArgSpecs{
{
Name: "instance-id",
Short: `UUID of the Database Instance you want logs of`,
Required: true,
Positional: true,
},
{
Name: "from",
Short: `Start datetime of your log. Supports absolute RFC3339 timestamps and relative times (see ` + "`scw help date`" + `).`,
Required: false,
},
{
Name: "to",
Short: `End datetime of your log. Supports absolute RFC3339 timestamps and relative times (see ` + "`scw help date`" + `).`,
Required: false,
},
{
Name: "output",
Short: "Filename to write to",
},
core.RegionArgSpec(
scw.RegionFrPar,
scw.RegionNlAms,
scw.RegionPlWaw,
),
},
Run: func(ctx context.Context, argsI any) (i any, err error) {
args := argsI.(*logDownloadArgs)
api := rdb.NewAPI(core.ExtractClient(ctx))

prepareRequest := &rdb.PrepareInstanceLogsRequest{
InstanceID: args.InstanceID,
Region: args.Region,
}
if args.From != nil {
prepareRequest.StartDate = args.From
}
if args.To != nil {
prepareRequest.EndDate = args.To
}

_, err = interactive.Print("Preparing logs... ")
if err != nil {
return nil, err
}

prepareResp, err := api.PrepareInstanceLogs(prepareRequest)
if err != nil {
return nil, err
}

if len(prepareResp.InstanceLogs) == 0 {
return nil, errors.New("no logs found for the specified time range")
}

_, err = interactive.Println("OK")
if err != nil {
return nil, err
}

_, err = interactive.Print("Waiting for logs to be ready... ")
if err != nil {
return nil, err
}

readyLogs := make([]*rdb.InstanceLog, len(prepareResp.InstanceLogs))
for i := range prepareResp.InstanceLogs {
logs, err := api.WaitForInstanceLog(&rdb.WaitForInstanceLogRequest{
InstanceLogID: prepareResp.InstanceLogs[i].ID,
Region: prepareResp.InstanceLogs[i].Region,
Timeout: scw.TimeDurationPtr(instanceActionTimeout),
RetryInterval: core.DefaultRetryInterval,
})
if err != nil {
return nil, err
}
readyLogs[i] = logs
}

_, err = interactive.Println("OK")
if err != nil {
return nil, err
}

if len(readyLogs) == 0 {
return nil, errors.New("no logs ready after waiting")
}

logToDownload := readyLogs[0]
if logToDownload.DownloadURL == nil {
return nil, errors.New("download URL is not available")
}

httpClient := core.ExtractHTTPClient(ctx)

_, err = interactive.Print("Downloading logs... ")
if err != nil {
return nil, err
}

res, err := httpClient.Get(*logToDownload.DownloadURL)
if err != nil {
return nil, err
}
defer res.Body.Close()

defaultFilename, err := getLogDefaultFileName(*logToDownload.DownloadURL)
if err != nil {
return nil, err
}
filename := defaultFilename
if args.Output != "" {
fi, err := os.Stat(args.Output)
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
filename = args.Output
} else {
switch mode := fi.Mode(); {
case mode.IsDir():
filename = path.Join(args.Output, defaultFilename)
case mode.IsRegular():
filename = args.Output
}
}
}

out, err := os.Create(filename)
if err != nil {
return nil, err
}
defer out.Close()

size, err := io.Copy(out, res.Body)
if err != nil {
return nil, err
}

_, err = interactive.Println("OK")
if err != nil {
return nil, err
}

return logDownloadResult{
Size: scw.Size(size),
FileName: filename,
}, nil
},
Examples: []*core.Example{
{
Short: "Download logs from a database instance",
ArgsJSON: `{"instance_id": "11111111-1111-1111-1111-111111111111"}`,
},
{
Short: "Download logs with a time range",
ArgsJSON: `{"instance_id": "11111111-1111-1111-1111-111111111111", "from": "2023-01-01T00:00:00Z", "to": "2023-01-02T00:00:00Z"}`,
},
{
Short: "Download logs to a specific file",
ArgsJSON: `{"instance_id": "11111111-1111-1111-1111-111111111111", "output": "myLogs.txt"}`,
},
},
}
}
Loading