cmd/apmtool/cache.go (74 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package main import ( "errors" "fmt" "os" "path/filepath" "strings" "github.com/gofrs/flock" ) var ( cacheDir = initCacheDir() ) // readCache reads a file in the cache directory with a file lock, // return an error satisfying errors.Is(err, os.ErrNotExist) if the // cache file does not yet exist. // // The filename may not start with a '.'. func readCache(filename string) ([]byte, error) { if strings.HasPrefix(filename, ".") { return nil, fmt.Errorf("invalid filename %q, may not start with '.'", filename) } cacheFlock := newCacheFlock() if err := cacheFlock.Lock(); err != nil { return nil, fmt.Errorf("error acquiring lock on cache directory: %w", err) } defer cacheFlock.Unlock() cacheFilePath := filepath.Join(cacheDir, filename) data, err := os.ReadFile(cacheFilePath) if err != nil { return nil, fmt.Errorf("error reading cache file: %w", err) } return data, nil } // updateCache reads a file in the cache directory with a file lock, // passes it to the update function, and then writes the result back // to the file. If the file does not exist initially, a nil slice is // passed to the update function. // // The filename may not start with a '.'. func updateCache(filename string, update func([]byte) ([]byte, error)) error { if strings.HasPrefix(filename, ".") { return fmt.Errorf("invalid filename %q, may not start with '.'", filename) } cacheFlock := newCacheFlock() if err := cacheFlock.Lock(); err != nil { return fmt.Errorf("error acquiring lock on cache directory: %w", err) } defer cacheFlock.Unlock() cacheFilePath := filepath.Join(cacheDir, filename) cacheFilePathTemp := filepath.Join(cacheDir, ".temp."+filename) data, err := os.ReadFile(cacheFilePath) if err != nil { if !errors.Is(err, os.ErrNotExist) { return fmt.Errorf("error reading cache file: %w", err) } data = nil } data, err = update(data) if err != nil { return fmt.Errorf("error updating cache file %q: %w", filename, err) } // Write temporary file and rename, to prevent partial writes. if err := os.WriteFile(cacheFilePathTemp, data, 0600); err != nil { return fmt.Errorf("error writing temporary cache file %q: %w", filename, err) } if err := os.Rename(cacheFilePathTemp, cacheFilePath); err != nil { return fmt.Errorf("error renaming temporary cache file %q: %w", filename, err) } return nil } func newCacheFlock() *flock.Flock { return flock.New(filepath.Join(cacheDir, ".flock")) } type CredentialsCache struct { } func initCacheDir() string { userCacheDir, err := os.UserCacheDir() if err != nil { panic(fmt.Errorf("error getting user cache dir: %w", err)) } dir := filepath.Join(userCacheDir, "apmtool") if err := os.MkdirAll(dir, 0700); err != nil { panic(fmt.Errorf("error creating cache dir: %w", err)) } return dir }