/
target_profile.go
160 lines (142 loc) · 5.53 KB
/
target_profile.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// 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)
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) (string, string, string, error) {
var err error
project := targetProfile.Conn.Sp.Project
if project == "" {
project, err = utils.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 == "" {
instance, err = utils.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 == "" {
dbName, err = utils.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
}