Skip to content

Commit

Permalink
cache implementation, add config settings
Browse files Browse the repository at this point in the history
  • Loading branch information
patinthehat committed Aug 2, 2023
1 parent 1924fab commit 6d9f665
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 24 deletions.
16 changes: 12 additions & 4 deletions lib/app/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ type WorkflowSettings struct {
Defaults *WorkflowSettingsDefaults `yaml:"defaults"`
ExitOnChecksumMismatch bool `yaml:"exit-on-checksum-mismatch"`
DotEnvFiles []string `yaml:"dotenv"`
Domains struct {
Cache struct {
TtlMinutes int `yaml:"ttl-minutes"`
} `yaml:"cache"`
Domains struct {
Allowed []string `yaml:"allowed"`
} `yaml:"domains"`
}
Expand Down Expand Up @@ -165,7 +168,7 @@ func (wi *WorkflowInclude) ValidateChecksum(contents string) (bool, error) {

if checksumContents != "" {
hashUrl = url
wi.Workflow.Cache.Set(url, checksumContents, 5)
wi.Workflow.Cache.Set(url, checksumContents, wi.Workflow.Settings.Cache.TtlMinutes)
fmt.Printf("using non-cached checksum file %s\n", url)
break
}
Expand Down Expand Up @@ -315,7 +318,8 @@ func (workflow *StackupWorkflow) reversePreconditions(items []*Precondition) []*
}

func (workflow *StackupWorkflow) Initialize() {
workflow.Cache = cache.CreateCache()
workflow.Cache = cache.CreateCache(utils.GetProjectName())

// generate uuids for each task as the initial step, as other code below relies on a uuid existing
for _, task := range workflow.Tasks {
task.Uuid = utils.GenerateTaskUuid()
Expand All @@ -342,6 +346,10 @@ func (workflow *StackupWorkflow) Initialize() {
}
}

if workflow.Settings.Cache.TtlMinutes <= 0 {
workflow.Settings.Cache.TtlMinutes = 5
}

if len(workflow.Settings.DotEnvFiles) == 0 {
workflow.Settings.DotEnvFiles = []string{".env"}
}
Expand Down Expand Up @@ -445,7 +453,7 @@ func (workflow *StackupWorkflow) ProcessInclude(include *WorkflowInclude) bool {
}

include.Hash = checksums.CalculateSha256Hash(include.Contents)
workflow.Cache.Set(include.DisplayName(), include.Contents, 5)
workflow.Cache.Set(include.DisplayName(), include.Contents, workflow.Settings.Cache.TtlMinutes)
}

// fmt.Printf("value: %v\n", workflow.Cache.Get(include.DisplayName()))
Expand Down
101 changes: 81 additions & 20 deletions lib/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,24 @@ import (
)

type Cache struct {
Db *bolt.DB
Enabled bool
ProjectName string
Path string
Filename string
Db *bolt.DB
Enabled bool
Name string
Path string
Filename string
}

func CreateCache() *Cache {
result := Cache{Enabled: false}
// The function creates and initializes a cache object.
func CreateCache(name string) *Cache {
result := Cache{Name: name, Enabled: false}
result.Init()

return &result
}

func EnsureConfigDirExists(dirName string) (string, error) {
// Get the user's home directory
// The function ensures that a directory with a given name exists in the user's home directory and
// returns the path to the directory.
func ensureConfigDirExists(dirName string) (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", err
Expand All @@ -45,16 +47,20 @@ func EnsureConfigDirExists(dirName string) (string, error) {
return configDir, nil
}

// The `Init` function in the `Cache` struct is used to initialize the cache by setting up the
// necessary configurations and opening the database connection. Here's a breakdown of what it does:
func (c *Cache) Init() {
if c.Db != nil {
return
}

c.Path, _ = EnsureConfigDirExists(".stackup")
c.Path, _ = ensureConfigDirExists(".stackup")
c.Filename = filepath.Join(c.Path, "stackup.db")
c.ProjectName = path.Base(utils.WorkingDir())
if c.Name == "" {
c.Name = path.Base(utils.WorkingDir())
}

db, err := bolt.Open(c.Filename, 0600, bolt.DefaultOptions)
db, err := bolt.Open(c.Filename, 0644, bolt.DefaultOptions)
if err != nil {
c.Enabled = false
c.Db = nil
Expand All @@ -65,26 +71,33 @@ func (c *Cache) Init() {

// create a new project bucket if it doesn't exist
c.Db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte(c.ProjectName))
_, err := tx.CreateBucketIfNotExists([]byte(c.Name))
if err != nil {
return fmt.Errorf("create bucket: %s", err)
}
return nil
})

c.Enabled = true
c.purgeExpired()
}

// The `Get` function in the `Cache` struct is used to retrieve the value of a cache entry with a given
// key. It takes a `key` parameter (string) and returns the corresponding value (string).
func (c *Cache) Get(key string) string {
var result string
// expiresAt := c.GetExpiresAt(key + "_expires_at")

// if expiresAt != nil && expiresAt.IsPast() {
// return nil
// }
if !c.Has(key) {
return ""
}

if c.IsExpired(key) {
c.purgeExpired()
return ""
}

c.Db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(c.ProjectName))
b := tx.Bucket([]byte(c.Name))
bytes := b.Get([]byte(key))
result = string(bytes)
return nil
Expand All @@ -93,17 +106,45 @@ func (c *Cache) Get(key string) string {
return result
}

// The `purgeExpired` function in the `Cache` struct is used to remove any cache entries that have
// expired. It iterates through all the keys in the cache bucket and checks if each key has expired
// using the `IsExpired` function. If a key is expired, it is deleted from the cache bucket. This
// function ensures that expired cache entries are automatically removed from the cache to free up
// space and maintain cache integrity.
func (c *Cache) purgeExpired() {
c.Db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(c.Name))
cur := b.Cursor()

for k, _ := cur.First(); k != nil; k, _ = cur.Next() {
if c.IsExpired(string(k)) {
b.Delete(k)
}
}

return nil
})
}

// The `GetExpiresAt` function in the `Cache` struct is used to retrieve the expiration time of a cache
// entry with a given key.
func (c *Cache) GetExpiresAt(key string) *carbon.Carbon {
value := c.Get(key + "_expires_at")
time := carbon.Parse(value, "America/New_York")
return &time
}

// The `GetHash` function in the `Cache` struct is used to retrieve the hash value stored in the cache
// for a given key. It takes a `key` parameter (string) and returns the corresponding hash value
// (string). It calls the `Get` function to retrieve the value of the cache entry with the given key
// appended with "_hash".
func (c *Cache) GetHash(key string) string {
value := c.Get(key + "_hash")
return value
}

// The `IsExpired` function in the `Cache` struct is used to check if a cache entry with a given key
// has expired.
func (c *Cache) IsExpired(key string) bool {
expiresAt := c.GetExpiresAt(key)
if expiresAt == nil {
Expand All @@ -112,9 +153,17 @@ func (c *Cache) IsExpired(key string) bool {
return expiresAt.IsPast()
}

// The `HashMatches` function in the `Cache` struct is used to check if the hash value stored in the
// cache for a given key matches a provided hash value.
func (c *Cache) HashMatches(key string, hash string) bool {
return c.GetHash(key) == hash
}

// The `Set` function in the `Cache` struct is used to set a cache entry with a given key and value. It
// takes three parameters: `key` (string), `value` (any), and `ttlMinutes` (int).
func (c *Cache) Set(key string, value any, ttlMinutes int) {
c.Db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(c.ProjectName))
b := tx.Bucket([]byte(c.Name))

if value == nil {
b.Delete([]byte(key))
Expand All @@ -133,6 +182,18 @@ func (c *Cache) Set(key string, value any, ttlMinutes int) {
})
}

// The `Has` function in the `Cache` struct is used to check if a cache entry with a given key exists
// and is not expired. It calls the `Get` function to retrieve the value of the cache entry with the
// given key and checks if the value is not empty (`c.Get(key) != ""`) and if the cache entry is not
// expired (`!c.IsExpired(key)`). If both conditions are true, it returns `true`, indicating that the
// cache entry exists and is valid. Otherwise, it returns `false`.
func (c *Cache) Has(key string) bool {
return c.Get(key) != ""
return c.Get(key) != "" && !c.IsExpired(key)
}

// The `Remove` function in the `Cache` struct is used to remove a cache entry with a given key. It
// calls the `Set` function with a `value` parameter of `nil` and a `ttlMinutes` parameter of `0`. This
// effectively sets the cache entry to be empty and expired, effectively removing it from the cache.
func (c *Cache) Remove(key string) {
c.Set(key, nil, 0)
}
4 changes: 4 additions & 0 deletions lib/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,3 +340,7 @@ func GetUrlHostAndPath(urlStr string) string {

return parsedUrl.Host + parsedUrl.Path
}

func GetProjectName() string {
return path.Base(WorkingDir())
}

0 comments on commit 6d9f665

Please sign in to comment.