8724: add keep-block-check script
[arvados.git] / tools / keep-block-check / keep-block-check.go
1 package main
2
3 import (
4         "crypto/tls"
5         "errors"
6         "flag"
7         "fmt"
8         "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
9         "git.curoverse.com/arvados.git/sdk/go/keepclient"
10         "io/ioutil"
11         "log"
12         "net/http"
13         "os"
14         "regexp"
15         "strings"
16         "time"
17 )
18
19 func main() {
20         err := doMain()
21         if err != nil {
22                 log.Fatalf("%v", err)
23         }
24 }
25
26 func doMain() error {
27         flags := flag.NewFlagSet("keep-block-check", flag.ExitOnError)
28
29         configFile := flags.String(
30                 "config",
31                 "",
32                 "Configuration filename. May be either a pathname to a config file, or (for example) 'foo' as shorthand for $HOME/.config/arvados/foo.conf file. This file is expected to specify the values for ARVADOS_API_TOKEN, ARVADOS_API_HOST, ARVADOS_API_HOST_INSECURE, and ARVADOS_BLOB_SIGNING_KEY for the source.")
33
34         keepServicesJSON := flags.String(
35                 "keep-services-json",
36                 "",
37                 "An optional list of available keepservices. "+
38                         "If not provided, this list is obtained from api server configured in config-file.")
39
40         locatorFile := flags.String(
41                 "block-hash-file",
42                 "",
43                 "Filename containing the block hashes to be checked. This is required. "+
44                         "This file contains the block hashes one per line.")
45
46         prefix := flags.String(
47                 "prefix",
48                 "",
49                 "Block hash prefix. When a prefix is specified, only hashes listed in the file with this prefix will be checked.")
50
51         // Parse args; omit the first arg which is the command name
52         flags.Parse(os.Args[1:])
53
54         config, blobSigningKey, err := loadConfig(*configFile)
55         if err != nil {
56                 return fmt.Errorf("Error loading configuration from file: %s", err.Error())
57         }
58
59         // get list of block locators to be checked
60         blockLocators, err := getBlockLocators(*locatorFile)
61         if err != nil {
62                 return fmt.Errorf("Error reading block hashes to be checked from file: %s", err.Error())
63         }
64
65         // setup keepclient
66         kc, err := setupKeepClient(config, *keepServicesJSON)
67         if err != nil {
68                 return fmt.Errorf("Error configuring keepclient: %s", err.Error())
69         }
70
71         performKeepBlockCheck(kc, blobSigningKey, *prefix, blockLocators)
72         return nil
73 }
74
75 type apiConfig struct {
76         APIToken        string
77         APIHost         string
78         APIHostInsecure bool
79         ExternalClient  bool
80 }
81
82 // Load config from given file
83 func loadConfig(configFile string) (config apiConfig, blobSigningKey string, err error) {
84         if configFile == "" {
85                 err = errors.New("API config file not specified")
86                 return
87         }
88
89         config, blobSigningKey, err = readConfigFromFile(configFile)
90         return
91 }
92
93 var matchTrue = regexp.MustCompile("^(?i:1|yes|true)$")
94
95 // Read config from file
96 func readConfigFromFile(filename string) (config apiConfig, blobSigningKey string, err error) {
97         if !strings.Contains(filename, "/") {
98                 filename = os.Getenv("HOME") + "/.config/arvados/" + filename + ".conf"
99         }
100
101         content, err := ioutil.ReadFile(filename)
102
103         if err != nil {
104                 return
105         }
106
107         lines := strings.Split(string(content), "\n")
108         for _, line := range lines {
109                 if line == "" {
110                         continue
111                 }
112
113                 kv := strings.SplitN(line, "=", 2)
114                 key := strings.TrimSpace(kv[0])
115                 value := strings.TrimSpace(kv[1])
116
117                 switch key {
118                 case "ARVADOS_API_TOKEN":
119                         config.APIToken = value
120                 case "ARVADOS_API_HOST":
121                         config.APIHost = value
122                 case "ARVADOS_API_HOST_INSECURE":
123                         config.APIHostInsecure = matchTrue.MatchString(value)
124                 case "ARVADOS_EXTERNAL_CLIENT":
125                         config.ExternalClient = matchTrue.MatchString(value)
126                 case "ARVADOS_BLOB_SIGNING_KEY":
127                         blobSigningKey = value
128                 }
129         }
130
131         return
132 }
133
134 // setup keepclient using the config provided
135 func setupKeepClient(config apiConfig, keepServicesJSON string) (kc *keepclient.KeepClient, err error) {
136         arv := arvadosclient.ArvadosClient{
137                 ApiToken:    config.APIToken,
138                 ApiServer:   config.APIHost,
139                 ApiInsecure: config.APIHostInsecure,
140                 Client: &http.Client{Transport: &http.Transport{
141                         TLSClientConfig: &tls.Config{InsecureSkipVerify: config.APIHostInsecure}}},
142                 External: config.ExternalClient,
143         }
144
145         // if keepServicesJSON is provided, use it to load services; else, use DiscoverKeepServers
146         if keepServicesJSON == "" {
147                 kc, err = keepclient.MakeKeepClient(&arv)
148                 if err != nil {
149                         return
150                 }
151         } else {
152                 kc = keepclient.New(&arv)
153                 err = kc.LoadKeepServicesFromJSON(keepServicesJSON)
154                 if err != nil {
155                         return
156                 }
157         }
158
159         return
160 }
161
162 // Get list of block locators from the given file
163 func getBlockLocators(locatorFile string) (locators []string, err error) {
164         if locatorFile == "" {
165                 err = errors.New("block-hash-file not specified")
166                 return
167         }
168
169         content, err := ioutil.ReadFile(locatorFile)
170
171         if err != nil {
172                 return
173         }
174
175         lines := strings.Split(string(content), "\n")
176         for _, line := range lines {
177                 if line == "" {
178                         continue
179                 }
180                 locators = append(locators, strings.TrimSpace(line))
181         }
182
183         return
184 }
185
186 // Get block headers from keep. Log any errors.
187 func performKeepBlockCheck(kc *keepclient.KeepClient, blobSigningKey, prefix string, blockLocators []string) {
188         for _, locator := range blockLocators {
189                 if !strings.HasPrefix(locator, prefix) {
190                         continue
191                 }
192                 getLocator := locator
193                 if blobSigningKey != "" {
194                         expiresAt := time.Now().AddDate(0, 0, 1)
195                         getLocator = keepclient.SignLocator(locator, kc.Arvados.ApiToken, expiresAt, []byte(blobSigningKey))
196                 }
197
198                 _, _, err := kc.Ask(getLocator)
199                 if err != nil {
200                         log.Printf("Error getting head info for block: %v %v", locator, err)
201                 }
202         }
203 }