profiles/target_profile.go (107 lines of code) (raw):

// Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package profiles import ( "fmt" "os" "strings" "time" "github.com/GoogleCloudPlatform/spanner-migration-tool/common/constants" "github.com/GoogleCloudPlatform/spanner-migration-tool/common/utils" "golang.org/x/net/context" adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1" ) type TargetProfileType int const ( TargetProfileTypeUnset = iota TargetProfileTypeConnection ) type TargetProfileConnectionType int const ( TargetProfileConnectionTypeUnset = iota TargetProfileConnectionTypeSpanner ) type TargetProfileConnectionSpanner struct { Endpoint string // Same as SPANNER_API_ENDPOINT environment variable Project string // Same as GCLOUD_PROJECT environment variable Instance string Dbname string Dialect string } type TargetProfileConnection struct { Ty TargetProfileConnectionType Sp TargetProfileConnectionSpanner } type TargetProfile struct { Ty TargetProfileType Conn TargetProfileConnection } // This expects that GetResourceIds has already been called once and the project, instance and dbName // fields in target profile are populated. func (trg TargetProfile) FetchTargetDialect(ctx context.Context) (string, error) { // TODO: consider moving all clients to target profile instead of passing them around the codebase. // Ideally we should use the client we create at the beginning, but we can fix that with the refactoring. adminClient, _ := utils.NewDatabaseAdminClient(ctx) // The parameters are irrelevant because the results are already cached when called the first time. project, instance, dbName, _ := trg.GetResourceIds(ctx, time.Now(), "", nil, &utils.GetUtilInfoImpl{}) result, err := adminClient.GetDatabase(ctx, &adminpb.GetDatabaseRequest{Name: fmt.Sprintf("projects/%s/instances/%s/databases/%s", project, instance, dbName)}) if err != nil { return "", fmt.Errorf("cannot connect to target: %v", err) } return strings.ToLower(result.DatabaseDialect.String()), nil } func (targetProfile *TargetProfile) GetResourceIds(ctx context.Context, now time.Time, driverName string, out *os.File, g utils.GetUtilInfoInterface) (string, string, string, error) { var err error project := targetProfile.Conn.Sp.Project if project == "" { project, err = g.GetProject() if err != nil { return "", "", "", fmt.Errorf("can't get project: %v", err) } targetProfile.Conn.Sp.Project = project } instance := targetProfile.Conn.Sp.Instance if instance == "" { g := utils.GetUtilInfoImpl{} instance, err = g.GetInstance(ctx, project, out) if err != nil { return "", "", "", fmt.Errorf("can't get instance: %v", err) } targetProfile.Conn.Sp.Instance = instance } dbName := targetProfile.Conn.Sp.Dbname if dbName == "" { g := utils.GetUtilInfoImpl{} dbName, err = g.GetDatabaseName(driverName, now) if err != nil { return "", "", "", fmt.Errorf("can't get database name: %v", err) } targetProfile.Conn.Sp.Dbname = dbName } return project, instance, dbName, err } // Target profile is passed as a list of key value pairs on the command line. // Today we support only direct connection as a valid target profile type, but // in future we can support writing to CSV or AVRO as valid targets. // // Among direct connection targets, today we only support Spanner database. // TargetProfileConnectionType can be extended to add more databases. // Users can specify the database dialect, instance, database name etc when // connecting to Spanner. // // Database dialect can take 2 values: GoogleSQL or PostgreSQL and the same // correspond to regular Cloud Spanner database and PG Cloud Spanner database // respectively. // // If dbName is not specified, then Spanner migration tool will autogenerate the same // and create a database with the same name. // // Example: -target-profile="instance=my-instance1,dbName=my-new-db1" // Example: -target-profile="instance=my-instance1,dbName=my-new-db1,dialect=PostgreSQL" func NewTargetProfile(s string) (TargetProfile, error) { params, err := ParseMap(s) if err != nil { return TargetProfile{}, fmt.Errorf("could not parse target profile, error = %v", err) } sp := TargetProfileConnectionSpanner{} if endpoint, ok := params["endpoint"]; ok { sp.Endpoint = endpoint } if project, ok := params["project"]; ok { sp.Project = project } if instance, ok := params["instance"]; ok { sp.Instance = instance } if dbName, ok := params["dbName"]; ok { sp.Dbname = dbName } if dialect, ok := params["dialect"]; ok { sp.Dialect = strings.ToLower(dialect) } if sp.Dialect == "" { sp.Dialect = constants.DIALECT_GOOGLESQL } else if sp.Dialect != constants.DIALECT_POSTGRESQL && sp.Dialect != constants.DIALECT_GOOGLESQL { return TargetProfile{}, fmt.Errorf("dialect not supported %v", sp.Dialect) } // if target-profile is not empty, it must contain spanner instance if s != "" && sp.Instance == "" { return TargetProfile{}, fmt.Errorf("found empty string for instance. please specify instance (spanner instance) in the target-profile") } conn := TargetProfileConnection{Ty: TargetProfileConnectionTypeSpanner, Sp: sp} return TargetProfile{Ty: TargetProfileTypeConnection, Conn: conn}, nil }