databricks-cli/cmd/labs/localcache/jsonfile.go

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

112 lines
2.7 KiB
Go
Raw Permalink Normal View History

package localcache
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/fs"
"net/url"
"os"
"path/filepath"
"time"
"github.com/databricks/cli/libs/log"
)
const (
userRW = 0o600
ownerRWXworldRX = 0o755
)
func NewLocalCache[T any](dir, name string, validity time.Duration) LocalCache[T] {
return LocalCache[T]{
dir: dir,
name: name,
validity: validity,
}
}
type LocalCache[T any] struct {
name string
dir string
validity time.Duration
zero T
}
func (r *LocalCache[T]) Load(ctx context.Context, refresh func() (T, error)) (T, error) {
Enable offline install of labs projects (#2049) ## Changes <!-- Summary of your changes that are easy to understand --> This PR makes changes to the labs code base to allow for offline installation of labs projects (like UCX). By passing a flag --offline=true, the code will skip checking for project versions and download code from GitHub and instead will look from the local installation folder. This cmd is useful in systems where there is internet restriction, the user should follow a set-up as follows: - install a labs project on a machine which has internet - zip and copy the file to the intended machine and - run databricks labs install <project name>--offline=true it will look for the code in the same install directory and if present load from there. Closes #1646 related to https://github.com/databrickslabs/ucx/issues/3418 ## Tests <!-- How is this tested? --> Added unit test case and tested. NO_CHANGELOG=true --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Pieter Noordhuis <pieter.noordhuis@databricks.com> Co-authored-by: Lennart Kats (databricks) <lennart.kats@databricks.com> Co-authored-by: Denis Bilenko <denis.bilenko@databricks.com> Co-authored-by: Julia Crawford (Databricks) <julia.crawford@databricks.com> Co-authored-by: Ilya Kuznetsov <ilya.kuznetsov@databricks.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Andrew Nester <andrew.nester@databricks.com> Co-authored-by: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Co-authored-by: shreyas-goenka <88374338+shreyas-goenka@users.noreply.github.com>
2025-03-10 10:01:17 +00:00
cached, err := r.LoadCache()
if errors.Is(err, fs.ErrNotExist) {
return r.refreshCache(ctx, refresh, r.zero)
} else if err != nil {
return r.zero, err
} else if time.Since(cached.Refreshed) > r.validity {
return r.refreshCache(ctx, refresh, cached.Data)
}
return cached.Data, nil
}
type cached[T any] struct {
// we don't use mtime of the file because it's easier to
// for testdata used in the unit tests to be somewhere far
// in the future and don't bother about switching the mtime bit.
Refreshed time.Time `json:"refreshed_at"`
Data T `json:"data"`
}
func (r *LocalCache[T]) refreshCache(ctx context.Context, refresh func() (T, error), offlineVal T) (T, error) {
data, err := refresh()
var urlError *url.Error
if errors.As(err, &urlError) {
log.Warnf(ctx, "System offline. Cannot refresh cache: %s", urlError)
return offlineVal, nil
}
if err != nil {
return r.zero, fmt.Errorf("refresh: %w", err)
}
return r.writeCache(ctx, data)
}
func (r *LocalCache[T]) writeCache(ctx context.Context, data T) (T, error) {
cached := &cached[T]{time.Now(), data}
raw, err := json.MarshalIndent(cached, "", " ")
if err != nil {
return r.zero, fmt.Errorf("json marshal: %w", err)
}
cacheFile := r.FileName()
err = os.WriteFile(cacheFile, raw, userRW)
if errors.Is(err, fs.ErrNotExist) {
cacheDir := filepath.Dir(cacheFile)
err := os.MkdirAll(cacheDir, ownerRWXworldRX)
if err != nil {
return r.zero, fmt.Errorf("create %s: %w", cacheDir, err)
}
err = os.WriteFile(cacheFile, raw, userRW)
if err != nil {
return r.zero, fmt.Errorf("retry save cache: %w", err)
}
return data, nil
} else if err != nil {
return r.zero, fmt.Errorf("save cache: %w", err)
}
return data, nil
}
func (r *LocalCache[T]) FileName() string {
return filepath.Join(r.dir, r.name+".json")
}
Enable offline install of labs projects (#2049) ## Changes <!-- Summary of your changes that are easy to understand --> This PR makes changes to the labs code base to allow for offline installation of labs projects (like UCX). By passing a flag --offline=true, the code will skip checking for project versions and download code from GitHub and instead will look from the local installation folder. This cmd is useful in systems where there is internet restriction, the user should follow a set-up as follows: - install a labs project on a machine which has internet - zip and copy the file to the intended machine and - run databricks labs install <project name>--offline=true it will look for the code in the same install directory and if present load from there. Closes #1646 related to https://github.com/databrickslabs/ucx/issues/3418 ## Tests <!-- How is this tested? --> Added unit test case and tested. NO_CHANGELOG=true --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Pieter Noordhuis <pieter.noordhuis@databricks.com> Co-authored-by: Lennart Kats (databricks) <lennart.kats@databricks.com> Co-authored-by: Denis Bilenko <denis.bilenko@databricks.com> Co-authored-by: Julia Crawford (Databricks) <julia.crawford@databricks.com> Co-authored-by: Ilya Kuznetsov <ilya.kuznetsov@databricks.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Andrew Nester <andrew.nester@databricks.com> Co-authored-by: Anton Nekipelov <226657+anton-107@users.noreply.github.com> Co-authored-by: shreyas-goenka <88374338+shreyas-goenka@users.noreply.github.com>
2025-03-10 10:01:17 +00:00
func (r *LocalCache[T]) LoadCache() (*cached[T], error) {
jsonFile := r.FileName()
raw, err := os.ReadFile(r.FileName())
if err != nil {
return nil, fmt.Errorf("read %s: %w", jsonFile, err)
}
var v cached[T]
err = json.Unmarshal(raw, &v)
if err != nil {
return nil, fmt.Errorf("parse %s: %w", jsonFile, err)
}
return &v, nil
}