pkg/seqno/seqno.go (91 lines of code) (raw):
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package seqno
import (
"io/ioutil"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
"github.com/Azure/azure-extension-platform/pkg/extensionerrors"
"github.com/Azure/azure-extension-platform/pkg/logging"
)
const configSequenceNumber = "ConfigSequenceNumber"
type ISequenceNumberRetriever interface {
GetSequenceNumber(name, version string) (uint, error)
}
type ProdSequenceNumberRetriever struct {
}
func (*ProdSequenceNumberRetriever) GetSequenceNumber(name, version string) (uint, error) {
return getSequenceNumberInternal(name, version)
}
// GetCurrentSequenceNumber returns the current sequence number the extension is using
func GetCurrentSequenceNumber(el logging.ILogger, retriever ISequenceNumberRetriever, name, version string) (sn uint, _ error) {
sequenceNumber, err := retriever.GetSequenceNumber(name, version)
if err == extensionerrors.ErrNotFound || err == extensionerrors.ErrNoMrseqFile {
// If we can't find the sequence number, then it's possible that the extension
// hasn't been installed yet. Go back to 0.
el.Info("Couldn't find current sequence number, likely first execution of the extension, returning sequence number 0")
return 0, nil
}
return sequenceNumber, err
}
func SetSequenceNumber(extName, extVersion string, seqNo uint) error {
return setSequenceNumberInternal(extName, extVersion, seqNo)
}
// findSeqnum finds the most recently used file under the config folder
// Note that this is different than just choosing the highest number, which may be incorrect
func FindSeqNum(el logging.ILogger, configFolder string) (uint, error) {
// try getting the sequence number from the environment first
seqNoString := os.Getenv(configSequenceNumber)
if seqNoString == "" {
el.Info("could not read environment variable %s for getting sequence number", configSequenceNumber)
} else {
seqNo, err := strconv.ParseUint(seqNoString, 10, 64)
if err != nil {
el.Info("could not read sequence number string %s into unsigned integer", seqNoString)
} else {
el.Info("using sequence number %d from environment variable %s", seqNo, configSequenceNumber)
return uint(seqNo), nil
}
}
g, err := filepath.Glob(configFolder + "/*.settings")
if err != nil {
return 0, err
}
// Start by finding the file with the latest time
files, err := ioutil.ReadDir(configFolder)
if err != nil {
return 0, err
}
var modTime time.Time
var names []string
for _, fi := range files {
if fi.Mode().IsRegular() && strings.HasSuffix(fi.Name(), ".settings") {
if !fi.ModTime().Before(modTime) {
if fi.ModTime().After(modTime) {
modTime = fi.ModTime()
names = names[:0]
}
// Unlikely - but just in case the files have the same time
names = append(names, fi.Name())
}
}
}
if len(names) == 0 {
el.Error("Cannot find the seqNo from %s. Not enough files", configFolder)
return 0, extensionerrors.ErrNoSettingsFiles
} else if len(names) == 1 {
i, err := strconv.Atoi(strings.Replace(names[0], ".settings", "", 1))
if err != nil {
el.Error("Can't parse int from filename: %s", names[0])
return 0, extensionerrors.ErrInvalidSettingsFileName
}
return uint(i), nil
} else {
// For some reason we have two or more files with the same time stamp.
// Revert to choosing the highest number.
seqs := make([]int, len(g))
for _, v := range g {
f := filepath.Base(v)
i, err := strconv.Atoi(strings.Replace(f, ".settings", "", 1))
if err != nil {
el.Error("Can't parse int from filename: %s", f)
return 0, extensionerrors.ErrInvalidSettingsFileName
}
seqs = append(seqs, i)
}
sort.Sort(sort.Reverse(sort.IntSlice(seqs)))
return uint(seqs[0]), nil
}
}