-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add data import/export commands #156
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package data | ||
|
||
import ( | ||
"github.com/spf13/cobra" | ||
data_export "github.com/zitadel/zitadel-tools/cmd/data/export" | ||
data_import "github.com/zitadel/zitadel-tools/cmd/data/import" | ||
) | ||
|
||
// Cmd represents the data root command | ||
var Cmd = &cobra.Command{ | ||
Use: "data", | ||
Short: "Import/Export data", | ||
} | ||
|
||
func init() { | ||
issuer := Cmd.PersistentFlags().String("issuer", "", "issuer of your ZITADEL instance (in the form: https://<instance>.zitadel.cloud or https://<yourdomain>)") | ||
api := Cmd.PersistentFlags().String("api", "", "gRPC endpoint of your ZITADEL instance (in the form: <instance>.zitadel.cloud:443 or <yourdomain>:443)") | ||
insecure := Cmd.PersistentFlags().Bool("insecure", false, "disable TLS to connect to gRPC API (use for local development only)") | ||
keyPath := Cmd.PersistentFlags().String("key", "", "path to the JSON machine key") | ||
|
||
Cmd.MarkPersistentFlagRequired("issuer") | ||
Cmd.MarkPersistentFlagRequired("api") | ||
Cmd.MarkPersistentFlagRequired("key") | ||
|
||
Cmd.AddCommand(data_import.Cmd(issuer, api, insecure, keyPath)) | ||
Cmd.AddCommand(data_export.Cmd(issuer, api, insecure, keyPath)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package data_export | ||
|
||
import ( | ||
"context" | ||
"log" | ||
"os" | ||
|
||
"github.com/spf13/cobra" | ||
"github.com/zitadel/zitadel-go/v2/pkg/client/admin" | ||
"github.com/zitadel/zitadel-go/v2/pkg/client/middleware" | ||
"github.com/zitadel/zitadel-go/v2/pkg/client/zitadel" | ||
pb "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/admin" | ||
"google.golang.org/protobuf/encoding/protojson" | ||
) | ||
|
||
// Cmd represents the export command | ||
var Cmd = func(issuer *string, api *string, insecure *bool, keyPath *string) *cobra.Command { | ||
var dataPath string | ||
|
||
cmd := &cobra.Command{ | ||
Use: "export", | ||
Short: "Export data from an instance", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
exportData(*issuer, *api, *insecure, *keyPath, dataPath) | ||
}, | ||
} | ||
|
||
cmd.Flags().StringVar(&dataPath, "data", "", "path to the file where exported data will be written") | ||
cmd.MarkFlagRequired("data") | ||
|
||
return cmd | ||
} | ||
|
||
func exportData(issuer string, api string, insecure bool, keyPath string, dataPath string) { | ||
opts := []zitadel.Option{ | ||
zitadel.WithJWTProfileTokenSource(middleware.JWTProfileFromPath(keyPath)), | ||
} | ||
|
||
if insecure { | ||
opts = append(opts, zitadel.WithInsecure()) | ||
} | ||
|
||
client, err := admin.NewClient( | ||
issuer, | ||
api, | ||
[]string{zitadel.ScopeZitadelAPI()}, | ||
opts..., | ||
) | ||
|
||
if err != nil { | ||
log.Fatalln("failed to create admin client:", err) | ||
return | ||
} | ||
|
||
defer func() { | ||
if err := client.Connection.Close(); err != nil { | ||
log.Fatalln("failed to close client connection:", err) | ||
} | ||
}() | ||
|
||
resp, err := client.ExportData(context.Background(), &pb.ExportDataRequest{}) | ||
|
||
if err != nil { | ||
log.Fatalln("failed to export data:", err) | ||
return | ||
} | ||
|
||
data, err := protojson.Marshal(resp) | ||
|
||
if err != nil { | ||
log.Fatalln("failed to marshal data:", err) | ||
return | ||
} | ||
|
||
err = os.WriteFile(dataPath, data, 0644) | ||
|
||
if err != nil { | ||
log.Fatalln("failed to write data file:", err) | ||
return | ||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using the exported json file, the import fails with:
That's because the Request has a Oneof field that has to be populated first. I'll make a suggestion below in the file. |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,86 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
package data_import | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
"context" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
"log" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
"os" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/spf13/cobra" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/zitadel/zitadel-go/v2/pkg/client/admin" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/zitadel/zitadel-go/v2/pkg/client/middleware" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/zitadel/zitadel-go/v2/pkg/client/zitadel" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
pb "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/admin" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
"google.golang.org/protobuf/encoding/protojson" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
// Cmd represents the import command | ||||||||||||||||||||||||||||||||||||||||||||||||||||
var Cmd = func(issuer *string, api *string, insecure *bool, keyPath *string) *cobra.Command { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
var dataPath string | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
cmd := &cobra.Command{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
Use: "import", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
Short: "Import data to an instance", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
Run: func(cmd *cobra.Command, args []string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
importData(*issuer, *api, *insecure, *keyPath, dataPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
cmd.Flags().StringVar(&dataPath, "data", "", "path to the file containing data to import") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
cmd.MarkFlagRequired("data") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
return cmd | ||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
func importData(issuer string, api string, insecure bool, keyPath string, dataPath string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
opts := []zitadel.Option{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
zitadel.WithJWTProfileTokenSource(middleware.JWTProfileFromPath(keyPath)), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
if insecure { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
opts = append(opts, zitadel.WithInsecure()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
client, err := admin.NewClient( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
issuer, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
api, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
[]string{zitadel.ScopeZitadelAPI()}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
opts..., | ||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
log.Fatalln("failed to create admin client:", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
defer func() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
if err := client.Connection.Close(); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
log.Fatalln("failed to close client connection:", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
}() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
data, err := os.ReadFile(dataPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
log.Fatalln("failed to read data file:", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
var req pb.ImportDataRequest | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
err = protojson.Unmarshal(data, &req) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
log.Fatalln("failed to unmarshal data:", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
resp, err := client.ImportData(context.Background(), &req) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+68
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the propper message type for unmarshall like this:
Suggested change
Timeout is a required field and will need to be parsed from the command line arguments as well. |
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
log.Fatalln("failed to import data:", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
log.Println("Success: ", resp.Success) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
log.Println("Errors: ", resp.Errors) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel that these 3 command line options are quite redundant.
For example:
http://localhost:8080
, you already know the API endpoint islocalhost:8080
and the connection is insecure.https://<instance>.zitadel.cloud
, you already know the API endpoint is<instance>.zitadel.cloud:443
and the connection is secure.For this example you'll need to parse the URL of the issuer and imply the the API configuration from there.
The reverse can also be applied:
--host
flag can point to the API and will become part of the issuer--insecure
flag toggleshttps
tohttp
in front of the issuer. The API port toggles between443
and80
by default.--port
flag sets / overrides the port suffix to the host for the issuer and API.In the last example, if one would only set
<instance>.zitadel.cloud
, the default will create issuerhttps://<instance>.zitadel.cloud
and API endpoint<instance>.zitadel.cloud:443
.For user friendlyness I would want to stick to one of the above solutions, you may pick one.