8 "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
9 "git.curoverse.com/arvados.git/sdk/go/keepclient"
20 err := doMain(os.Args[1:])
26 func doMain(args []string) error {
27 flags := flag.NewFlagSet("keep-block-check", flag.ExitOnError)
29 configFile := flags.String(
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.")
34 keepServicesJSON := flags.String(
37 "An optional list of available keepservices. "+
38 "If not provided, this list is obtained from api server configured in config-file.")
40 locatorFile := flags.String(
43 "Filename containing the block hashes to be checked. This is required. "+
44 "This file contains the block hashes one per line.")
46 prefix := flags.String(
49 "Block hash prefix. When a prefix is specified, only hashes listed in the file with this prefix will be checked.")
51 blobSignatureTTLFlag := flags.Duration(
54 "Lifetime of blob permission signatures on the keepservers. If not provided, this will be retrieved from the API server's discovery document.")
56 verbose := flags.Bool(
59 "Log progress of each block verification")
61 // Parse args; omit the first arg which is the command name
64 config, blobSigningKey, err := loadConfig(*configFile)
66 return fmt.Errorf("Error loading configuration from file: %s", err.Error())
69 // get list of block locators to be checked
70 blockLocators, err := getBlockLocators(*locatorFile, *prefix)
72 return fmt.Errorf("Error reading block hashes to be checked from file: %s", err.Error())
76 kc, blobSignatureTTL, err := setupKeepClient(config, *keepServicesJSON, *blobSignatureTTLFlag)
78 return fmt.Errorf("Error configuring keepclient: %s", err.Error())
81 return performKeepBlockCheck(kc, blobSignatureTTL, blobSigningKey, blockLocators, *verbose)
84 type apiConfig struct {
91 // Load config from given file
92 func loadConfig(configFile string) (config apiConfig, blobSigningKey string, err error) {
94 err = errors.New("Client config file not specified")
98 config, blobSigningKey, err = readConfigFromFile(configFile)
102 var matchTrue = regexp.MustCompile("^(?i:1|yes|true)$")
104 // Read config from file
105 func readConfigFromFile(filename string) (config apiConfig, blobSigningKey string, err error) {
106 if !strings.Contains(filename, "/") {
107 filename = os.Getenv("HOME") + "/.config/arvados/" + filename + ".conf"
110 content, err := ioutil.ReadFile(filename)
116 lines := strings.Split(string(content), "\n")
117 for _, line := range lines {
122 kv := strings.SplitN(line, "=", 2)
124 key := strings.TrimSpace(kv[0])
125 value := strings.TrimSpace(kv[1])
128 case "ARVADOS_API_TOKEN":
129 config.APIToken = value
130 case "ARVADOS_API_HOST":
131 config.APIHost = value
132 case "ARVADOS_API_HOST_INSECURE":
133 config.APIHostInsecure = matchTrue.MatchString(value)
134 case "ARVADOS_EXTERNAL_CLIENT":
135 config.ExternalClient = matchTrue.MatchString(value)
136 case "ARVADOS_BLOB_SIGNING_KEY":
137 blobSigningKey = value
145 // setup keepclient using the config provided
146 func setupKeepClient(config apiConfig, keepServicesJSON string, blobSignatureTTL time.Duration) (kc *keepclient.KeepClient, ttl time.Duration, err error) {
147 arv := arvadosclient.ArvadosClient{
148 ApiToken: config.APIToken,
149 ApiServer: config.APIHost,
150 ApiInsecure: config.APIHostInsecure,
151 Client: &http.Client{Transport: &http.Transport{
152 TLSClientConfig: &tls.Config{InsecureSkipVerify: config.APIHostInsecure}}},
153 External: config.ExternalClient,
156 // if keepServicesJSON is provided, use it to load services; else, use DiscoverKeepServers
157 if keepServicesJSON == "" {
158 kc, err = keepclient.MakeKeepClient(&arv)
163 kc = keepclient.New(&arv)
164 err = kc.LoadKeepServicesFromJSON(keepServicesJSON)
170 // Get if blobSignatureTTL is not provided
171 ttl = blobSignatureTTL
172 if blobSignatureTTL == 0 {
173 value, err := arv.Discovery("blobSignatureTtl")
175 ttl = time.Duration(int(value.(float64))) * time.Second
184 // Get list of unique block locators from the given file
185 func getBlockLocators(locatorFile, prefix string) (locators []string, err error) {
186 if locatorFile == "" {
187 err = errors.New("block-hash-file not specified")
191 content, err := ioutil.ReadFile(locatorFile)
196 locatorMap := make(map[string]bool)
197 for _, line := range strings.Split(string(content), "\n") {
198 line = strings.TrimSpace(line)
199 if line == "" || !strings.HasPrefix(line, prefix) || locatorMap[line] {
202 locators = append(locators, line)
203 locatorMap[line] = true
209 // Get block headers from keep. Log any errors.
210 func performKeepBlockCheck(kc *keepclient.KeepClient, blobSignatureTTL time.Duration, blobSigningKey string, blockLocators []string, verbose bool) error {
211 totalBlocks := len(blockLocators)
214 for _, locator := range blockLocators {
217 log.Printf("Verifying block %d of %d: %v", current, totalBlocks, locator)
219 getLocator := locator
220 if blobSigningKey != "" {
221 expiresAt := time.Now().AddDate(0, 0, 1)
222 getLocator = keepclient.SignLocator(locator, kc.Arvados.ApiToken, expiresAt, blobSignatureTTL, []byte(blobSigningKey))
225 _, _, err := kc.Ask(getLocator)
228 log.Printf("Error verifying block %v: %v", locator, err)
232 log.Printf("Verify block totals: %d attempts, %d successes, %d errors", totalBlocks, totalBlocks-notFoundBlocks, notFoundBlocks)
234 if notFoundBlocks > 0 {
235 return fmt.Errorf("Block verification failed for %d out of %d blocks with matching prefix.", notFoundBlocks, totalBlocks)