Skip to content

Commit d019643

Browse files
authored
Export support ali cluster and OSS target (#275)
1 parent 719798a commit d019643

14 files changed

+860
-30
lines changed

Diff for: docs/generate_doc/ticloud_serverless_export_create.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,16 @@ ticloud serverless export create [flags]
4646
--gcs.service-account-key string The base64 encoded service account key of GCS.
4747
--gcs.uri string The GCS URI in gs://<bucket>/<path> format. Required when target type is GCS.
4848
-h, --help help for create
49+
--oss.access-key-id string The access key ID of the OSS.
50+
--oss.access-key-secret string The access key secret of the OSS.
51+
--oss.uri string The OSS URI in oss://<bucket>/<path> format. Required when target type is OSS.
4952
--parquet.compression string The parquet compression algorithm. One of ["GZIP" "SNAPPY" "ZSTD" "NONE"]. (default "ZSTD")
5053
--s3.access-key-id string The access key ID of the S3. You only need to set one of the s3.role-arn and [s3.access-key-id, s3.secret-access-key].
5154
--s3.role-arn string The role arn of the S3. You only need to set one of the s3.role-arn and [s3.access-key-id, s3.secret-access-key].
5255
--s3.secret-access-key string The secret access key of the S3. You only need to set one of the s3.role-arn and [s3.access-key-id, s3.secret-access-key].
5356
--s3.uri string The S3 URI in s3://<bucket>/<path> format. Required when target type is S3.
5457
--sql string Filter the exported data with SQL SELECT statement.
55-
--target-type string The export target. One of ["LOCAL" "S3" "GCS" "AZURE_BLOB"]. (default "LOCAL")
58+
--target-type string The export target. One of ["LOCAL" "S3" "GCS" "AZURE_BLOB" "OSS"]. (default "LOCAL")
5659
--where string Filter the exported table(s) with the where condition.
5760
```
5861

Diff for: internal/cli/serverless/export/create.go

+74-16
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ func (c CreateOpts) NonInteractiveFlags() []string {
7070
flag.AzureBlobSASToken,
7171
flag.ParquetCompression,
7272
flag.DisplayName,
73+
flag.OSSURI,
74+
flag.OSSAccessKeyID,
75+
flag.OSSAccessKeySecret,
7376
}
7477
}
7578

@@ -155,6 +158,8 @@ func CreateCmd(h *internal.Helper) *cobra.Command {
155158
var gcsURI, gcsServiceAccountKey string
156159
// azure
157160
var azBlobURI, azBlobSasToken string
161+
// oss
162+
var ossURI, ossAccessKeyID, ossAccessKeySecret string
158163
var displayName string
159164

160165
if opts.interactive {
@@ -187,28 +192,50 @@ func CreateCmd(h *internal.Helper) *cobra.Command {
187192
if err != nil {
188193
return err
189194
}
190-
selectedAuthType, err := GetSelectedAuthType(targetType)
195+
selectedAuthType, err := GetSelectedAuthType(targetType, *cluster.CloudProvider)
191196
if err != nil {
192197
return err
193198
}
194199
switch selectedAuthType {
200+
// Both S3 and OSS supports ACCESS_KEY
195201
case string(export.EXPORTS3AUTHTYPEENUM_ACCESS_KEY):
196-
inputs := []string{flag.S3URI, flag.S3AccessKeyID, flag.S3SecretAccessKey}
197-
textInput, err := ui.InitialInputModel(inputs, inputDescription)
198-
if err != nil {
199-
return err
200-
}
201-
s3URI = textInput.Inputs[0].Value()
202-
if s3URI == "" {
203-
return errors.New("empty S3 URI")
204-
}
205-
accessKeyID = textInput.Inputs[1].Value()
206-
if accessKeyID == "" {
207-
return errors.New("empty S3 access key Id")
202+
if targetType == export.EXPORTTARGETTYPEENUM_S3 {
203+
inputs := []string{flag.S3URI, flag.S3AccessKeyID, flag.S3SecretAccessKey}
204+
textInput, err := ui.InitialInputModel(inputs, inputDescription)
205+
if err != nil {
206+
return err
207+
}
208+
s3URI = textInput.Inputs[0].Value()
209+
if s3URI == "" {
210+
return errors.New("empty S3 URI")
211+
}
212+
accessKeyID = textInput.Inputs[1].Value()
213+
if accessKeyID == "" {
214+
return errors.New("empty S3 access key Id")
215+
}
216+
secretAccessKey = textInput.Inputs[2].Value()
217+
if secretAccessKey == "" {
218+
return errors.New("empty S3 secret access key")
219+
}
208220
}
209-
secretAccessKey = textInput.Inputs[2].Value()
210-
if secretAccessKey == "" {
211-
return errors.New("empty S3 secret access key")
221+
if targetType == export.EXPORTTARGETTYPEENUM_OSS {
222+
inputs := []string{flag.OSSURI, flag.OSSAccessKeyID, flag.OSSAccessKeySecret}
223+
textInput, err := ui.InitialInputModel(inputs, inputDescription)
224+
if err != nil {
225+
return err
226+
}
227+
ossURI = textInput.Inputs[0].Value()
228+
if ossURI == "" {
229+
return errors.New("empty OSS URI")
230+
}
231+
ossAccessKeyID = textInput.Inputs[1].Value()
232+
if ossAccessKeyID == "" {
233+
return errors.New("empty OSS access key Id")
234+
}
235+
ossAccessKeySecret = textInput.Inputs[2].Value()
236+
if ossAccessKeySecret == "" {
237+
return errors.New("empty OSS access key secret")
238+
}
212239
}
213240
case string(export.EXPORTS3AUTHTYPEENUM_ROLE_ARN):
214241
inputs := []string{flag.S3URI, flag.S3RoleArn}
@@ -463,6 +490,25 @@ func CreateCmd(h *internal.Helper) *cobra.Command {
463490
if azBlobSasToken == "" {
464491
return errors.New("Azure Blob SAS token is required when target type is AZURE_BLOB")
465492
}
493+
case export.EXPORTTARGETTYPEENUM_OSS:
494+
ossURI, err = cmd.Flags().GetString(flag.OSSURI)
495+
if err != nil {
496+
return errors.Trace(err)
497+
}
498+
if ossURI == "" {
499+
return errors.New("OSS URI is required when target type is OSS")
500+
}
501+
ossAccessKeyID, err = cmd.Flags().GetString(flag.OSSAccessKeyID)
502+
if err != nil {
503+
return errors.Trace(err)
504+
}
505+
ossAccessKeySecret, err = cmd.Flags().GetString(flag.OSSAccessKeySecret)
506+
if err != nil {
507+
return errors.Trace(err)
508+
}
509+
if ossAccessKeyID == "" || ossAccessKeySecret == "" {
510+
return errors.New("OSS access key id and access key secret are required when target type is OSS")
511+
}
466512
}
467513

468514
compressionStr, err := cmd.Flags().GetString(flag.Compression)
@@ -603,6 +649,15 @@ func CreateCmd(h *internal.Helper) *cobra.Command {
603649
AuthType: export.EXPORTAZUREBLOBAUTHTYPEENUM_SAS_TOKEN,
604650
SasToken: &azBlobSasToken,
605651
}
652+
case export.EXPORTTARGETTYPEENUM_OSS:
653+
params.Target.Oss = &export.OSSTarget{
654+
Uri: ossURI,
655+
AuthType: export.EXPORTOSSAUTHTYPEENUM_ACCESS_KEY,
656+
AccessKey: &export.OSSTargetAccessKey{
657+
Id: ossAccessKeyID,
658+
Secret: ossAccessKeySecret,
659+
},
660+
}
606661
}
607662
// add compression
608663
if compression != "" {
@@ -669,6 +724,9 @@ func CreateCmd(h *internal.Helper) *cobra.Command {
669724
createCmd.Flags().String(flag.GCSServiceAccountKey, "", "The base64 encoded service account key of GCS.")
670725
createCmd.Flags().String(flag.AzureBlobURI, "", "The Azure Blob URI in azure://<account>.blob.core.windows.net/<container>/<path> format. Required when target type is AZURE_BLOB.")
671726
createCmd.Flags().String(flag.AzureBlobSASToken, "", "The SAS token of Azure Blob.")
727+
createCmd.Flags().String(flag.OSSURI, "", "The OSS URI in oss://<bucket>/<path> format. Required when target type is OSS.")
728+
createCmd.Flags().String(flag.OSSAccessKeyID, "", "The access key ID of the OSS.")
729+
createCmd.Flags().String(flag.OSSAccessKeySecret, "", "The access key secret of the OSS.")
672730
createCmd.Flags().String(flag.ParquetCompression, "ZSTD", fmt.Sprintf("The parquet compression algorithm. One of %q.", export.AllowedExportParquetCompressionTypeEnumEnumValues))
673731
createCmd.Flags().String(flag.DisplayName, "", "The display name of the export. (default \"SNAPSHOT_<snapshot_time>\")")
674732

Diff for: internal/cli/serverless/export/create_test.go

+80
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,86 @@ func (suite *CreateExportSuite) TestCreateExportWithTableFilter() {
373373
suite.AssertTest(ctx, tests)
374374
}
375375

376+
func (suite *CreateExportSuite) TestCreateExportToOSS() {
377+
ctx := context.Background()
378+
379+
clusterId := "fake-cluster-id"
380+
exportId := "fake-export-id"
381+
targetType := export.EXPORTTARGETTYPEENUM_OSS
382+
uri := "s3://fake-bucket/fake-prefix"
383+
accessKeyId := "fake-id"
384+
secretAccess := "fake-secret"
385+
386+
body := getDefaultCreateExportBody()
387+
body.Target = &export.ExportTarget{
388+
Type: &targetType,
389+
Oss: &export.OSSTarget{
390+
Uri: uri,
391+
AuthType: export.EXPORTOSSAUTHTYPEENUM_ACCESS_KEY,
392+
AccessKey: &export.OSSTargetAccessKey{
393+
Id: accessKeyId,
394+
Secret: secretAccess,
395+
},
396+
},
397+
}
398+
suite.mockClient.On("CreateExport", ctx, clusterId, body).
399+
Return(&export.Export{
400+
ExportId: &exportId,
401+
}, nil)
402+
403+
tests := []Test{
404+
{
405+
name: "export data to oss without uri",
406+
args: []string{"-c", clusterId, "--target-type", "OSS", "--oss.access-key-id", accessKeyId, "--oss.access-key-secret", secretAccess, "--force"},
407+
err: errors.New("OSS URI is required when target type is OSS"),
408+
},
409+
{
410+
name: "export data to oss without auth",
411+
args: []string{"-c", clusterId, "--target-type", "OSS", "--oss.uri", uri, "--oss.access-key-id", accessKeyId, "--force"},
412+
err: errors.New("OSS access key id and access key secret are required when target type is OSS"),
413+
},
414+
{
415+
name: "export all data to oss using access key",
416+
args: []string{"-c", clusterId, "--target-type", "OSS", "--oss.uri", uri, "--oss.access-key-id", accessKeyId, "--oss.access-key-secret", secretAccess, "--force"},
417+
stdoutString: fmt.Sprintf("export %s is running now\n", exportId),
418+
},
419+
}
420+
suite.AssertTest(ctx, tests)
421+
}
422+
423+
func (suite *CreateExportSuite) TestAliClusterExportToS3WithRoleArn() {
424+
ctx := context.Background()
425+
426+
clusterId := "fake-cluster-id"
427+
targetType := export.EXPORTTARGETTYPEENUM_S3
428+
uri := "s3://fake-bucket/fake-prefix"
429+
roleArn := "fake-role-arn"
430+
431+
body := getDefaultCreateExportBody()
432+
body.Target = &export.ExportTarget{
433+
Type: &targetType,
434+
S3: &export.S3Target{
435+
Uri: &uri,
436+
AuthType: export.EXPORTS3AUTHTYPEENUM_ROLE_ARN,
437+
RoleArn: &roleArn,
438+
},
439+
}
440+
441+
errMsg := "export to s3 with role arn is not supported in alicloud"
442+
443+
suite.mockClient.On("CreateExport", ctx, clusterId, body).
444+
Return(nil, errors.New(errMsg))
445+
446+
tests := []Test{
447+
{
448+
name: "export ali cluster with S3 role arn",
449+
args: []string{"-c", clusterId, "--target-type", "S3", "--s3.uri", uri, "--s3.role-arn", roleArn, "--force"},
450+
err: errors.New(errMsg),
451+
},
452+
}
453+
suite.AssertTest(ctx, tests)
454+
}
455+
376456
func getDefaultCreateExportBody() *export.ExportServiceCreateExportBody {
377457
defaultFileType := export.EXPORTFILETYPEENUM_CSV
378458
defaultTargetType := export.EXPORTTARGETTYPEENUM_LOCAL

Diff for: internal/cli/serverless/export/ui.go

+17-6
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
package export
1616

1717
import (
18+
"github.com/charmbracelet/bubbles/textinput"
19+
tea "github.com/charmbracelet/bubbletea"
20+
"github.com/juju/errors"
1821
"github.com/tidbcloud/tidbcloud-cli/internal/config"
1922
"github.com/tidbcloud/tidbcloud-cli/internal/flag"
2023
"github.com/tidbcloud/tidbcloud-cli/internal/ui"
2124
"github.com/tidbcloud/tidbcloud-cli/internal/util"
25+
"github.com/tidbcloud/tidbcloud-cli/pkg/tidbcloud/v1beta1/serverless/cluster"
2226
"github.com/tidbcloud/tidbcloud-cli/pkg/tidbcloud/v1beta1/serverless/export"
23-
24-
"github.com/charmbracelet/bubbles/textinput"
25-
tea "github.com/charmbracelet/bubbletea"
26-
"github.com/juju/errors"
2727
)
2828

2929
var inputDescription = map[string]string{
@@ -43,6 +43,9 @@ var inputDescription = map[string]string{
4343
flag.CSVNullValue: "Input the CSV null value: representation of null values in CSV files, skip to use default value (\\N). If you want to set empty string, please use non-interactive mode",
4444
flag.CSVSkipHeader: "Input the CSV skip header: export CSV files of the tables without header. Type `true` to skip header, others will not skip header",
4545
flag.DisplayName: "Input the name of export. You can skip and use the default name SNAPSHOT_<snapshot_time> by pressing Enter",
46+
flag.OSSURI: "Input your OSS URI in oss://<bucket>/<path> format",
47+
flag.OSSAccessKeyID: "Input your OSS access key id",
48+
flag.OSSAccessKeySecret: "Input your OSS access key secret",
4649
}
4750

4851
func GetSelectedParquetCompression() (export.ExportParquetCompressionTypeEnum, error) {
@@ -70,7 +73,9 @@ func GetSelectedParquetCompression() (export.ExportParquetCompressionTypeEnum, e
7073

7174
func GetSelectedTargetType() (export.ExportTargetTypeEnum, error) {
7275
targetTypes := make([]interface{}, 0, 4)
73-
targetTypes = append(targetTypes, export.EXPORTTARGETTYPEENUM_LOCAL, export.EXPORTTARGETTYPEENUM_S3, export.EXPORTTARGETTYPEENUM_GCS, export.EXPORTTARGETTYPEENUM_AZURE_BLOB)
76+
for _, v := range export.AllowedExportTargetTypeEnumEnumValues {
77+
targetTypes = append(targetTypes, v)
78+
}
7479
model, err := ui.InitialSelectModel(targetTypes, "Choose the export target:")
7580
if err != nil {
7681
return "", errors.Trace(err)
@@ -91,22 +96,28 @@ func GetSelectedTargetType() (export.ExportTargetTypeEnum, error) {
9196
return targetType.(export.ExportTargetTypeEnum), nil
9297
}
9398

94-
func GetSelectedAuthType(target export.ExportTargetTypeEnum) (_ string, err error) {
99+
func GetSelectedAuthType(target export.ExportTargetTypeEnum, provider cluster.V1beta1RegionCloudProvider) (_ string, err error) {
95100
var model *ui.SelectModel
96101
switch target {
97102
case export.EXPORTTARGETTYPEENUM_S3:
103+
if provider != cluster.V1BETA1REGIONCLOUDPROVIDER_AWS {
104+
return string(export.EXPORTS3AUTHTYPEENUM_ACCESS_KEY), nil
105+
}
98106
authTypes := make([]interface{}, 0, 2)
99107
authTypes = append(authTypes, string(export.EXPORTS3AUTHTYPEENUM_ROLE_ARN), string(export.EXPORTS3AUTHTYPEENUM_ACCESS_KEY))
100108
model, err = ui.InitialSelectModel(authTypes, "Choose and input the S3 auth:")
101109
if err != nil {
102110
return "", errors.Trace(err)
103111
}
112+
104113
case export.EXPORTTARGETTYPEENUM_GCS:
105114
return string(export.EXPORTGCSAUTHTYPEENUM_SERVICE_ACCOUNT_KEY), nil
106115
case export.EXPORTTARGETTYPEENUM_AZURE_BLOB:
107116
return string(export.EXPORTAZUREBLOBAUTHTYPEENUM_SAS_TOKEN), nil
108117
case export.EXPORTTARGETTYPEENUM_LOCAL:
109118
return "", nil
119+
case export.EXPORTTARGETTYPEENUM_OSS:
120+
return string(export.EXPORTOSSAUTHTYPEENUM_ACCESS_KEY), nil
110121
}
111122
if model == nil {
112123
return "", errors.New("unknown auth type")

0 commit comments

Comments
 (0)