├── main.go ├── cmd ├── admin.go ├── query.go ├── admin_tasks.go ├── query_archive.go ├── query_fork.go ├── query_show.go ├── root.go ├── query_create.go ├── query_list.go ├── util.go └── query_modify.go ├── scripts └── backup_queries.py ├── LICENSE ├── README.md └── redash └── client.go /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/ariarijp/redashman/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /cmd/admin.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var adminCmd = &cobra.Command{ 8 | Use: "admin", 9 | } 10 | 11 | func init() { 12 | RootCmd.AddCommand(adminCmd) 13 | } 14 | -------------------------------------------------------------------------------- /cmd/query.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/franela/goreq" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var queryCmd = &cobra.Command{ 11 | Use: "query", 12 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 13 | connTimeout, _ := RootCmd.PersistentFlags().GetUint("timeout") 14 | goreq.SetConnectTimeout(time.Duration(connTimeout) * time.Millisecond) 15 | }, 16 | } 17 | 18 | func init() { 19 | RootCmd.AddCommand(queryCmd) 20 | } 21 | -------------------------------------------------------------------------------- /cmd/admin_tasks.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/ariarijp/redashman/redash" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var adminTasksCmd = &cobra.Command{ 11 | Use: "tasks", 12 | Short: "Show tasks", 13 | Run: func(cmd *cobra.Command, args []string) { 14 | redashUrl, apiKey, err := getRequiredFlags() 15 | checkError(err) 16 | 17 | queryStrings := getDefaultQueryStrings(*apiKey) 18 | 19 | res, err := redash.GetTasks(*redashUrl, queryStrings) 20 | checkError(err) 21 | checkStatusCode(res) 22 | 23 | body, err := res.Body.ToString() 24 | checkError(err) 25 | 26 | fmt.Println(body) 27 | }, 28 | } 29 | 30 | func init() { 31 | adminCmd.AddCommand(adminTasksCmd) 32 | } 33 | -------------------------------------------------------------------------------- /scripts/backup_queries.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from __future__ import print_function 3 | 4 | import json 5 | import os 6 | import subprocess 7 | 8 | REDASH_API_KEY = os.environ.get('REDASH_API_KEY') 9 | REDASH_URL = os.environ.get('REDASH_URL') 10 | BACKUP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'backup') 11 | PAGE_SIZE = 50 12 | 13 | page = 1 14 | 15 | if not os.path.exists(BACKUP_DIR): 16 | os.mkdir(BACKUP_DIR) 17 | 18 | while True: 19 | cmd = 'redashman query list %d %d --api-key=%s --url=%s --json' % (PAGE_SIZE, page, REDASH_API_KEY, REDASH_URL) 20 | stdout, stderr = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate() 21 | if stderr: 22 | break 23 | 24 | queries = json.loads(stdout) 25 | for query in queries['results']: 26 | backup_file_path = '%s/%04d.sql' % (BACKUP_DIR, query['id']) 27 | with open(backup_file_path, 'w') as f: 28 | f.write(query['query']) 29 | 30 | page += 1 31 | -------------------------------------------------------------------------------- /cmd/query_archive.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | 8 | "github.com/Songmu/prompter" 9 | "github.com/ariarijp/redashman/redash" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var queryArchiveCmd = &cobra.Command{ 14 | Use: "archive [id]", 15 | Short: "Archive a query", 16 | Args: cobra.ExactArgs(1), 17 | Run: func(cmd *cobra.Command, args []string) { 18 | redashUrl, apiKey, err := getRequiredFlags() 19 | checkError(err) 20 | force, _ := cmd.Flags().GetBool("force") 21 | 22 | id, err := strconv.Atoi(args[0]) 23 | checkError(err) 24 | 25 | queryStrings := getDefaultQueryStrings(*apiKey) 26 | 27 | if !force && !prompter.YN("Are you sure you want to archive this query?", false) { 28 | os.Exit(1) 29 | } 30 | 31 | res, err := redash.ArchiveQuery(*redashUrl, id, queryStrings) 32 | checkError(err) 33 | checkStatusCode(res) 34 | 35 | fmt.Println(res.Status) 36 | }, 37 | } 38 | 39 | func init() { 40 | queryArchiveCmd.Flags().BoolP("force", "f", false, "Run without asking") 41 | queryCmd.AddCommand(queryArchiveCmd) 42 | } 43 | -------------------------------------------------------------------------------- /cmd/query_fork.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | 8 | "github.com/Songmu/prompter" 9 | "github.com/ariarijp/redashman/redash" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var queryForkCmd = &cobra.Command{ 14 | Use: "fork [id]", 15 | Short: "Fork a query from an existing one", 16 | Args: cobra.ExactArgs(1), 17 | Run: func(cmd *cobra.Command, args []string) { 18 | redashUrl, apiKey, err := getRequiredFlags() 19 | checkError(err) 20 | force, _ := cmd.Flags().GetBool("force") 21 | 22 | id, err := strconv.Atoi(args[0]) 23 | checkError(err) 24 | 25 | queryStrings := getDefaultQueryStrings(*apiKey) 26 | 27 | if !force && !prompter.YN("Are you sure you want to fork this query?", false) { 28 | os.Exit(1) 29 | } 30 | 31 | res, err := redash.ForkQuery(*redashUrl, id, queryStrings) 32 | checkError(err) 33 | checkStatusCode(res) 34 | 35 | fmt.Println(res.Status) 36 | }, 37 | } 38 | 39 | func init() { 40 | queryForkCmd.Flags().BoolP("force", "f", false, "Run without asking") 41 | queryCmd.AddCommand(queryForkCmd) 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 ariarijp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cmd/query_show.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/ariarijp/redashman/redash" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var queryShowCmd = &cobra.Command{ 13 | Use: "show [id]", 14 | Short: "Show a query", 15 | Args: cobra.ExactArgs(1), 16 | Run: func(cmd *cobra.Command, args []string) { 17 | redashUrl, apiKey, err := getRequiredFlags() 18 | checkError(err) 19 | 20 | id, err := strconv.Atoi(args[0]) 21 | checkError(err) 22 | 23 | queryStrings := getDefaultQueryStrings(*apiKey) 24 | 25 | res, err := redash.GetQuery(*redashUrl, id, queryStrings) 26 | checkError(err) 27 | checkStatusCode(res) 28 | 29 | body, err := res.Body.ToString() 30 | checkError(err) 31 | 32 | flagJson, err := cmd.Flags().GetBool("json") 33 | checkError(err) 34 | if flagJson { 35 | fmt.Println(body) 36 | return 37 | } 38 | 39 | query, err := getQueryFromResponseBody(body) 40 | checkError(err) 41 | 42 | fmt.Println(strings.TrimSpace(*query)) 43 | }, 44 | } 45 | 46 | func init() { 47 | queryShowCmd.Flags().Bool("json", false, "Dump as JSON") 48 | queryCmd.AddCommand(queryShowCmd) 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redashman 2 | 3 | `redashman` is a query management tool for Redash 4 | 5 | ## Installation 6 | 7 | ```bash 8 | $ go get -u github.com/ariarijp/redashman 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```bash 14 | $ export REDASH_URL=http://localhost:5000 15 | $ export REDASH_API_KEY=YOUR_API_KEY 16 | $ 17 | $ # List queries 18 | $ redashman query list 100 1 --url $REDASH_URL --api-key $REDASH_API_KEY 19 | $ 20 | $ # Show a query 21 | $ redashman query show 1 --url $REDASH_URL --api-key $REDASH_API_KEY 22 | $ 23 | $ # Create a new query with text from a file 24 | $ echo "SELECT NOW();" > new_query.sql 25 | $ redashman query create new_query.sql --url $REDASH_URL --api-key $REDASH_API_KEY 26 | $ 27 | $ # Modify a query with text from a file 28 | $ echo "SELECT NOW(), CURDATE();" > modified_query.sql 29 | $ redashman query modify 1 modified_query.sql --url $REDASH_URL --api-key $REDASH_API_KEY 30 | $ 31 | $ # Fork a query from an existing one 32 | $ redashman query fork 1 --url $REDASH_URL --api-key $REDASH_API_KEY 33 | $ 34 | $ # Archive a query 35 | $ redashman query archive 1 --url $REDASH_URL --api-key $REDASH_API_KEY 36 | ``` 37 | 38 | ## License 39 | 40 | MIT 41 | 42 | ## Author 43 | 44 | [ariarijp](https://github.com/ariarijp) 45 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/mitchellh/go-homedir" 8 | "github.com/spf13/cobra" 9 | "github.com/spf13/viper" 10 | ) 11 | 12 | var cfgFile string 13 | 14 | var RootCmd = &cobra.Command{ 15 | Use: "redashman", 16 | Short: "redashman is a query management tool for Redash", 17 | } 18 | 19 | func Execute() { 20 | if err := RootCmd.Execute(); err != nil { 21 | fmt.Println(err) 22 | os.Exit(1) 23 | } 24 | } 25 | 26 | func init() { 27 | RootCmd.PersistentFlags().String("api-key", "", "Redash User API Key") 28 | RootCmd.PersistentFlags().String("url", "", "Redash URL e.g. http://localhost") 29 | RootCmd.PersistentFlags().Uint("timeout", 1000, "Set connection timeout in milliseconds") 30 | RootCmd.MarkPersistentFlagRequired("api-key") 31 | RootCmd.MarkPersistentFlagRequired("url") 32 | cobra.OnInitialize(initConfig) 33 | } 34 | 35 | func initConfig() { 36 | if cfgFile != "" { 37 | viper.SetConfigFile(cfgFile) 38 | } else { 39 | home, err := homedir.Dir() 40 | checkError(err) 41 | 42 | viper.AddConfigPath(home) 43 | viper.SetConfigName(".redashman") 44 | } 45 | 46 | viper.AutomaticEnv() 47 | 48 | if err := viper.ReadInConfig(); err == nil { 49 | fmt.Println("Using config file:", viper.ConfigFileUsed()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cmd/query_create.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | 8 | "github.com/Songmu/prompter" 9 | "github.com/ariarijp/redashman/redash" 10 | "github.com/bitly/go-simplejson" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var queryCreateCmd = &cobra.Command{ 15 | Use: "create [file]", 16 | Short: "Create a new query with text from a file", 17 | Args: cobra.ExactArgs(1), 18 | Run: func(cmd *cobra.Command, args []string) { 19 | redashUrl, apiKey, err := getRequiredFlags() 20 | checkError(err) 21 | force, _ := cmd.Flags().GetBool("force") 22 | 23 | inputFilePath := args[0] 24 | checkError(err) 25 | query, err := ioutil.ReadFile(inputFilePath) 26 | checkError(err) 27 | 28 | if !force && !prompter.YN("Are you sure you want to create a new query?", false) { 29 | os.Exit(1) 30 | } 31 | 32 | queryStrings := getDefaultQueryStrings(*apiKey) 33 | 34 | json := simplejson.New() 35 | json.Set("query", string(query)) 36 | json.Set("data_source_id", 1) 37 | json.Set("name", "New Query") 38 | 39 | res, err := redash.CreateQuery(*redashUrl, queryStrings, json) 40 | checkError(err) 41 | checkStatusCode(res) 42 | 43 | fmt.Println(res.Status) 44 | }, 45 | } 46 | 47 | func init() { 48 | queryCreateCmd.Flags().BoolP("force", "f", false, "Run without asking") 49 | queryCmd.AddCommand(queryCreateCmd) 50 | } 51 | -------------------------------------------------------------------------------- /cmd/query_list.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | encjson "encoding/json" 5 | "fmt" 6 | 7 | "github.com/ariarijp/redashman/redash" 8 | "github.com/bitly/go-simplejson" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var queryListCmd = &cobra.Command{ 13 | Use: "list [page_size] [page]", 14 | Short: "List queries", 15 | Args: cobra.ExactArgs(2), 16 | Run: func(cmd *cobra.Command, args []string) { 17 | redashUrl, apiKey, err := getRequiredFlags() 18 | checkError(err) 19 | pageSize := args[0] 20 | page := args[1] 21 | 22 | queryStrings := getDefaultQueryStrings(*apiKey) 23 | queryStrings.Set("page_size", pageSize) 24 | queryStrings.Set("page", page) 25 | 26 | res, err := redash.GetQueries(*redashUrl, queryStrings) 27 | checkError(err) 28 | checkStatusCode(res) 29 | 30 | body, err := res.Body.ToString() 31 | checkError(err) 32 | 33 | flagJson, err := cmd.Flags().GetBool("json") 34 | checkError(err) 35 | if flagJson { 36 | fmt.Println(body) 37 | return 38 | } 39 | 40 | json, err := simplejson.NewJson([]byte(body)) 41 | checkError(err) 42 | 43 | for _, query := range json.Get("results").MustArray() { 44 | q := query.(map[string]interface{}) 45 | queryUrl := fmt.Sprintf("%s/queries/%s", *redashUrl, q["id"]) 46 | fmt.Printf("%s\t%s\t%s\n", 47 | q["id"].(encjson.Number).String(), 48 | q["name"].(string), 49 | queryUrl, 50 | ) 51 | } 52 | }, 53 | } 54 | 55 | func init() { 56 | queryListCmd.Flags().Bool("json", false, "Dump as JSON") 57 | queryCmd.AddCommand(queryListCmd) 58 | } 59 | -------------------------------------------------------------------------------- /cmd/util.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "os" 7 | 8 | "github.com/bitly/go-simplejson" 9 | "github.com/franela/goreq" 10 | ) 11 | 12 | func getUrlFlag() (*string, error) { 13 | flag, err := RootCmd.PersistentFlags().GetString("url") 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | url := string(flag) 19 | return &url, nil 20 | } 21 | 22 | func getApiKeyFlag() (*string, error) { 23 | flag, err := RootCmd.PersistentFlags().GetString("api-key") 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | apiKey := string(flag) 29 | return &apiKey, nil 30 | } 31 | 32 | func getRequiredFlags() (*string, *string, error) { 33 | redashUrl, err := getUrlFlag() 34 | if err != nil { 35 | return nil, nil, err 36 | } 37 | apiKey, err := getApiKeyFlag() 38 | if err != nil { 39 | return nil, nil, err 40 | } 41 | 42 | return redashUrl, apiKey, nil 43 | } 44 | 45 | func getDefaultQueryStrings(apiKey string) url.Values { 46 | queryStrings := url.Values{} 47 | queryStrings.Set("api_key", apiKey) 48 | 49 | return queryStrings 50 | } 51 | 52 | func getQueryFromResponseBody(body string) (*string, error) { 53 | js, err := simplejson.NewJson([]byte(body)) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | query, err := js.Get("query").String() 59 | checkError(err) 60 | 61 | return &query, nil 62 | } 63 | 64 | func checkError(err error) { 65 | if err != nil { 66 | fmt.Println(err) 67 | os.Exit(1) 68 | } 69 | } 70 | 71 | func checkStatusCode(res *goreq.Response) { 72 | if res.StatusCode != 200 { 73 | println(res.Status) 74 | os.Exit(1) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /redash/client.go: -------------------------------------------------------------------------------- 1 | package redash 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | 7 | "github.com/bitly/go-simplejson" 8 | "github.com/franela/goreq" 9 | ) 10 | 11 | func GetQuery(redashUrl string, id int, queryStrings url.Values) (*goreq.Response, error) { 12 | return goreq.Request{ 13 | Method: "GET", 14 | Uri: fmt.Sprintf("%s/api/queries/%d", redashUrl, id), 15 | QueryString: queryStrings, 16 | }.Do() 17 | } 18 | 19 | func GetQueries(redashUrl string, queryStrings url.Values) (*goreq.Response, error) { 20 | return goreq.Request{ 21 | Method: "GET", 22 | Uri: fmt.Sprintf("%s/api/queries", redashUrl), 23 | QueryString: queryStrings, 24 | }.Do() 25 | } 26 | 27 | func CreateQuery(redashUrl string, queryStrings url.Values, json *simplejson.Json) (*goreq.Response, error) { 28 | body, err := json.Encode() 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return goreq.Request{ 34 | Method: "POST", 35 | Uri: fmt.Sprintf("%s/api/queries", redashUrl), 36 | QueryString: queryStrings, 37 | Body: body, 38 | }.Do() 39 | } 40 | 41 | func ModifyQuery(redashUrl string, id int, queryStrings url.Values, json *simplejson.Json) (*goreq.Response, error) { 42 | body, err := json.Encode() 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | return goreq.Request{ 48 | Method: "POST", 49 | Uri: fmt.Sprintf("%s/api/queries/%d", redashUrl, id), 50 | QueryString: queryStrings, 51 | Body: body, 52 | }.Do() 53 | } 54 | 55 | func ForkQuery(redashUrl string, id int, queryStrings url.Values) (*goreq.Response, error) { 56 | return goreq.Request{ 57 | Method: "POST", 58 | Uri: fmt.Sprintf("%s/api/queries/%d/fork", redashUrl, id), 59 | QueryString: queryStrings, 60 | }.Do() 61 | } 62 | 63 | func ArchiveQuery(redashUrl string, id int, queryStrings url.Values) (*goreq.Response, error) { 64 | return goreq.Request{ 65 | Method: "DELETE", 66 | Uri: fmt.Sprintf("%s/api/queries/%d", redashUrl, id), 67 | QueryString: queryStrings, 68 | }.Do() 69 | } 70 | 71 | func GetTasks(redashUrl string, queryStrings url.Values) (*goreq.Response, error) { 72 | return goreq.Request{ 73 | Method: "GET", 74 | Uri: fmt.Sprintf("%s/api/admin/queries/tasks", redashUrl), 75 | QueryString: queryStrings, 76 | }.Do() 77 | } 78 | -------------------------------------------------------------------------------- /cmd/query_modify.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/url" 7 | "os" 8 | "path" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/Songmu/prompter" 13 | "github.com/ariarijp/redashman/redash" 14 | "github.com/bitly/go-simplejson" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | var queryModifyCmd = &cobra.Command{ 19 | Use: "modify [id] [file]", 20 | Short: "Modify a query with text from a file", 21 | Args: cobra.ExactArgs(2), 22 | Run: func(cmd *cobra.Command, args []string) { 23 | redashUrl, apiKey, err := getRequiredFlags() 24 | checkError(err) 25 | backupDir, err := cmd.Flags().GetString("backup-dir") 26 | checkError(err) 27 | force, _ := cmd.Flags().GetBool("force") 28 | 29 | id, err := strconv.Atoi(args[0]) 30 | checkError(err) 31 | 32 | inputFilePath := args[1] 33 | checkError(err) 34 | query, err := ioutil.ReadFile(inputFilePath) 35 | checkError(err) 36 | 37 | if !force && !prompter.YN("Are you sure you want to modify this query?", false) { 38 | os.Exit(1) 39 | } 40 | 41 | queryStrings := getDefaultQueryStrings(*apiKey) 42 | 43 | if backupDir != "" { 44 | err = makeBackupFile(*redashUrl, id, queryStrings, backupDir) 45 | checkError(err) 46 | } 47 | 48 | json := simplejson.New() 49 | json.Set("query", string(query)) 50 | 51 | res, err := redash.ModifyQuery(*redashUrl, id, queryStrings, json) 52 | checkError(err) 53 | checkStatusCode(res) 54 | 55 | fmt.Println(res.Status) 56 | }, 57 | } 58 | 59 | func makeBackupFile(redashUrl string, id int, queryStrings url.Values, backupDir string) error { 60 | res, err := redash.GetQuery(redashUrl, id, queryStrings) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | body, err := res.Body.ToString() 66 | if err != nil { 67 | return err 68 | } 69 | 70 | query, err := getQueryFromResponseBody(body) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | now := time.Now() 76 | backupFileName := fmt.Sprintf("query_%d_%s.sql", id, now.Format("20060102150405")) 77 | backupFilePath := path.Join(backupDir, backupFileName) 78 | 79 | err = ioutil.WriteFile(backupFilePath, []byte(*query), 0644) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | return nil 85 | } 86 | 87 | func init() { 88 | queryModifyCmd.Flags().BoolP("force", "f", false, "Run without asking") 89 | queryModifyCmd.Flags().String("backup-dir", "", "Backup file path") 90 | queryCmd.AddCommand(queryModifyCmd) 91 | } 92 | --------------------------------------------------------------------------------