message and a non-zero exit status. This prevents hiding possibly important
errors. In addition all files are fetched first and then deployed to try to
prevent inconsistent state if only one file can be downloaded. The state
- file (containing last file modifications) is only updated when all
- operations were successful.
+ file (containing last file modification and content hash) is only updated
+ when all operations were successful.
- To prevent unexpected permissions, `nsscash` does not create new files. The
user must create them first and `nsscash` will then re-use the permissions
and owner/group when updating the file (see examples below).
The following global keys are available:
-- `statepath`: Path to a JSON file which stores the last modification time of
- each file; automatically updated by `nsscash`. Used to fetch data only when
- something has changed to reduce the required traffic.
+- `statepath`: Path to a JSON file which stores the last modification time and
+ hash of each file; automatically updated by `nsscash`. Used to fetch data
+ only when something has changed to reduce the required traffic, via
+ `If-Modified-Since`. When the hash of a file has changed the download is
+ forced.
Each `file` block describes a single file to download/write. The following
keys are available:
import (
"bytes"
+ "crypto/sha512"
+ "encoding/hex"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"syscall"
+ "time"
"github.com/pkg/errors"
)
return nil
}
+func checksumFile(file *File) (string, error) {
+ x, err := ioutil.ReadFile(file.Path)
+ if err != nil {
+ return "", err
+ }
+ return checksumBytes(x), nil
+}
+
+func checksumBytes(x []byte) string {
+ h := sha512.New()
+ h.Write(x)
+ return hex.EncodeToString(h.Sum(nil))
+}
+
func fetchFile(file *File, state *State) error {
t := state.LastModified[file.Url]
+
+ hash, err := checksumFile(file)
+ if err != nil {
+ // See below in deployFile() for the reason
+ return errors.Wrapf(err, "file.path %q must exist", file.Path)
+ }
+ if hash != state.Checksum[file.Url] {
+ log.Printf("%q -> %q: hash has changed", file.Url, file.Path)
+ var zero time.Time
+ t = zero // force download
+ }
+
status, body, err := fetchIfModified(file.Url, &t)
if err != nil {
return err
} else {
return fmt.Errorf("unsupported file type %v", file.Type)
}
+
+ state.Checksum[file.Url] = checksumBytes(file.body)
return nil
}
)
type State struct {
+ // Key is File.Url
LastModified map[string]time.Time
+ Checksum map[string]string // SHA512 in hex
}
func LoadState(path string) (*State, error) {
if state.LastModified == nil {
state.LastModified = make(map[string]time.Time)
}
+ if state.Checksum == nil {
+ state.Checksum = make(map[string]string)
+ }
return &state, nil
}