github.com/codefly-dev/core@v0.1.107/resources/workspace.go (about)

     1  package resources
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"path"
     7  	"path/filepath"
     8  
     9  	basev0 "github.com/codefly-dev/core/generated/go/base/v0"
    10  
    11  	"github.com/codefly-dev/core/templates"
    12  
    13  	actionsv0 "github.com/codefly-dev/core/generated/go/actions/v0"
    14  	"github.com/codefly-dev/core/shared"
    15  	"github.com/codefly-dev/core/wool"
    16  )
    17  
    18  const WorkspaceConfigurationName = "workspace.codefly.yaml"
    19  
    20  type Workspace struct {
    21  	Name string `yaml:"name"`
    22  
    23  	Description string `yaml:"description,omitempty"`
    24  
    25  	Layout string `yaml:"layout"`
    26  
    27  	// Modules in the Workspace
    28  	Modules []*ModuleReference `yaml:"modules,omitempty"`
    29  
    30  	// internal
    31  	dir string
    32  
    33  	// helper
    34  	layout Layout `yaml:"-"`
    35  }
    36  
    37  func (workspace *Workspace) Proto() (*basev0.Workspace, error) {
    38  	proto := &basev0.Workspace{
    39  		Name:        workspace.Name,
    40  		Description: workspace.Description,
    41  		Layout:      workspace.Layout,
    42  	}
    43  	if err := Validate(proto); err != nil {
    44  		return nil, err
    45  	}
    46  	return proto, nil
    47  }
    48  
    49  // Dir is the directory of the
    50  func (workspace *Workspace) Dir() string {
    51  	return workspace.dir
    52  }
    53  
    54  func NewWorkspace(ctx context.Context, name string, layout string) (*Workspace, error) {
    55  	w := wool.Get(ctx).In("New", wool.NameField(name))
    56  	workspace := &Workspace{Name: name, Layout: layout}
    57  	_, err := workspace.Proto()
    58  	if err != nil {
    59  		return nil, w.Wrapf(err, "cannot validate  name")
    60  	}
    61  	return workspace, nil
    62  }
    63  
    64  // CreateWorkspace creates a new Workspace
    65  func CreateWorkspace(ctx context.Context, action *actionsv0.NewWorkspace) (*Workspace, error) {
    66  	w := wool.Get(ctx).In("CreateWorkspace", wool.NameField(action.Name), wool.DirField(action.Path))
    67  
    68  	dir := path.Join(action.Path, action.Name)
    69  
    70  	exists, err := shared.DirectoryExists(ctx, dir)
    71  	if err != nil {
    72  		return nil, w.Wrapf(err, "cannot check directory")
    73  	}
    74  	if exists {
    75  		return nil, w.NewError(" directory already exists")
    76  	}
    77  
    78  	_, err = shared.CheckDirectoryOrCreate(ctx, dir)
    79  	if err != nil {
    80  		return nil, w.Wrapf(err, "cannot create  directory")
    81  	}
    82  
    83  	workspace, err := NewWorkspace(ctx, action.Name, action.Layout)
    84  	if err != nil {
    85  		return nil, w.Wrapf(err, "cannot create workspace")
    86  	}
    87  	workspace.WithDir(dir)
    88  
    89  	// Create layout
    90  	workspace.layout, err = NewLayout(ctx, workspace.Dir(), action.Layout, nil)
    91  	if err != nil {
    92  		return nil, w.Wrapf(err, "cannot create layout")
    93  	}
    94  
    95  	// Scaffold the workspace
    96  	template := fmt.Sprintf("templates/workspace/%s", workspace.Layout)
    97  	err = templates.CopyAndApply(ctx, shared.Embed(fs), template, workspace.dir, workspace)
    98  	if err != nil {
    99  		return nil, w.Wrapf(err, "cannot copy and apply template")
   100  	}
   101  	if workspace.Layout == LayoutKindFlat {
   102  		_, err = workspace.WithRootModule(ctx)
   103  		if err != nil {
   104  			return nil, w.Wrapf(err, "cannot create module for flat")
   105  		}
   106  	}
   107  
   108  	err = workspace.Save(ctx)
   109  	if err != nil {
   110  		return nil, w.Wrapf(err, "cannot save ")
   111  	}
   112  
   113  	return workspace, nil
   114  }
   115  
   116  func (workspace *Workspace) Save(ctx context.Context) error {
   117  	return workspace.SaveToDirUnsafe(ctx, workspace.Dir())
   118  }
   119  
   120  func (workspace *Workspace) SaveToDirUnsafe(ctx context.Context, dir string) error {
   121  	w := wool.Get(ctx).In("Save", wool.NameField(workspace.Name))
   122  	w.Debug("modules", wool.SliceCountField(workspace.Modules))
   123  	serialized, err := workspace.preSave(ctx)
   124  	if err != nil {
   125  		return w.Wrapf(err, "cannot pre-save ")
   126  	}
   127  	err = SaveToDir[Workspace](ctx, serialized, dir)
   128  	if err != nil {
   129  		return w.Wrapf(err, "cannot save ")
   130  	}
   131  	return nil
   132  }
   133  
   134  /*
   135  Loaders
   136  */
   137  
   138  // LoadWorkspaceFromDir loads a Workspace configuration from a directory
   139  func LoadWorkspaceFromDir(ctx context.Context, dir string) (*Workspace, error) {
   140  	w := wool.Get(ctx).In("LoadFromDir")
   141  	var err error
   142  	dir, err = shared.SolvePath(dir)
   143  	if err != nil {
   144  		return nil, w.Wrap(err)
   145  	}
   146  
   147  	workspace, err := LoadFromDir[Workspace](ctx, dir)
   148  	if err != nil {
   149  		return nil, w.Wrap(err)
   150  	}
   151  	workspace.dir = dir
   152  
   153  	err = workspace.postLoad(ctx)
   154  	if err != nil {
   155  		return nil, w.Wrap(err)
   156  	}
   157  	return workspace, nil
   158  }
   159  
   160  func FindWorkspaceUp(ctx context.Context) (*Workspace, error) {
   161  	w := wool.Get(ctx).In("LoadFromPath")
   162  	dir, err := FindUp[Workspace](ctx)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  	if dir == nil {
   167  		w.Debug("no  found from path")
   168  		return nil, nil
   169  	}
   170  	return LoadWorkspaceFromDir(ctx, *dir)
   171  }
   172  
   173  // LoadModuleFromReference loads an module from a reference
   174  func (workspace *Workspace) LoadModuleFromReference(ctx context.Context, ref *ModuleReference) (*Module, error) {
   175  	w := wool.Get(ctx).In("Workspace::LoadModuleFromReference", wool.NameField(ref.Name))
   176  	dir := workspace.ModulePath(ctx, ref)
   177  	mod, err := LoadModuleFromDirUnsafe(ctx, dir)
   178  	if err != nil {
   179  		return nil, w.Wrapf(err, "cannot load module")
   180  	}
   181  	return mod, nil
   182  }
   183  
   184  // LoadModuleFromName loads an module from a name
   185  func (workspace *Workspace) LoadModuleFromName(ctx context.Context, name string) (*Module, error) {
   186  	w := wool.Get(ctx).In("Workspace::LoadModuleFromName", wool.NameField(name))
   187  	for _, ref := range workspace.Modules {
   188  		if ReferenceMatch(ref.Name, name) {
   189  			return workspace.LoadModuleFromReference(ctx, ref)
   190  		}
   191  	}
   192  	var present []string
   193  	for _, ref := range workspace.Modules {
   194  		present = append(present, ref.Name)
   195  	}
   196  	return nil, w.NewError("cannot find module <%s> in  <%s>, present: %v", name, workspace.Name, present)
   197  }
   198  
   199  // LoadModules returns the modules in the
   200  func (workspace *Workspace) LoadModules(ctx context.Context) ([]*Module, error) {
   201  	w := wool.Get(ctx).In("Workspace::.LoadModules", wool.NameField(workspace.Name))
   202  	var modules []*Module
   203  	for _, ref := range workspace.Modules {
   204  		mod, err := workspace.LoadModuleFromReference(ctx, ref)
   205  		if err != nil {
   206  			return nil, w.Wrapf(err, "cannot load module: <%s>", ref.Name)
   207  		}
   208  		modules = append(modules, mod)
   209  	}
   210  	return modules, nil
   211  }
   212  
   213  // ModulesNames returns the names of the modules in the
   214  func (workspace *Workspace) ModulesNames() []string {
   215  	var names []string
   216  	for _, app := range workspace.Modules {
   217  		names = append(names, app.Name)
   218  	}
   219  	return names
   220  }
   221  
   222  // ModulePath returns the absolute path of an module
   223  // Cases for Reference.Dir
   224  // nil: relative path to  with name
   225  // rel: relative path
   226  // /abs: absolute path
   227  func (workspace *Workspace) ModulePath(ctx context.Context, ref *ModuleReference) string {
   228  	w := wool.Get(ctx).In("Workspace::ModulePath", wool.Field("module ref", ref))
   229  	if ref.PathOverride == nil {
   230  		p := workspace.layout.ModulePath(ref.Name)
   231  		w.Trace("no path override", wool.PathField(p))
   232  		return p
   233  	}
   234  	if filepath.IsAbs(*ref.PathOverride) {
   235  		return *ref.PathOverride
   236  	}
   237  	return path.Join(workspace.Dir(), *ref.PathOverride)
   238  }
   239  
   240  // postLoad ensures the workspace is valid after loading
   241  func (workspace *Workspace) postLoad(ctx context.Context) error {
   242  	w := wool.Get(ctx).In("Workspace::postLoad", wool.NameField(workspace.Name))
   243  	_, err := workspace.Proto()
   244  	if err != nil {
   245  		return w.Wrapf(err, "cannot validate proto")
   246  	}
   247  	if workspace.Layout == LayoutKindFlat {
   248  		workspace.Modules = []*ModuleReference{{Name: workspace.Name}}
   249  	}
   250  	workspace.layout, err = NewLayout(context.Background(), workspace.Dir(), workspace.Layout, nil)
   251  	if err != nil {
   252  		return w.Wrapf(err, "cannot create layout")
   253  	}
   254  	return err
   255  }
   256  
   257  func (workspace *Workspace) preSave(ctx context.Context) (*Workspace, error) {
   258  	w := wool.Get(ctx).In("preSave", wool.NameField(workspace.Name))
   259  	_, err := workspace.Proto()
   260  	if err != nil {
   261  		return nil, w.Wrapf(err, "cannot validate proto")
   262  	}
   263  	serialized := workspace.Clone()
   264  	if workspace.Layout == LayoutKindFlat {
   265  		serialized.Modules = nil
   266  	}
   267  	return serialized, nil
   268  }
   269  
   270  // ExistsModule returns true if the module exists in the
   271  func (workspace *Workspace) ExistsModule(name string) bool {
   272  	for _, app := range workspace.Modules {
   273  		if app.Name == name {
   274  			return true
   275  		}
   276  	}
   277  	return false
   278  }
   279  
   280  // AddModuleReference adds an module to the
   281  func (workspace *Workspace) AddModuleReference(modRef *ModuleReference) error {
   282  	for _, mod := range workspace.Modules {
   283  		if mod.Name == modRef.Name {
   284  			return nil
   285  		}
   286  	}
   287  	workspace.Modules = append(workspace.Modules, modRef)
   288  	return nil
   289  }
   290  
   291  // DeleteModule deletes an module from the
   292  func (workspace *Workspace) DeleteModule(ctx context.Context, name string) error {
   293  	w := wool.Get(ctx).In(".DeleteModule")
   294  	if !workspace.ExistsModule(name) {
   295  		return w.NewError("module <%s> does not exist in  <%s>", name, workspace.Name)
   296  	}
   297  	var modRefs []*ModuleReference
   298  	for _, modRef := range workspace.Modules {
   299  		if modRef.Name != name {
   300  			modRefs = append(modRefs, modRef)
   301  		}
   302  	}
   303  	workspace.Modules = modRefs
   304  	return workspace.Save(ctx)
   305  }
   306  
   307  // DeleteServiceDependencies deletes all service dependencies from a
   308  func (workspace *Workspace) DeleteServiceDependencies(ctx context.Context, ref *ServiceReference) error {
   309  	w := wool.Get(ctx).In("configurations.DeleteService", wool.NameField(ref.String()))
   310  	mods, err := workspace.LoadModules(ctx)
   311  	if err != nil {
   312  		return w.Wrapf(err, "cannot load services")
   313  	}
   314  	for _, mod := range mods {
   315  		err = mod.DeleteServiceDependencies(ctx, ref)
   316  		if err != nil {
   317  			return w.Wrapf(err, "cannot delete service dependencies")
   318  		}
   319  	}
   320  
   321  	return nil
   322  }
   323  
   324  // LoadService loads a service from a reference
   325  // returns NotFoundError if not found
   326  func (workspace *Workspace) LoadService(ctx context.Context, input *ServiceWithModule) (*Service, error) {
   327  	w := wool.Get(ctx).In("Workspace::LoadService", wool.NameField(input.Name))
   328  	mod, err := workspace.LoadModuleFromName(ctx, input.Module)
   329  	if err != nil {
   330  		return nil, w.Wrapf(err, "cannot load module")
   331  	}
   332  	return mod.LoadServiceFromName(ctx, input.Name)
   333  }
   334  
   335  func Reload(ctx context.Context, workspace *Workspace) (*Workspace, error) {
   336  	return LoadWorkspaceFromDir(ctx, workspace.Dir())
   337  }
   338  
   339  func (workspace *Workspace) LoadServices(ctx context.Context) ([]*Service, error) {
   340  	w := wool.Get(ctx).In("Workspace.LoadServices")
   341  	refs, err := workspace.LoadServiceWithModules(ctx)
   342  	if err != nil {
   343  		return nil, w.Wrapf(err, "cannot load service references")
   344  	}
   345  	var services []*Service
   346  	for _, ref := range refs {
   347  		svc, err := workspace.LoadService(ctx, ref)
   348  		if err != nil {
   349  			return nil, w.Wrapf(err, "cannot load service: %s", ref.Name)
   350  		}
   351  		services = append(services, svc)
   352  	}
   353  	return services, nil
   354  }
   355  
   356  func (workspace *Workspace) LoadServiceWithModules(ctx context.Context) ([]*ServiceWithModule, error) {
   357  	w := wool.Get(ctx).In("Workspace.LoadServices")
   358  	var services []*ServiceWithModule
   359  	for _, modRef := range workspace.Modules {
   360  		mod, err := workspace.LoadModuleFromReference(ctx, modRef)
   361  		if err != nil {
   362  			return nil, w.Wrapf(err, "cannot load module")
   363  		}
   364  		for _, svc := range mod.ServiceReferences {
   365  			services = append(services, &ServiceWithModule{Name: svc.Name, Module: mod.Name})
   366  		}
   367  	}
   368  	return services, nil
   369  }
   370  
   371  type NonUniqueServiceNameError struct {
   372  	name string
   373  }
   374  
   375  func (n NonUniqueServiceNameError) Error() string {
   376  	return fmt.Sprintf("service name %s is not unique in ", n.name)
   377  }
   378  
   379  // FindUniqueServiceAndModuleByName finds a service by name
   380  // returns ResourceNotFound error if not found
   381  func (workspace *Workspace) FindUniqueServiceAndModuleByName(ctx context.Context, name string) (*ServiceWithModule, error) {
   382  	w := wool.Get(ctx).In("Workspace::FindUniqueServiceByName", wool.NameField(name))
   383  	svcRef, err := ParseServiceWithOptionalModule(name)
   384  	if err != nil {
   385  		return nil, w.Wrapf(err, "cannot parse service name")
   386  	}
   387  	if svcRef.Module != "" {
   388  		return svcRef, nil
   389  	}
   390  	// We look at all the services and check if the name is unique
   391  	var found *ServiceWithModule
   392  	svcs, err := workspace.LoadServiceWithModules(ctx)
   393  	if err != nil {
   394  		return nil, w.Wrapf(err, "cannot load services")
   395  	}
   396  	for _, s := range svcs {
   397  		if s.Name == svcRef.Name {
   398  			if found != nil {
   399  				return nil, NonUniqueServiceNameError{name}
   400  			}
   401  			found = s
   402  		}
   403  	}
   404  	if found == nil {
   405  		return nil, shared.NewErrorResourceNotFound("service", name)
   406  	}
   407  	return found, nil
   408  }
   409  
   410  // FindUniqueServiceByName finds a service by name
   411  // returns ResourceNotFound error if not found
   412  func (workspace *Workspace) FindUniqueServiceByName(ctx context.Context, name string) (*Service, error) {
   413  	w := wool.Get(ctx).In("Workspace::FindUniqueServiceByName", wool.NameField(name))
   414  	unique, err := workspace.FindUniqueServiceAndModuleByName(ctx, name)
   415  	if err != nil {
   416  		return nil, w.Wrapf(err, "cannot find unique service")
   417  	}
   418  	svc, err := workspace.LoadService(ctx, unique)
   419  	if err != nil {
   420  		return nil, w.Wrapf(err, "cannot load service")
   421  	}
   422  	return svc, nil
   423  
   424  }
   425  
   426  // Valid checks if the workspace is valid
   427  func (workspace *Workspace) Valid() error {
   428  	if workspace.layout == nil {
   429  		return fmt.Errorf("layout is nil")
   430  	}
   431  	return nil
   432  }
   433  
   434  // WithDir sets the directory of the workspace
   435  func (workspace *Workspace) WithDir(dir string) {
   436  	workspace.dir = dir
   437  }
   438  
   439  // RootModule only applies to Flat layout
   440  func (workspace *Workspace) RootModule(ctx context.Context) (*Module, error) {
   441  	w := wool.Get(ctx).In("Workspace.RootModule")
   442  	if workspace.Layout != LayoutKindFlat {
   443  		return nil, w.NewError("root module only applies to flat layout")
   444  	}
   445  	return workspace.LoadModuleFromName(ctx, workspace.Name)
   446  }
   447  
   448  // Clone returns a copy of the workspace
   449  func (workspace *Workspace) Clone() *Workspace {
   450  	clone := *workspace
   451  	return &clone
   452  }