forked from a2call/cli
-
Notifications
You must be signed in to change notification settings - Fork 0
/
contract.go
258 lines (242 loc) · 13.1 KB
/
contract.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
package db
import (
"io"
"github.com/Sirupsen/logrus"
"github.com/catalyzeio/gcm/gcm"
"github.com/daticahealth/cli/commands/services"
"github.com/daticahealth/cli/config"
"github.com/daticahealth/cli/lib/auth"
"github.com/daticahealth/cli/lib/crypto"
"github.com/daticahealth/cli/lib/jobs"
"github.com/daticahealth/cli/lib/prompts"
"github.com/daticahealth/cli/lib/transfer"
"github.com/daticahealth/cli/models"
"github.com/jault3/mow.cli"
)
// Cmd is the contract between the user and the CLI. This specifies the command
// name, arguments, and required/optional arguments and flags for the command.
var Cmd = models.Command{
Name: "db",
ShortHelp: "Tasks for databases",
LongHelp: "The <code>db</code> command gives access to backup, import, and export services for databases. The db command can not be run directly but has subcommands.",
CmdFunc: func(settings *models.Settings) func(cmd *cli.Cmd) {
return func(cmd *cli.Cmd) {
cmd.CommandLong(BackupSubCmd.Name, BackupSubCmd.ShortHelp, BackupSubCmd.LongHelp, BackupSubCmd.CmdFunc(settings))
cmd.CommandLong(DownloadSubCmd.Name, DownloadSubCmd.ShortHelp, DownloadSubCmd.LongHelp, DownloadSubCmd.CmdFunc(settings))
cmd.CommandLong(ExportSubCmd.Name, ExportSubCmd.ShortHelp, ExportSubCmd.LongHelp, ExportSubCmd.CmdFunc(settings))
cmd.CommandLong(ImportSubCmd.Name, ImportSubCmd.ShortHelp, ImportSubCmd.LongHelp, ImportSubCmd.CmdFunc(settings))
cmd.CommandLong(ListSubCmd.Name, ListSubCmd.ShortHelp, ListSubCmd.LongHelp, ListSubCmd.CmdFunc(settings))
cmd.CommandLong(LogsSubCmd.Name, LogsSubCmd.ShortHelp, LogsSubCmd.LongHelp, LogsSubCmd.CmdFunc(settings))
}
},
}
var BackupSubCmd = models.Command{
Name: "backup",
ShortHelp: "Create a new backup",
LongHelp: "<code>db backup</code> creates a new backup for the given database service. " +
"The backup is started and unless <code>-s</code> is specified, the CLI will poll every few seconds until it finishes. " +
"Regardless of a successful backup or not, the logs for the backup will be printed to the console when the backup is finished. " +
"If an error occurs and the logs are not printed, you can use the db logs command to print out historical backup job logs. Here is a sample command\n\n" +
"<pre>\ndatica -E \"<your_env_name>\" db backup db01\n</pre>",
CmdFunc: func(settings *models.Settings) func(cmd *cli.Cmd) {
return func(subCmd *cli.Cmd) {
databaseName := subCmd.StringArg("DATABASE_NAME", "", "The name of the database service to create a backup for (e.g. 'db01')")
skipPoll := subCmd.BoolOpt("s skip-poll", false, "Whether or not to wait for the backup to finish")
subCmd.Action = func() {
if _, err := auth.New(settings, prompts.New()).Signin(); err != nil {
logrus.Fatal(err.Error())
}
if err := config.CheckRequiredAssociation(settings); err != nil {
logrus.Fatal(err.Error())
}
err := CmdBackup(*databaseName, *skipPoll, New(settings, crypto.New(), jobs.New(settings)), services.New(settings), jobs.New(settings))
if err != nil {
logrus.Fatal(err.Error())
}
}
subCmd.Spec = "DATABASE_NAME [-s]"
}
},
}
var DownloadSubCmd = models.Command{
Name: "download",
ShortHelp: "Download a previously created backup",
LongHelp: "<code>db download</code> downloads a previously created backup to your local hard drive. " +
"Be careful using this command as it could download PHI. " +
"Be sure that all hard drive encryption and necessary precautions have been taken before performing a download. " +
"The ID of the backup is found by first running the db list command. Here is a sample command\n\n" +
"<pre>\ndatica -E \"<your_env_name>\" db download db01 cd2b4bce-2727-42d1-89e0-027bf3f1a203 ./db.sql\n</pre>\n\n" +
"This assumes you are downloading a MySQL or PostgreSQL backup which takes the <code>.sql</code> file format. If you are downloading a mongo backup, the command might look like this\n\n" +
"<pre>\ndatica -E \"<your_env_name>\" db download db01 cd2b4bce-2727-42d1-89e0-027bf3f1a203 ./db.tar.gz\n</pre>",
CmdFunc: func(settings *models.Settings) func(cmd *cli.Cmd) {
return func(subCmd *cli.Cmd) {
databaseName := subCmd.StringArg("DATABASE_NAME", "", "The name of the database service which was backed up (e.g. 'db01')")
backupID := subCmd.StringArg("BACKUP_ID", "", "The ID of the backup to download (found from \"datica backup list\")")
filePath := subCmd.StringArg("FILEPATH", "", "The location to save the downloaded backup to. This location must NOT already exist unless -f is specified")
force := subCmd.BoolOpt("f force", false, "If a file previously exists at \"filepath\", overwrite it and download the backup")
subCmd.Action = func() {
if _, err := auth.New(settings, prompts.New()).Signin(); err != nil {
logrus.Fatal(err.Error())
}
if err := config.CheckRequiredAssociation(settings); err != nil {
logrus.Fatal(err.Error())
}
err := CmdDownload(*databaseName, *backupID, *filePath, *force, New(settings, crypto.New(), jobs.New(settings)), prompts.New(), services.New(settings))
if err != nil {
logrus.Fatal(err.Error())
}
}
subCmd.Spec = "DATABASE_NAME BACKUP_ID FILEPATH [-f]"
}
},
}
var ExportSubCmd = models.Command{
Name: "export",
ShortHelp: "Export data from a database",
LongHelp: "<code>db export</code> is a simple wrapper around the <code>db backup</code> and <code>db download</code> commands. " +
"When you request an export, a backup is created that will be added to the list of backups shown when you perform the db list command. " +
"Then that backup is immediately downloaded. Regardless of a successful export or not, the logs for the backup will be printed to the console when the export is finished. " +
"If an error occurs and the logs are not printed, you can use the db logs command to print out historical backup job logs. Here is a sample command\n\n" +
"<pre>\ndatica -E \"<your_env_name>\" db export db01 ./dbexport.sql\n</pre>\n\n" +
"This assumes you are exporting a MySQL or PostgreSQL database which takes the <code>.sql</code> file format. If you are exporting a mongo database, the command might look like this\n\n" +
"<pre>\ndatica -E \"<your_env_name>\" db export db01 ./dbexport.tar.gz\n</pre>",
CmdFunc: func(settings *models.Settings) func(cmd *cli.Cmd) {
return func(subCmd *cli.Cmd) {
databaseName := subCmd.StringArg("DATABASE_NAME", "", "The name of the database to export data from (e.g. 'db01')")
filePath := subCmd.StringArg("FILEPATH", "", "The location to save the exported data. This location must NOT already exist unless -f is specified")
force := subCmd.BoolOpt("f force", false, "If a file previously exists at <code>filepath</code>, overwrite it and export data")
subCmd.Action = func() {
if _, err := auth.New(settings, prompts.New()).Signin(); err != nil {
logrus.Fatal(err.Error())
}
if err := config.CheckRequiredAssociation(settings); err != nil {
logrus.Fatal(err.Error())
}
err := CmdExport(*databaseName, *filePath, *force, New(settings, crypto.New(), jobs.New(settings)), prompts.New(), services.New(settings), jobs.New(settings))
if err != nil {
logrus.Fatal(err.Error())
}
}
subCmd.Spec = "DATABASE_NAME FILEPATH [-f]"
}
},
}
var ImportSubCmd = models.Command{
Name: "import",
ShortHelp: "Import data into a database",
LongHelp: "<code>db import</code> allows you to inject new data into your database service. For example, if you wrote a simple SQL file\n\n" +
"<pre>\nCREATE TABLE mytable (\n" +
"id TEXT PRIMARY KEY,\n" +
"val TEXT\n" +
");\n\n" +
"INSERT INTO mytable (id, val) values ('1', 'test');\n</pre>\n\n" +
"and stored it at <code>./db.sql</code> you could import this into your database service. " +
"When importing data into mongo, you may specify the database and collection to import into using the <code>-d</code> and <code>-c</code> flags respectively. " +
"Regardless of a successful import or not, the logs for the import will be printed to the console when the import is finished. " +
"Before an import takes place, your database is backed up automatically in case any issues arise. Here is a sample command\n\n" +
"<pre>\ndatica -E \"<your_env_name>\" db import db01 ./db.sql\n</pre>",
CmdFunc: func(settings *models.Settings) func(cmd *cli.Cmd) {
return func(subCmd *cli.Cmd) {
databaseName := subCmd.StringArg("DATABASE_NAME", "", "The name of the database to import data to (e.g. 'db01')")
filePath := subCmd.StringArg("FILEPATH", "", "The location of the file to import to the database")
mongoCollection := subCmd.StringOpt("c mongo-collection", "", "If importing into a mongo service, the name of the collection to import into")
mongoDatabase := subCmd.StringOpt("d mongo-database", "", "If importing into a mongo service, the name of the database to import into")
skipBackup := subCmd.BoolOpt("s skip-backup", false, "Skip backing up database. Useful for large databases, which can have long backup times.")
subCmd.Action = func() {
if _, err := auth.New(settings, prompts.New()).Signin(); err != nil {
logrus.Fatal(err.Error())
}
if err := config.CheckRequiredAssociation(settings); err != nil {
logrus.Fatal(err.Error())
}
err := CmdImport(*databaseName, *filePath, *mongoCollection, *mongoDatabase, *skipBackup, New(settings, crypto.New(), jobs.New(settings)), prompts.New(), services.New(settings), jobs.New(settings))
if err != nil {
logrus.Fatal(err.Error())
}
}
subCmd.Spec = "DATABASE_NAME FILEPATH [-s][-d [-c]]"
}
},
}
var ListSubCmd = models.Command{
Name: "list",
ShortHelp: "List created backups",
LongHelp: "<code>db list</code> lists all previously created backups. " +
"After listing backups you can copy the backup ID and use it to download that backup or view the logs from that backup. Here is a sample command\n\n" +
"<pre>\ndatica -E \"<your_env_name>\" db list db01\n</pre>",
CmdFunc: func(settings *models.Settings) func(cmd *cli.Cmd) {
return func(subCmd *cli.Cmd) {
databaseName := subCmd.StringArg("DATABASE_NAME", "", "The name of the database service to list backups for (e.g. 'db01')")
page := subCmd.IntOpt("p page", 1, "The page to view")
pageSize := subCmd.IntOpt("n page-size", 10, "The number of items to show per page")
subCmd.Action = func() {
if _, err := auth.New(settings, prompts.New()).Signin(); err != nil {
logrus.Fatal(err.Error())
}
if err := config.CheckRequiredAssociation(settings); err != nil {
logrus.Fatal(err.Error())
}
err := CmdList(*databaseName, *page, *pageSize, New(settings, crypto.New(), jobs.New(settings)), services.New(settings))
if err != nil {
logrus.Fatal(err.Error())
}
}
subCmd.Spec = "DATABASE_NAME [-p] [-n]"
}
},
}
var LogsSubCmd = models.Command{
Name: "logs",
ShortHelp: "Print out the logs from a previous database backup job",
LongHelp: "<code>db logs</code> allows you to view backup logs from historical backup jobs. " +
"You can find the backup ID from using the <code>db list</code> command. Here is a sample command\n\n" +
"<pre>\ndatica -E \"<your_env_name>\" db logs db01 cd2b4bce-2727-42d1-89e0-027bf3f1a203\n</pre>",
CmdFunc: func(settings *models.Settings) func(cmd *cli.Cmd) {
return func(subCmd *cli.Cmd) {
databaseName := subCmd.StringArg("DATABASE_NAME", "", "The name of the database service (e.g. 'db01')")
backupID := subCmd.StringArg("BACKUP_ID", "", "The ID of the backup to download logs from (found from \"datica backup list\")")
subCmd.Action = func() {
if _, err := auth.New(settings, prompts.New()).Signin(); err != nil {
logrus.Fatal(err.Error())
}
if err := config.CheckRequiredAssociation(settings); err != nil {
logrus.Fatal(err.Error())
}
err := CmdLogs(*databaseName, *backupID, New(settings, crypto.New(), jobs.New(settings)), services.New(settings), jobs.New(settings))
if err != nil {
logrus.Fatal(err.Error())
}
}
subCmd.Spec = "DATABASE_NAME BACKUP_ID"
}
},
}
// IDb
type IDb interface {
Backup(service *models.Service) (*models.Job, error)
Download(backupID, filePath string, service *models.Service) error
Export(filePath string, job *models.Job, service *models.Service) error
Import(rt *transfer.ReaderTransfer, key, iv []byte, mongoCollection, mongoDatabase string, service *models.Service) (*models.Job, error)
List(page, pageSize int, service *models.Service) (*[]models.Job, error)
TempDownloadURL(jobID string, service *models.Service) (*models.TempURL, error)
TempLogsURL(jobID string, serviceID string) (*models.TempURL, error)
DumpLogs(taskType string, job *models.Job, service *models.Service) error
NewEncryptReader(reader io.Reader, key, iv []byte) (*gcm.EncryptReader, error)
}
// SDb is a concrete implementation of IDb
type SDb struct {
Settings *models.Settings
Crypto crypto.ICrypto
Jobs jobs.IJobs
}
// New returns an instance of IDb
func New(settings *models.Settings, crypto crypto.ICrypto, jobs jobs.IJobs) IDb {
return &SDb{
Settings: settings,
Crypto: crypto,
Jobs: jobs,
}
}
func (db *SDb) NewEncryptReader(reader io.Reader, key, iv []byte) (*gcm.EncryptReader, error) {
return db.Crypto.NewEncryptReader(reader, key, iv)
}