"regexp"
"strconv"
"strings"
+ "time"
"git.arvados.org/arvados.git/sdk/go/arvados"
"github.com/ghodss/yaml"
KeepBalancePath string
configdata []byte
+ // UTC time for configdata: either the modtime of the file we
+ // read configdata from, or the time when we read configdata
+ // from a pipe.
+ sourceTimestamp time.Time
}
// NewLoader returns a new Loader with Stdin and Logger set to the
return munged
}
-func (ldr *Loader) loadBytes(path string) ([]byte, error) {
+func (ldr *Loader) loadBytes(path string) ([]byte, time.Time, error) {
if path == "-" {
- return ioutil.ReadAll(ldr.Stdin)
+ buf, err := ioutil.ReadAll(ldr.Stdin)
+ return buf, time.Now().UTC(), err
}
f, err := os.Open(path)
if err != nil {
- return nil, err
+ return nil, time.Time{}, err
}
defer f.Close()
- return ioutil.ReadAll(f)
+ fi, err := f.Stat()
+ if err != nil {
+ return nil, time.Time{}, err
+ }
+ buf, err := ioutil.ReadAll(f)
+ return buf, fi.ModTime().UTC(), err
}
func (ldr *Loader) Load() (*arvados.Config, error) {
if ldr.configdata == nil {
- buf, err := ldr.loadBytes(ldr.Path)
+ buf, t, err := ldr.loadBytes(ldr.Path)
if err != nil {
return nil, err
}
ldr.configdata = buf
+ ldr.sourceTimestamp = t
}
// FIXME: We should reject YAML if the same key is used twice
}
}
}
+ cfg.SourceTimestamp = ldr.sourceTimestamp
+ cfg.SourceSHA256 = fmt.Sprintf("%x", sha256.Sum256(ldr.configdata))
return &cfg, nil
}
"os"
"os/exec"
"reflect"
+ "regexp"
"strings"
"testing"
+ "time"
"git.arvados.org/arvados.git/sdk/go/arvados"
"git.arvados.org/arvados.git/sdk/go/ctxlog"
"github.com/ghodss/yaml"
"github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
check "gopkg.in/check.v1"
)
c.Assert(err, check.IsNil)
yaml, err := yaml.Marshal(cfg)
c.Assert(err, check.IsNil)
+ // Well, *nearly* no warnings. SourceTimestamp and
+ // SourceSHA256 are included in a config-dump, but not
+ // expected in a real config file.
+ yaml = regexp.MustCompile(`(^|\n)(Source(Timestamp|SHA256): .*?\n)+`).ReplaceAll(yaml, []byte("$1"))
cfgDumped, err := testLoader(c, string(yaml), &logbuf).Load()
c.Assert(err, check.IsNil)
+ // SourceTimestamp and SourceSHA256 aren't expected to be
+ // preserved through dump+load
+ cfgDumped.SourceTimestamp = cfg.SourceTimestamp
+ cfgDumped.SourceSHA256 = cfg.SourceSHA256
c.Check(cfg, check.DeepEquals, cfgDumped)
c.Check(logbuf.String(), check.Equals, "")
}
c.Assert(err, check.IsNil)
expected, err := expectedldr.Load()
c.Assert(err, check.IsNil)
+ // The inputs generally aren't even files, so SourceTimestamp
+ // can't be expected to match.
+ got.SourceTimestamp = expected.SourceTimestamp
+ // Obviously the content isn't identical -- otherwise we
+ // wouldn't need to check that it's equivalent.
+ got.SourceSHA256 = expected.SourceSHA256
checkEqualYAML(c, got, expected)
}
c.Check(logbuf.String(), check.Not(check.Matches), `(?ms).*Type2\.preemptible.*`)
c.Check(logbuf.String(), check.Not(check.Matches), `(?ms).*(z1111|z2222)[^\n]*InstanceTypes.*`)
}
+
+func (s *LoadSuite) TestSourceTimestamp(c *check.C) {
+ conftime, err := time.Parse(time.RFC3339, "2022-03-04T05:06:07-08:00")
+ c.Assert(err, check.IsNil)
+ confdata := `Clusters: {zzzzz: {}}`
+ conffile := c.MkDir() + "/config.yml"
+ ioutil.WriteFile(conffile, []byte(confdata), 0777)
+ tv := unix.NsecToTimeval(conftime.UnixNano())
+ unix.Lutimes(conffile, []unix.Timeval{tv, tv})
+ for _, trial := range []struct {
+ configarg string
+ expectTime time.Time
+ }{
+ {"-", time.Now()},
+ {conffile, conftime},
+ } {
+ c.Logf("trial: %+v", trial)
+ ldr := NewLoader(strings.NewReader(confdata), ctxlog.TestLogger(c))
+ ldr.Path = trial.configarg
+ cfg, err := ldr.Load()
+ c.Assert(err, check.IsNil)
+ c.Check(cfg.SourceTimestamp, check.Equals, cfg.SourceTimestamp.UTC())
+ c.Check(int(cfg.SourceTimestamp.Sub(trial.expectTime).Seconds()), check.Equals, 0)
+ }
+}