5f23f29b3f
Signed-off-by: Solomon Hykes <sh.github.6811@hykes.org>
251 lines
5.1 KiB
Go
251 lines
5.1 KiB
Go
package dagger
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"sync"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
var (
|
|
ErrEnvironmentExist = errors.New("environment already exists")
|
|
ErrEnvironmentNotExist = errors.New("environment doesn't exist")
|
|
)
|
|
|
|
const (
|
|
defaultStoreRoot = "$HOME/.dagger/store"
|
|
)
|
|
|
|
type Store struct {
|
|
root string
|
|
|
|
l sync.RWMutex
|
|
|
|
// ID -> Environment
|
|
environments map[string]*EnvironmentState
|
|
|
|
// Name -> Environment
|
|
environmentsByName map[string]*EnvironmentState
|
|
|
|
// Path -> (ID->Environment)
|
|
environmentsByPath map[string]map[string]*EnvironmentState
|
|
|
|
// ID -> (Path->{})
|
|
pathsByEnvironmentID map[string]map[string]struct{}
|
|
}
|
|
|
|
func NewStore(root string) (*Store, error) {
|
|
store := &Store{
|
|
root: root,
|
|
environments: make(map[string]*EnvironmentState),
|
|
environmentsByName: make(map[string]*EnvironmentState),
|
|
environmentsByPath: make(map[string]map[string]*EnvironmentState),
|
|
pathsByEnvironmentID: make(map[string]map[string]struct{}),
|
|
}
|
|
return store, store.loadAll()
|
|
}
|
|
|
|
func DefaultStore() (*Store, error) {
|
|
if root := os.Getenv("DAGGER_STORE"); root != "" {
|
|
return NewStore(root)
|
|
}
|
|
|
|
return NewStore(os.ExpandEnv(defaultStoreRoot))
|
|
}
|
|
|
|
func (s *Store) environmentPath(name string) string {
|
|
// FIXME: rename to environment.json ?
|
|
return path.Join(s.root, name, "deployment.json")
|
|
}
|
|
|
|
func (s *Store) loadAll() error {
|
|
files, err := os.ReadDir(s.root)
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
for _, f := range files {
|
|
if !f.IsDir() {
|
|
continue
|
|
}
|
|
if err := s.loadEnvironment(f.Name()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) loadEnvironment(name string) error {
|
|
data, err := os.ReadFile(s.environmentPath(name))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var st EnvironmentState
|
|
if err := json.Unmarshal(data, &st); err != nil {
|
|
return err
|
|
}
|
|
s.indexEnvironment(&st)
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) syncEnvironment(r *EnvironmentState) error {
|
|
p := s.environmentPath(r.Name)
|
|
|
|
if err := os.MkdirAll(path.Dir(p), 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
data, err := json.MarshalIndent(r, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := os.WriteFile(p, data, 0600); err != nil {
|
|
return err
|
|
}
|
|
|
|
s.reindexEnvironment(r)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) indexEnvironment(r *EnvironmentState) {
|
|
s.environments[r.ID] = r
|
|
s.environmentsByName[r.Name] = r
|
|
|
|
mapPath := func(i Input) {
|
|
if i.Type != InputTypeDir {
|
|
return
|
|
}
|
|
if s.environmentsByPath[i.Dir.Path] == nil {
|
|
s.environmentsByPath[i.Dir.Path] = make(map[string]*EnvironmentState)
|
|
}
|
|
s.environmentsByPath[i.Dir.Path][r.ID] = r
|
|
|
|
if s.pathsByEnvironmentID[r.ID] == nil {
|
|
s.pathsByEnvironmentID[r.ID] = make(map[string]struct{})
|
|
}
|
|
s.pathsByEnvironmentID[r.ID][i.Dir.Path] = struct{}{}
|
|
}
|
|
|
|
mapPath(r.PlanSource)
|
|
for _, i := range r.Inputs {
|
|
mapPath(i.Value)
|
|
}
|
|
}
|
|
|
|
func (s *Store) deindexEnvironment(id string) {
|
|
r, ok := s.environments[id]
|
|
if !ok {
|
|
return
|
|
}
|
|
delete(s.environments, r.ID)
|
|
delete(s.environmentsByName, r.Name)
|
|
|
|
for p := range s.pathsByEnvironmentID[r.ID] {
|
|
delete(s.environmentsByPath[p], r.ID)
|
|
}
|
|
delete(s.pathsByEnvironmentID, r.ID)
|
|
}
|
|
|
|
func (s *Store) reindexEnvironment(r *EnvironmentState) {
|
|
s.deindexEnvironment(r.ID)
|
|
s.indexEnvironment(r)
|
|
}
|
|
|
|
func (s *Store) CreateEnvironment(ctx context.Context, st *EnvironmentState) error {
|
|
s.l.Lock()
|
|
defer s.l.Unlock()
|
|
|
|
if _, ok := s.environmentsByName[st.Name]; ok {
|
|
return fmt.Errorf("%s: %w", st.Name, ErrEnvironmentExist)
|
|
}
|
|
|
|
st.ID = uuid.New().String()
|
|
return s.syncEnvironment(st)
|
|
}
|
|
|
|
type UpdateOpts struct{}
|
|
|
|
func (s *Store) UpdateEnvironment(ctx context.Context, r *EnvironmentState, o *UpdateOpts) error {
|
|
s.l.Lock()
|
|
defer s.l.Unlock()
|
|
|
|
return s.syncEnvironment(r)
|
|
}
|
|
|
|
type DeleteOpts struct{}
|
|
|
|
func (s *Store) DeleteEnvironment(ctx context.Context, r *EnvironmentState, o *DeleteOpts) error {
|
|
s.l.Lock()
|
|
defer s.l.Unlock()
|
|
|
|
if err := os.Remove(s.environmentPath(r.Name)); err != nil {
|
|
return err
|
|
}
|
|
s.deindexEnvironment(r.ID)
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) LookupEnvironmentByID(ctx context.Context, id string) (*EnvironmentState, error) {
|
|
s.l.RLock()
|
|
defer s.l.RUnlock()
|
|
|
|
st, ok := s.environments[id]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%s: %w", id, ErrEnvironmentNotExist)
|
|
}
|
|
return st, nil
|
|
}
|
|
|
|
func (s *Store) LookupEnvironmentByName(ctx context.Context, name string) (*EnvironmentState, error) {
|
|
s.l.RLock()
|
|
defer s.l.RUnlock()
|
|
|
|
st, ok := s.environmentsByName[name]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%s: %w", name, ErrEnvironmentNotExist)
|
|
}
|
|
return st, nil
|
|
}
|
|
|
|
func (s *Store) LookupEnvironmentByPath(ctx context.Context, path string) ([]*EnvironmentState, error) {
|
|
s.l.RLock()
|
|
defer s.l.RUnlock()
|
|
|
|
res := []*EnvironmentState{}
|
|
|
|
environments, ok := s.environmentsByPath[path]
|
|
if !ok {
|
|
return res, nil
|
|
}
|
|
|
|
for _, d := range environments {
|
|
res = append(res, d)
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func (s *Store) ListEnvironments(ctx context.Context) ([]*EnvironmentState, error) {
|
|
s.l.RLock()
|
|
defer s.l.RUnlock()
|
|
|
|
environments := make([]*EnvironmentState, 0, len(s.environments))
|
|
|
|
for _, st := range s.environments {
|
|
environments = append(environments, st)
|
|
}
|
|
|
|
return environments, nil
|
|
}
|