diff --git a/pkg/server/handler/compose_library.go b/pkg/server/handler/compose_library.go index 9dacaff..0fac345 100644 --- a/pkg/server/handler/compose_library.go +++ b/pkg/server/handler/compose_library.go @@ -27,7 +27,7 @@ func (h *Handler) GetComposeProjectList(c echo.Context) error { return unprocessableEntity(c, queryGte1ExpectedError("s")) } - rows, totalRows, err := h.composeLibraryStore.GetList(uint(p), uint(s)) + rows, totalRows, err := h.localComposeLibraryStore.GetList(uint(p), uint(s)) if err != nil { panic(err) } @@ -36,13 +36,13 @@ func (h *Handler) GetComposeProjectList(c echo.Context) error { } func (h *Handler) CreateComposeProject(c echo.Context) error { - m := model.ComposeLibraryItem{} + m := model.LocalComposeLibraryItem{} r := &composeProjectCreateRequest{} if err := r.bind(c, &m); err != nil { return unprocessableEntity(c, err) } - if err := h.composeLibraryStore.Create(&m); err != nil { + if err := h.localComposeLibraryStore.Create(&m); err != nil { panic(err) } @@ -52,13 +52,13 @@ func (h *Handler) CreateComposeProject(c echo.Context) error { func (h *Handler) UpdateComposeProject(c echo.Context) error { projectName := c.Param("projectName") - m := model.ComposeLibraryItemUpdate{} + m := model.LocalComposeLibraryItemUpdate{} r := &composeProjectUpdateRequest{ProjectName: projectName} if err := r.bind(c, &m); err != nil { return unprocessableEntity(c, err) } - if err := h.composeLibraryStore.Update(&m); err != nil { + if err := h.localComposeLibraryStore.Update(&m); err != nil { panic(err) } @@ -68,7 +68,7 @@ func (h *Handler) UpdateComposeProject(c echo.Context) error { func (h *Handler) DeleteComposeProject(c echo.Context) error { projectName := c.Param("projectName") - if err := h.composeLibraryStore.DeleteByName(projectName); err != nil { + if err := h.localComposeLibraryStore.DeleteByName(projectName); err != nil { return unprocessableEntity(c, err) } @@ -78,7 +78,7 @@ func (h *Handler) DeleteComposeProject(c echo.Context) error { func (h *Handler) GetComposeProject(c echo.Context) error { projectName := c.Param("projectName") - m, err := h.composeLibraryStore.GetByName(projectName) + m, err := h.localComposeLibraryStore.GetByName(projectName) if err != nil { return notFound(c, "ComposeProject") } diff --git a/pkg/server/handler/handler.go b/pkg/server/handler/handler.go index 75f392c..1de2c2f 100644 --- a/pkg/server/handler/handler.go +++ b/pkg/server/handler/handler.go @@ -13,6 +13,7 @@ import ( type Handler struct { composeProjectsPath string + composeLibraryStore store.ComposeLibraryStore credentialStore store.CredentialStore environmentStore store.EnvironmentStore userStore store.UserStore @@ -21,7 +22,7 @@ type Handler struct { settingStore store.SettingStore variableStore store.VariableStore variableValueStore store.VariableValueStore - composeLibraryStore store.ComposeLibraryStore + localComposeLibraryStore store.LocalComposeLibraryStore } var ( @@ -30,6 +31,7 @@ var ( func NewHandler( composeProjectsPath string, + composeLibraryStore store.ComposeLibraryStore, credentialStore store.CredentialStore, environmentStore store.EnvironmentStore, userStore store.UserStore, @@ -38,10 +40,11 @@ func NewHandler( settingStore store.SettingStore, variableStore store.VariableStore, variableValueStore store.VariableValueStore, - composeLibraryStore store.ComposeLibraryStore, + localComposeLibraryStore store.LocalComposeLibraryStore, ) *Handler { return &Handler{ composeProjectsPath: composeProjectsPath, + composeLibraryStore: composeLibraryStore, credentialStore: credentialStore, environmentStore: environmentStore, userStore: userStore, @@ -50,7 +53,7 @@ func NewHandler( settingStore: settingStore, variableStore: variableStore, variableValueStore: variableValueStore, - composeLibraryStore: composeLibraryStore, + localComposeLibraryStore: localComposeLibraryStore, } } diff --git a/pkg/server/handler/node_compose.go b/pkg/server/handler/node_compose.go index 2ce3088..73d0d7a 100644 --- a/pkg/server/handler/node_compose.go +++ b/pkg/server/handler/node_compose.go @@ -191,7 +191,7 @@ func (h *Handler) GetNodeComposePull(c echo.Context) error { return unprocessableEntity(c, errors.New("Project not found")) } - clp, err := h.composeLibraryStore.GetByName(ncp.LibraryProjectName) + clp, err := h.localComposeLibraryStore.GetByName(ncp.LibraryProjectName) if err != nil { return unprocessableEntity(c, errors.New("Library Project not found")) } @@ -253,7 +253,7 @@ func (h *Handler) GetNodeComposeUp(c echo.Context) error { return unprocessableEntity(c, errors.New("Project not found")) } - clp, err := h.composeLibraryStore.GetByName(ncp.LibraryProjectName) + clp, err := h.localComposeLibraryStore.GetByName(ncp.LibraryProjectName) if err != nil { return unprocessableEntity(c, errors.New("Library Project not found")) } diff --git a/pkg/server/handler/request_compose_library.go b/pkg/server/handler/request_compose_library.go index 1d589f8..f0b9f52 100644 --- a/pkg/server/handler/request_compose_library.go +++ b/pkg/server/handler/request_compose_library.go @@ -11,7 +11,7 @@ type composeProjectCreateRequest struct { Definition string `json:"definition"` } -func (r *composeProjectCreateRequest) bind(c echo.Context, m *model.ComposeLibraryItem) error { +func (r *composeProjectCreateRequest) bind(c echo.Context, m *model.LocalComposeLibraryItem) error { if err := c.Bind(r); err != nil { return err } @@ -32,7 +32,7 @@ type composeProjectUpdateRequest struct { Definition string `json:"definition"` } -func (r *composeProjectUpdateRequest) bind(c echo.Context, m *model.ComposeLibraryItemUpdate) error { +func (r *composeProjectUpdateRequest) bind(c echo.Context, m *model.LocalComposeLibraryItemUpdate) error { if err := c.Bind(r); err != nil { return err } diff --git a/pkg/server/handler/response_compose_library.go b/pkg/server/handler/response_compose_library.go index 24ace43..aebee4e 100644 --- a/pkg/server/handler/response_compose_library.go +++ b/pkg/server/handler/response_compose_library.go @@ -11,11 +11,11 @@ type composeLibraryItem struct { Definition string `json:"definition"` } -func newComposeLibraryItemHead(m *model.ComposeLibraryItemHead) composeLibraryItemHead { +func newComposeLibraryItemHead(m *model.LocalComposeLibraryItemHead) composeLibraryItemHead { return composeLibraryItemHead{ProjectName: m.ProjectName} } -func newComposeLibraryItemHeadList(rows []model.ComposeLibraryItemHead) []composeLibraryItemHead { +func newComposeLibraryItemHeadList(rows []model.LocalComposeLibraryItemHead) []composeLibraryItemHead { headRows := make([]composeLibraryItemHead, len(rows)) for i, r := range rows { headRows[i] = newComposeLibraryItemHead(&r) @@ -23,6 +23,6 @@ func newComposeLibraryItemHeadList(rows []model.ComposeLibraryItemHead) []compos return headRows } -func newComposeLibraryItem(m *model.ComposeLibraryItem) composeLibraryItem { +func newComposeLibraryItem(m *model.LocalComposeLibraryItem) composeLibraryItem { return composeLibraryItem{ProjectName: m.ProjectName, Definition: m.Definition} } diff --git a/pkg/server/model/compose_library_item.go b/pkg/server/model/compose_library_item.go new file mode 100644 index 0000000..093931f --- /dev/null +++ b/pkg/server/model/compose_library_item.go @@ -0,0 +1,27 @@ +package model + +// Local: This is not a DB model +type LocalComposeLibraryItemHead struct { + ProjectName string +} + +type LocalComposeLibraryItem struct { + ProjectName string + Definition string +} + +type LocalComposeLibraryItemUpdate struct { + ProjectName string + NewProjectName string + Definition string +} + +// Remote: This is a DB model +type ComposeLibraryItem struct { + Id uint + CredentialId *uint + Credential *Credential + ProjectName string + Type string // github + Url string +} diff --git a/pkg/server/model/composelibraryitem.go b/pkg/server/model/composelibraryitem.go deleted file mode 100644 index a381541..0000000 --- a/pkg/server/model/composelibraryitem.go +++ /dev/null @@ -1,18 +0,0 @@ -package model - -// This is not a DB model - -type ComposeLibraryItemHead struct { - ProjectName string -} - -type ComposeLibraryItem struct { - ProjectName string - Definition string -} - -type ComposeLibraryItemUpdate struct { - ProjectName string - NewProjectName string - Definition string -} diff --git a/pkg/server/server.go b/pkg/server/server.go index 7689db8..09a42ad 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -53,6 +53,7 @@ func (s *Server) Init(dbConnectionString string, dataPath string, logLevel strin } db.AutoMigrate( + &model.ComposeLibraryItem{}, &model.Credential{}, &model.Environment{}, &model.Node{}, @@ -68,6 +69,7 @@ func (s *Server) Init(dbConnectionString string, dataPath string, logLevel strin h := handler.NewHandler( composeProjectsPath, + store.NewSqlComposeLibraryStore(db), store.NewSqlCredentialStore(db), store.NewSqlEnvironmentStore(db), store.NewSqlUserStore(db), diff --git a/pkg/server/store/compose_library.go b/pkg/server/store/compose_library.go index b9e8954..5c264ba 100644 --- a/pkg/server/store/compose_library.go +++ b/pkg/server/store/compose_library.go @@ -2,185 +2,89 @@ package store import ( "errors" - "fmt" - "os" - "path/filepath" "github.com/productiveops/dokemon/pkg/server/model" - "github.com/rs/zerolog/log" "gorm.io/gorm" ) -type FileSystemComposeLibraryStore struct { +type SqlComposeLibraryStore struct { db *gorm.DB - composeLibraryPath string } -func NewFileSystemComposeLibraryStore(db *gorm.DB, composeLibraryPath string) *FileSystemComposeLibraryStore { - return &FileSystemComposeLibraryStore{ +func NewSqlComposeLibraryStore(db *gorm.DB) *SqlComposeLibraryStore { + return &SqlComposeLibraryStore{ db: db, - composeLibraryPath: composeLibraryPath, } } -func (s *FileSystemComposeLibraryStore) Create(m *model.ComposeLibraryItem) error { - p := filepath.Join(s.composeLibraryPath, m.ProjectName) - - if _, err := os.Stat(p); errors.Is(err, os.ErrNotExist) { - err := os.MkdirAll(p, 0755) - if err != nil { - return err - } - - filename := filepath.Join(p, "compose.yaml") - - f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) - if err != nil { - return err - } - - f.WriteString(m.Definition) - if err := f.Close(); err != nil { - return err - } +func (s *SqlComposeLibraryStore) Create(m *model.ComposeLibraryItem) error { + return s.db.Create(m).Error +} - return nil - } else { - return errors.New("Another project with this name already exists.") - } +func (s *SqlComposeLibraryStore) Update(m *model.ComposeLibraryItem) error { + return s.db.Save(m).Error } -func (s *FileSystemComposeLibraryStore) Update(m *model.ComposeLibraryItemUpdate) error { - composeProjectDirPath := filepath.Join(s.composeLibraryPath, m.ProjectName) - _, err := os.ReadDir(filepath.Join(composeProjectDirPath)) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - return errors.New("Project does not exist") +func (s *SqlComposeLibraryStore) GetById(id uint) (*model.ComposeLibraryItem, error) { + var m model.ComposeLibraryItem + + if err := s.db.First(&m, id).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil } else { - return err + return nil, err } } + return &m, nil +} - if m.ProjectName != m.NewProjectName { - newComposeProjectDirPath := filepath.Join(s.composeLibraryPath, m.NewProjectName) - _, err := os.Stat(filepath.Join(newComposeProjectDirPath)) - if err == nil { - return errors.New("Another project with this name exist") - } - - err = os.Rename(composeProjectDirPath, newComposeProjectDirPath) - if err != nil { - log.Error().Err(err).Msg("Error while renaming compose project directory") - return err - } - - composeProjectDirPath = newComposeProjectDirPath - - r := s.db.Exec("update node_compose_projects set library_project_name = ? where library_project_name = ?", m.NewProjectName, m.ProjectName) - if r.Error != nil { - log.Error().Err(err).Msg("Error while renaming compose project references") - return err - } - } +func (s *SqlComposeLibraryStore) Exists(id uint) (bool, error) { + var count int64 - composeProjectFilePath := filepath.Join(composeProjectDirPath, "compose.yaml") - _, err = os.Stat(composeProjectFilePath) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - return errors.New("Compose definition does not exist") - } else { - return err - } + if err := s.db.Model(&model.ComposeLibraryItem{}).Where("id = ?", id).Count(&count).Error; err != nil { + return false, err } - f, err := os.OpenFile(composeProjectFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) - if err != nil { - return err - } + return count > 0, nil +} - f.WriteString(m.Definition) - if err := f.Close(); err != nil { +func (s *SqlComposeLibraryStore) DeleteById(id uint) error { + if err := s.db.Delete(&model.ComposeLibraryItem{}, id).Error; err != nil { return err } return nil } -func (s *FileSystemComposeLibraryStore) GetByName(projectName string) (*model.ComposeLibraryItem, error) { - composeProjectDirPath := filepath.Join(s.composeLibraryPath, projectName) - _, err := os.ReadDir(filepath.Join(composeProjectDirPath)) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - return nil, errors.New("Project does not exist") - } else { - return nil, err - } - } - - composeProjectFilePath := filepath.Join(composeProjectDirPath, "compose.yaml") - _, err = os.Stat(composeProjectFilePath) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - return nil, errors.New("Compose definition does not exist") - } else { - return nil, err - } - } +func (s *SqlComposeLibraryStore) GetList(pageNo, pageSize uint) ([]model.ComposeLibraryItem, int64, error) { + var ( + l []model.ComposeLibraryItem + count int64 + ) - definitionBytes, err := os.ReadFile(composeProjectFilePath) - if err != nil { - return nil, err - } + s.db.Model(&l).Count(&count) + s.db.Offset(int((pageNo - 1) * pageSize)).Limit(int(pageSize)).Order("name asc").Find(&l) - return &model.ComposeLibraryItem{ProjectName: projectName, Definition: string(definitionBytes)}, nil + return l, count, nil } -func (s *FileSystemComposeLibraryStore) DeleteByName(projectName string) error { - composeProjectDirPath := filepath.Join(s.composeLibraryPath, projectName) - _, err := os.ReadDir(filepath.Join(composeProjectDirPath)) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - return errors.New("Project does not exist") - } else { - return err - } - } - +func (s *SqlComposeLibraryStore) IsUniqueName(name string) (bool, error) { var count int64 - err = s.db.Model(&model.NodeComposeProject{}).Where("library_project_name = ?", projectName).Count(&count).Error - if err != nil { - return err - } - if count > 0 { - return errors.New(fmt.Sprintf("Definition is in use by %d projects and cannot be deleted", count)) - } - - err = os.RemoveAll(composeProjectDirPath) - if err != nil { - return err + if err := s.db.Model(&model.ComposeLibraryItem{}).Where("name = ? COLLATE NOCASE", name).Count(&count).Error; err != nil { + return false, err } - return nil + return count == 0, nil } -func (s *FileSystemComposeLibraryStore) GetList(pageNo, pageSize uint) ([]model.ComposeLibraryItemHead, int64, error) { - entries, err := os.ReadDir(s.composeLibraryPath) - if err != nil { - return nil, 0, err - } +func (s *SqlComposeLibraryStore) IsUniqueNameExcludeItself(name string, id uint) (bool, error) { + var count int64 - composeItemHeads := make([]model.ComposeLibraryItemHead, len(entries)) - for i, entry := range entries { - composeItemHeads[i] = model.ComposeLibraryItemHead{ProjectName: entry.Name()} + if err := s.db.Model(&model.ComposeLibraryItem{}).Where("name = ? COLLATE NOCASE and id <> ?", name, id).Count(&count).Error; err != nil { + return false, err } - - startIndex := (pageNo - 1) * pageSize - endIndex := startIndex + pageSize - if endIndex > uint(len(composeItemHeads)) { - endIndex = uint(len(composeItemHeads)) - } - return composeItemHeads[startIndex:endIndex], int64(len(entries)), nil -} + return count == 0, nil +} diff --git a/pkg/server/store/compose_library_local.go b/pkg/server/store/compose_library_local.go new file mode 100644 index 0000000..21374c6 --- /dev/null +++ b/pkg/server/store/compose_library_local.go @@ -0,0 +1,186 @@ +package store + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/productiveops/dokemon/pkg/server/model" + + "github.com/rs/zerolog/log" + "gorm.io/gorm" +) + +type FileSystemComposeLibraryStore struct { + db *gorm.DB + composeLibraryPath string +} + +func NewFileSystemComposeLibraryStore(db *gorm.DB, composeLibraryPath string) *FileSystemComposeLibraryStore { + return &FileSystemComposeLibraryStore{ + db: db, + composeLibraryPath: composeLibraryPath, + } +} + +func (s *FileSystemComposeLibraryStore) Create(m *model.LocalComposeLibraryItem) error { + p := filepath.Join(s.composeLibraryPath, m.ProjectName) + + if _, err := os.Stat(p); errors.Is(err, os.ErrNotExist) { + err := os.MkdirAll(p, 0755) + if err != nil { + return err + } + + filename := filepath.Join(p, "compose.yaml") + + f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + if err != nil { + return err + } + + f.WriteString(m.Definition) + if err := f.Close(); err != nil { + return err + } + + return nil + } else { + return errors.New("Another project with this name already exists.") + } +} + +func (s *FileSystemComposeLibraryStore) Update(m *model.LocalComposeLibraryItemUpdate) error { + composeProjectDirPath := filepath.Join(s.composeLibraryPath, m.ProjectName) + _, err := os.ReadDir(filepath.Join(composeProjectDirPath)) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return errors.New("Project does not exist") + } else { + return err + } + } + + if m.ProjectName != m.NewProjectName { + newComposeProjectDirPath := filepath.Join(s.composeLibraryPath, m.NewProjectName) + _, err := os.Stat(filepath.Join(newComposeProjectDirPath)) + if err == nil { + return errors.New("Another project with this name exist") + } + + err = os.Rename(composeProjectDirPath, newComposeProjectDirPath) + if err != nil { + log.Error().Err(err).Msg("Error while renaming compose project directory") + return err + } + + composeProjectDirPath = newComposeProjectDirPath + + r := s.db.Exec("update node_compose_projects set library_project_name = ? where library_project_name = ?", m.NewProjectName, m.ProjectName) + if r.Error != nil { + log.Error().Err(err).Msg("Error while renaming compose project references") + return err + } + } + + composeProjectFilePath := filepath.Join(composeProjectDirPath, "compose.yaml") + _, err = os.Stat(composeProjectFilePath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return errors.New("Compose definition does not exist") + } else { + return err + } + } + + f, err := os.OpenFile(composeProjectFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + if err != nil { + return err + } + + f.WriteString(m.Definition) + if err := f.Close(); err != nil { + return err + } + + return nil +} + +func (s *FileSystemComposeLibraryStore) GetByName(projectName string) (*model.LocalComposeLibraryItem, error) { + composeProjectDirPath := filepath.Join(s.composeLibraryPath, projectName) + _, err := os.ReadDir(filepath.Join(composeProjectDirPath)) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, errors.New("Project does not exist") + } else { + return nil, err + } + } + + composeProjectFilePath := filepath.Join(composeProjectDirPath, "compose.yaml") + _, err = os.Stat(composeProjectFilePath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, errors.New("Compose definition does not exist") + } else { + return nil, err + } + } + + definitionBytes, err := os.ReadFile(composeProjectFilePath) + if err != nil { + return nil, err + } + + return &model.LocalComposeLibraryItem{ProjectName: projectName, Definition: string(definitionBytes)}, nil +} + +func (s *FileSystemComposeLibraryStore) DeleteByName(projectName string) error { + composeProjectDirPath := filepath.Join(s.composeLibraryPath, projectName) + _, err := os.ReadDir(filepath.Join(composeProjectDirPath)) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return errors.New("Project does not exist") + } else { + return err + } + } + + var count int64 + err = s.db.Model(&model.NodeComposeProject{}).Where("library_project_name = ?", projectName).Count(&count).Error + if err != nil { + return err + } + + if count > 0 { + return errors.New(fmt.Sprintf("Definition is in use by %d projects and cannot be deleted", count)) + } + + err = os.RemoveAll(composeProjectDirPath) + if err != nil { + return err + } + + return nil +} + +func (s *FileSystemComposeLibraryStore) GetList(pageNo, pageSize uint) ([]model.LocalComposeLibraryItemHead, int64, error) { + entries, err := os.ReadDir(s.composeLibraryPath) + if err != nil { + return nil, 0, err + } + + composeItemHeads := make([]model.LocalComposeLibraryItemHead, len(entries)) + for i, entry := range entries { + composeItemHeads[i] = model.LocalComposeLibraryItemHead{ProjectName: entry.Name()} + } + + startIndex := (pageNo - 1) * pageSize + endIndex := startIndex + pageSize + if endIndex > uint(len(composeItemHeads)) { + endIndex = uint(len(composeItemHeads)) + } + return composeItemHeads[startIndex:endIndex], int64(len(entries)), nil +} + diff --git a/pkg/server/store/interfaces.go b/pkg/server/store/interfaces.go index c8553b8..0ab0f4c 100644 --- a/pkg/server/store/interfaces.go +++ b/pkg/server/store/interfaces.go @@ -56,12 +56,24 @@ type SettingStore interface { Exists(id string) (bool, error) } +type LocalComposeLibraryStore interface { + Create(m *model.LocalComposeLibraryItem) error + Update(m *model.LocalComposeLibraryItemUpdate) error + GetByName(projectName string) (*model.LocalComposeLibraryItem, error) + DeleteByName(projectName string) error + GetList(pageNo, pageSize uint) ([]model.LocalComposeLibraryItemHead, int64, error) +} + type ComposeLibraryStore interface { Create(m *model.ComposeLibraryItem) error - Update(m *model.ComposeLibraryItemUpdate) error - GetByName(projectName string) (*model.ComposeLibraryItem, error) - DeleteByName(projectName string) error - GetList(pageNo, pageSize uint) ([]model.ComposeLibraryItemHead, int64, error) + Update(m *model.ComposeLibraryItem) error + GetById(id uint) (*model.ComposeLibraryItem, error) + GetList(pageNo, pageSize uint) ([]model.ComposeLibraryItem, int64, error) + DeleteById(id uint) error + Exists(id uint) (bool, error) + + IsUniqueName(name string) (bool, error) + IsUniqueNameExcludeItself(name string, id uint) (bool, error) } type CredentialStore interface { diff --git a/web/src/app/compose-library/compose-library-items.tsx b/web/src/app/compose-library/compose-library-items.tsx index 8411e3c..0001ef3 100644 --- a/web/src/app/compose-library/compose-library-items.tsx +++ b/web/src/app/compose-library/compose-library-items.tsx @@ -36,6 +36,12 @@ export default function ComposeLibraryItems() { > Create +