1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
20 "git.arvados.org/arvados.git/lib/cmd"
21 "git.arvados.org/arvados.git/sdk/go/arvadosclient"
22 "git.arvados.org/arvados.git/sdk/go/keepclient"
28 os.Exit(doMain(os.Args[1:], os.Stderr))
31 func doMain(args []string, stderr io.Writer) int {
32 flags := flag.NewFlagSet("keep-block-check", flag.ExitOnError)
34 configFile := flags.String(
37 "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.")
39 keepServicesJSON := flags.String(
42 "An optional list of available keepservices. "+
43 "If not provided, this list is obtained from api server configured in config-file.")
45 locatorFile := flags.String(
48 "Filename containing the block hashes to be checked. This is required. "+
49 "This file contains the block hashes one per line.")
51 prefix := flags.String(
54 "Block hash prefix. When a prefix is specified, only hashes listed in the file with this prefix will be checked.")
56 blobSignatureTTLFlag := flags.Duration(
59 "Lifetime of blob permission signatures on the keepservers. If not provided, this will be retrieved from the API server's discovery document.")
61 verbose := flags.Bool(
64 "Log progress of each block verification")
66 getVersion := flags.Bool(
69 "Print version information and exit.")
71 if ok, code := cmd.ParseFlags(flags, os.Args[0], args, "", stderr); !ok {
73 } else if *getVersion {
74 fmt.Printf("%s %s\n", os.Args[0], version)
78 config, blobSigningKey, err := loadConfig(*configFile)
80 fmt.Fprintf(stderr, "Error loading configuration from file: %s\n", err)
84 // get list of block locators to be checked
85 blockLocators, err := getBlockLocators(*locatorFile, *prefix)
87 fmt.Fprintf(stderr, "Error reading block hashes to be checked from file: %s\n", err)
92 kc, blobSignatureTTL, err := setupKeepClient(config, *keepServicesJSON, *blobSignatureTTLFlag)
94 fmt.Fprintf(stderr, "Error configuring keepclient: %s\n", err)
98 err = performKeepBlockCheck(kc, blobSignatureTTL, blobSigningKey, blockLocators, *verbose)
100 fmt.Fprintln(stderr, err)
107 type apiConfig struct {
114 // Load config from given file
115 func loadConfig(configFile string) (config apiConfig, blobSigningKey string, err error) {
116 if configFile == "" {
117 err = errors.New("Client config file not specified")
121 config, blobSigningKey, err = readConfigFromFile(configFile)
125 // Read config from file
126 func readConfigFromFile(filename string) (config apiConfig, blobSigningKey string, err error) {
127 if !strings.Contains(filename, "/") {
128 filename = os.Getenv("HOME") + "/.config/arvados/" + filename + ".conf"
131 content, err := ioutil.ReadFile(filename)
137 lines := strings.Split(string(content), "\n")
138 for _, line := range lines {
143 kv := strings.SplitN(line, "=", 2)
145 key := strings.TrimSpace(kv[0])
146 value := strings.TrimSpace(kv[1])
149 case "ARVADOS_API_TOKEN":
150 config.APIToken = value
151 case "ARVADOS_API_HOST":
152 config.APIHost = value
153 case "ARVADOS_API_HOST_INSECURE":
154 config.APIHostInsecure = arvadosclient.StringBool(value)
155 case "ARVADOS_EXTERNAL_CLIENT":
156 config.ExternalClient = arvadosclient.StringBool(value)
157 case "ARVADOS_BLOB_SIGNING_KEY":
158 blobSigningKey = value
166 // setup keepclient using the config provided
167 func setupKeepClient(config apiConfig, keepServicesJSON string, blobSignatureTTL time.Duration) (kc *keepclient.KeepClient, ttl time.Duration, err error) {
168 arv := arvadosclient.ArvadosClient{
169 ApiToken: config.APIToken,
170 ApiServer: config.APIHost,
171 ApiInsecure: config.APIHostInsecure,
172 Client: &http.Client{Transport: &http.Transport{
173 TLSClientConfig: &tls.Config{InsecureSkipVerify: config.APIHostInsecure}}},
174 External: config.ExternalClient,
177 // If keepServicesJSON is provided, use it instead of service discovery
178 if keepServicesJSON == "" {
179 kc, err = keepclient.MakeKeepClient(&arv)
184 kc = keepclient.New(&arv)
185 err = kc.LoadKeepServicesFromJSON(keepServicesJSON)
191 // Get if blobSignatureTTL is not provided
192 ttl = blobSignatureTTL
193 if blobSignatureTTL == 0 {
194 value, err := arv.Discovery("blobSignatureTtl")
196 ttl = time.Duration(int(value.(float64))) * time.Second
205 // Get list of unique block locators from the given file
206 func getBlockLocators(locatorFile, prefix string) (locators []string, err error) {
207 if locatorFile == "" {
208 err = errors.New("block-hash-file not specified")
212 content, err := ioutil.ReadFile(locatorFile)
217 locatorMap := make(map[string]bool)
218 for _, line := range strings.Split(string(content), "\n") {
219 line = strings.TrimSpace(line)
220 if line == "" || !strings.HasPrefix(line, prefix) || locatorMap[line] {
223 locators = append(locators, line)
224 locatorMap[line] = true
230 // Get block headers from keep. Log any errors.
231 func performKeepBlockCheck(kc *keepclient.KeepClient, blobSignatureTTL time.Duration, blobSigningKey string, blockLocators []string, verbose bool) error {
232 totalBlocks := len(blockLocators)
235 for _, locator := range blockLocators {
238 log.Printf("Verifying block %d of %d: %v", current, totalBlocks, locator)
240 getLocator := locator
241 if blobSigningKey != "" {
242 expiresAt := time.Now().AddDate(0, 0, 1)
243 getLocator = keepclient.SignLocator(locator, kc.Arvados.ApiToken, expiresAt, blobSignatureTTL, []byte(blobSigningKey))
246 _, _, err := kc.Ask(getLocator)
249 log.Printf("Error verifying block %v: %v", locator, err)
253 log.Printf("Verify block totals: %d attempts, %d successes, %d errors", totalBlocks, totalBlocks-notFoundBlocks, notFoundBlocks)
255 if notFoundBlocks > 0 {
256 return fmt.Errorf("Block verification failed for %d out of %d blocks with matching prefix", notFoundBlocks, totalBlocks)