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 }