Skip to content

Commit 569d10e

Browse files
feat: common inputs for all projects in a workspace (#98)
Signed-off-by: Harikrishnan Balagopal <[email protected]>
1 parent dbae7fc commit 569d10e

File tree

8 files changed

+731
-144
lines changed

8 files changed

+731
-144
lines changed

internal/common/constants.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,6 @@ const (
3131
APP_NAME_SHORT = "m2k"
3232
// SESSIONS_DIR is the name of the directory where the sessions are stored
3333
SESSIONS_DIR = "sessions"
34-
// PROJECTS_DIR is the name of the directory where the projects are stored
35-
PROJECTS_DIR = "projects"
36-
// METADATAS_DIR is the name of the directory where the metadata files are stored
37-
METADATAS_DIR = "metadata"
38-
// WORKSPACE_METADATAS_DIR is the name of the directory where the workspace metadata files are stored
39-
WORKSPACE_METADATAS_DIR = "workspaces"
4034
// LOGIN_PATH is the URL endpoint to start the login flow
4135
LOGIN_PATH = "/auth/login"
4236
// LOGIN_CALLBACK_PATH is the URL endpoint to finish the login flow

internal/filesystem/filesystem.go

Lines changed: 609 additions & 68 deletions
Large diffs are not rendered by default.

internal/filesystem/interface.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ type IFileSystem interface {
3737
ReadProject(workspaceId, projectId string) (types.Project, error)
3838
UpdateProject(workspaceId string, project types.Project) error
3939
DeleteProject(workspaceId, projectId string) error
40-
CreateProjectInput(workspaceId, projectId string, projInput types.ProjectInput, file io.Reader) error
41-
ReadProjectInput(workspaceId, projectId, projInputId string) (projInput types.ProjectInput, file io.Reader, err error)
42-
DeleteProjectInput(workspaceId, projectId, projInputId string) error
40+
CreateProjectInput(workspaceId, projectId string, projInput types.ProjectInput, file io.Reader, isCommon bool) error
41+
ReadProjectInput(workspaceId, projectId, projInputId string, isCommon bool) (projInput types.ProjectInput, file io.Reader, err error)
42+
DeleteProjectInput(workspaceId, projectId, projInputId string, isCommon bool) error
4343
StartPlanning(workspaceId, projectId string, debugMode bool) error
4444
ReadPlan(workspaceId, projectId string) (plan io.Reader, err error)
4545
UpdatePlan(workspaceId, projectId string, plan io.Reader) error

internal/move2kubeapi/handlers/handlers.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"encoding/json"
2121
"fmt"
2222
"net/http"
23-
"os"
2423
"path/filepath"
2524

2625
"github.com/konveyor/move2kube-api/internal/authserver"
@@ -60,11 +59,6 @@ func Setup() error {
6059
return fmt.Errorf("failed to make the data directory path %s absolute. Error: %q", common.Config.DataDir, err)
6160
}
6261
common.Config.DataDir = absDataDir
63-
projDir := filepath.Join(common.Config.DataDir, common.PROJECTS_DIR)
64-
logrus.Debugf("making the projects directory at %s", projDir)
65-
if err := os.MkdirAll(projDir, filesystem.DEFAULT_DIRECTORY_PERMISSIONS); err != nil {
66-
return fmt.Errorf("failed to make the projects directory at path %s . Error: %q", projDir, err)
67-
}
6862
logrus.Debug("creating the filesystem object")
6963
m2kFS = filesystem.NewFileSystem()
7064
if common.Config.AuthEnabled {

internal/move2kubeapi/handlers/inputs.go

Lines changed: 78 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package handlers
1919
import (
2020
"encoding/json"
2121
"io"
22+
"mime/multipart"
2223
"net/http"
2324
"path/filepath"
2425
"strings"
@@ -30,53 +31,82 @@ import (
3031
)
3132

3233
// HandleCreateProjectInput is the handler for creating a project input
33-
func HandleCreateProjectInput(w http.ResponseWriter, r *http.Request) {
34+
// isCommon: if true the input will be available for all the projects in the workspace
35+
func HandleCreateProjectInput(w http.ResponseWriter, r *http.Request, isCommon bool) {
3436
logrus := GetLogger(r)
3537
logrus.Trace("HandleCreateProjectInput start")
3638
defer logrus.Trace("HandleCreateProjectInput end")
3739
workspaceId := mux.Vars(r)[WORKSPACE_ID_ROUTE_VAR]
38-
projectId := mux.Vars(r)[PROJECT_ID_ROUTE_VAR]
39-
if !common.IsValidId(workspaceId) || !common.IsValidId(projectId) {
40-
logrus.Errorf("invalid id. Actual: %s %s", workspaceId, projectId)
41-
sendErrorJSON(w, "invalid id", http.StatusBadRequest)
40+
if !common.IsValidId(workspaceId) {
41+
logrus.Errorf("invalid workspace id. Actual: %s", workspaceId)
42+
sendErrorJSON(w, "invalid workspace id", http.StatusBadRequest)
4243
return
4344
}
45+
projectId := ""
46+
if !isCommon {
47+
projectId = mux.Vars(r)[PROJECT_ID_ROUTE_VAR]
48+
if !common.IsValidId(projectId) {
49+
logrus.Errorf("invalid project id. Actual: %s", projectId)
50+
sendErrorJSON(w, "invalid project id", http.StatusBadRequest)
51+
return
52+
}
53+
}
4454
r.Body = http.MaxBytesReader(w, r.Body, common.Config.MaxUploadSize)
4555
if err := r.ParseMultipartForm(common.Config.MaxUploadSize); err != nil {
4656
logrus.Errorf("failed to parse the request body as multipart/form-data. Error: %q", err)
4757
sendErrorJSON(w, "failed to parse the request body as multipart/form-data", http.StatusBadRequest)
4858
return
4959
}
50-
file, handler, err := r.FormFile("file")
60+
timestamp, _, err := common.GetTimestamp()
5161
if err != nil {
52-
logrus.Errorf("failed to get the file from the request body. Error: %q", err)
53-
sendErrorJSON(w, "failed to get the file from the request body", http.StatusBadRequest)
62+
logrus.Errorf("failed to get the timestamp. Error: %q", err)
63+
w.WriteHeader(http.StatusInternalServerError)
5464
return
5565
}
56-
defer file.Close()
5766
projType, err := types.ParseProjectInputType(r.FormValue("type"))
5867
if err != nil {
5968
logrus.Errorf("failed to parse the project input type. Error: %q", err)
6069
sendErrorJSON(w, "the input type is invalid", http.StatusBadRequest)
6170
return
6271
}
63-
timestamp, _, err := common.GetTimestamp()
64-
if err != nil {
65-
logrus.Errorf("failed to get the timestamp. Error: %q", err)
66-
w.WriteHeader(http.StatusInternalServerError)
67-
return
68-
}
69-
normName := filepath.Base(filepath.Clean(handler.Filename))
70-
normName = strings.TrimSuffix(normName, filepath.Ext(normName))
71-
normName, err = common.NormalizeName(normName)
72-
if err != nil {
73-
logrus.Errorf("failed to normalize the filename '%s'. Error: %q", handler.Filename, err)
74-
sendErrorJSON(w, "failed to normalize the filename. Please use a filename that has only alphanumeric and hyphen characters.", http.StatusBadRequest)
75-
return
72+
var file io.ReadCloser
73+
filename := ""
74+
normName := ""
75+
projInputId := uuid.NewString()
76+
if projType == types.ProjectInputReference {
77+
if isCommon {
78+
logrus.Errorf("cannot upload reference type input for workspaces")
79+
sendErrorJSON(w, "cannot upload reference type input for workspaces", http.StatusBadRequest)
80+
return
81+
}
82+
projInputId = r.FormValue("id")
83+
if !common.IsValidId(projInputId) {
84+
logrus.Errorf("the reference input id is invalid. Actual: %s", projInputId)
85+
sendErrorJSON(w, "the reference input id is invalid", http.StatusBadRequest)
86+
return
87+
}
88+
} else {
89+
var fileHeader *multipart.FileHeader
90+
file, fileHeader, err = r.FormFile("file")
91+
if err != nil {
92+
logrus.Errorf("failed to get the file from the request body. Error: %q", err)
93+
sendErrorJSON(w, "failed to get the file from the request body", http.StatusBadRequest)
94+
return
95+
}
96+
filename = fileHeader.Filename
97+
defer file.Close()
98+
normName = filepath.Base(filepath.Clean(filename))
99+
normName = strings.TrimSuffix(normName, filepath.Ext(normName))
100+
normName, err = common.NormalizeName(normName)
101+
if err != nil {
102+
logrus.Errorf("failed to normalize the filename '%s'. Error: %q", filename, err)
103+
sendErrorJSON(w, "failed to normalize the filename. Please use a filename that has only alphanumeric and hyphen characters.", http.StatusBadRequest)
104+
return
105+
}
76106
}
77-
projInput := types.ProjectInput{Metadata: types.Metadata{Id: uuid.NewString(), Name: handler.Filename, Description: r.FormValue("description"), Timestamp: timestamp}, Type: projType, NormalizedName: normName}
107+
projInput := types.ProjectInput{Metadata: types.Metadata{Id: projInputId, Name: filename, Description: r.FormValue("description"), Timestamp: timestamp}, Type: projType, NormalizedName: normName}
78108
logrus.Debug("trying to create a new input for the project", projectId, " with the details:", projInput)
79-
if err := m2kFS.CreateProjectInput(workspaceId, projectId, projInput, file); err != nil {
109+
if err := m2kFS.CreateProjectInput(workspaceId, projectId, projInput, file, isCommon); err != nil {
80110
logrus.Errorf("failed to create the project input. Error: %q", err)
81111
if _, ok := err.(types.ErrorDoesNotExist); ok {
82112
w.WriteHeader(http.StatusNotFound)
@@ -99,19 +129,26 @@ func HandleCreateProjectInput(w http.ResponseWriter, r *http.Request) {
99129
}
100130

101131
// HandleReadProjectInput is the handler for reading a project input
102-
func HandleReadProjectInput(w http.ResponseWriter, r *http.Request) {
132+
func HandleReadProjectInput(w http.ResponseWriter, r *http.Request, isCommon bool) {
103133
logrus := GetLogger(r)
104134
logrus.Trace("HandleReadProjectInput start")
105135
defer logrus.Trace("HandleReadProjectInput end")
106136
workspaceId := mux.Vars(r)[WORKSPACE_ID_ROUTE_VAR]
107137
projectId := mux.Vars(r)[PROJECT_ID_ROUTE_VAR]
108138
projInputId := mux.Vars(r)[PROJECT_INPUT_ID_ROUTE_VAR]
109-
if !common.IsValidId(workspaceId) || !common.IsValidId(projectId) || !common.IsValidId(projInputId) {
110-
logrus.Errorf("invalid id. Actual: %s %s %s", workspaceId, projectId, projInputId)
111-
sendErrorJSON(w, "invalid id", http.StatusBadRequest)
139+
if !common.IsValidId(workspaceId) || !common.IsValidId(projInputId) {
140+
logrus.Errorf("invalid workspace and/or project input id. Actual: %s %s", workspaceId, projInputId)
141+
sendErrorJSON(w, "invalid workspace and/or project input id", http.StatusBadRequest)
112142
return
113143
}
114-
projInput, file, err := m2kFS.ReadProjectInput(workspaceId, projectId, projInputId)
144+
if !isCommon {
145+
if !common.IsValidId(projectId) {
146+
logrus.Errorf("invalid project id. Actual: %s", projectId)
147+
sendErrorJSON(w, "invalid project id", http.StatusBadRequest)
148+
return
149+
}
150+
}
151+
projInput, file, err := m2kFS.ReadProjectInput(workspaceId, projectId, projInputId, isCommon)
115152
if err != nil {
116153
logrus.Errorf("failed to get the input with id %s for the project %s . Error: %q", projInputId, projectId, err)
117154
if _, ok := err.(types.ErrorDoesNotExist); ok {
@@ -132,19 +169,26 @@ func HandleReadProjectInput(w http.ResponseWriter, r *http.Request) {
132169
}
133170

134171
// HandleDeleteProjectInput is the handler for deleting a project input
135-
func HandleDeleteProjectInput(w http.ResponseWriter, r *http.Request) {
172+
func HandleDeleteProjectInput(w http.ResponseWriter, r *http.Request, isCommon bool) {
136173
logrus := GetLogger(r)
137174
logrus.Trace("HandleDeleteProjectInput start")
138175
defer logrus.Trace("HandleDeleteProjectInput end")
139176
workspaceId := mux.Vars(r)[WORKSPACE_ID_ROUTE_VAR]
140177
projectId := mux.Vars(r)[PROJECT_ID_ROUTE_VAR]
141178
projInputId := mux.Vars(r)[PROJECT_INPUT_ID_ROUTE_VAR]
142-
if !common.IsValidId(workspaceId) || !common.IsValidId(projectId) || !common.IsValidId(projInputId) {
143-
logrus.Errorf("invalid id. Actual: %s %s %s", workspaceId, projectId, projInputId)
144-
sendErrorJSON(w, "invalid id", http.StatusBadRequest)
179+
if !common.IsValidId(workspaceId) || !common.IsValidId(projInputId) {
180+
logrus.Errorf("invalid workspace and/or project input id. Actual: %s %s", workspaceId, projInputId)
181+
sendErrorJSON(w, "invalid workspace and/or project input id", http.StatusBadRequest)
145182
return
146183
}
147-
if err := m2kFS.DeleteProjectInput(workspaceId, projectId, projInputId); err != nil {
184+
if !isCommon {
185+
if !common.IsValidId(projectId) {
186+
logrus.Errorf("invalid project id. Actual: %s", projectId)
187+
sendErrorJSON(w, "invalid project id", http.StatusBadRequest)
188+
return
189+
}
190+
}
191+
if err := m2kFS.DeleteProjectInput(workspaceId, projectId, projInputId, isCommon); err != nil {
148192
logrus.Errorf("failed to delete the input %s of the project %s . Error: %q", projInputId, projectId, err)
149193
if _, ok := err.(types.ErrorDoesNotExist); ok {
150194
w.WriteHeader(http.StatusNotFound)

internal/move2kubeapi/handlers/workspaces.go

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ func HandleCreateWorkspace(w http.ResponseWriter, r *http.Request) {
105105
}
106106
reqWorkspace.Id = uuid.NewString()
107107
reqWorkspace.Timestamp = timestamp
108+
reqWorkspace.Inputs = map[string]types.ProjectInput{}
108109
reqWorkspace.ProjectIds = []string{}
109110
if err := m2kFS.CreateWorkspace(reqWorkspace); err != nil {
110111
logrus.Errorf("failed to create the workspace %+v . Error: %q", reqWorkspace, err)
@@ -204,33 +205,34 @@ func HandleUpdateWorkspace(w http.ResponseWriter, r *http.Request) {
204205
reqWorkspace.Id = workspaceId
205206
oldWork, err := m2kFS.ReadWorkspace(workspaceId)
206207
if err != nil {
207-
if _, ok := err.(types.ErrorDoesNotExist); ok {
208-
logrus.Infof("the workspace with id: %s does not exist. creating...", workspaceId)
209-
timestamp, _, err := common.GetTimestamp()
210-
if err != nil {
211-
logrus.Errorf("failed to get the timestamp. Error: %q", err)
212-
w.WriteHeader(http.StatusInternalServerError)
213-
return
214-
}
215-
reqWorkspace.Timestamp = timestamp
216-
reqWorkspace.ProjectIds = []string{}
217-
if err := m2kFS.CreateWorkspace(reqWorkspace); err != nil {
218-
if _, ok := err.(types.ErrorValidation); ok {
219-
sendErrorJSON(w, "the project given in the request body is invalid", http.StatusBadRequest)
220-
return
221-
}
222-
logrus.Errorf("failed to create the workspace. Error: %q", err)
223-
w.WriteHeader(http.StatusInternalServerError)
208+
if _, ok := err.(types.ErrorDoesNotExist); !ok {
209+
logrus.Errorf("failed to get the workspace with id: %s Error: %q", workspaceId, err)
210+
w.WriteHeader(http.StatusInternalServerError)
211+
return
212+
}
213+
logrus.Infof("the workspace with id: %s does not exist. creating...", workspaceId)
214+
timestamp, _, err := common.GetTimestamp()
215+
if err != nil {
216+
logrus.Errorf("failed to get the timestamp. Error: %q", err)
217+
w.WriteHeader(http.StatusInternalServerError)
218+
return
219+
}
220+
reqWorkspace.Timestamp = timestamp
221+
reqWorkspace.ProjectIds = []string{}
222+
if err := m2kFS.CreateWorkspace(reqWorkspace); err != nil {
223+
if _, ok := err.(types.ErrorValidation); ok {
224+
sendErrorJSON(w, "the project given in the request body is invalid", http.StatusBadRequest)
224225
return
225226
}
226-
w.WriteHeader(http.StatusCreated)
227+
logrus.Errorf("failed to create the workspace. Error: %q", err)
228+
w.WriteHeader(http.StatusInternalServerError)
227229
return
228230
}
229-
logrus.Errorf("failed to get the workspace with id: %s Error: %q", workspaceId, err)
230-
w.WriteHeader(http.StatusInternalServerError)
231+
w.WriteHeader(http.StatusCreated)
231232
return
232233
}
233234
reqWorkspace.ProjectIds = oldWork.ProjectIds
235+
reqWorkspace.Inputs = oldWork.Inputs
234236
if err := m2kFS.UpdateWorkspace(reqWorkspace); err != nil {
235237
logrus.Errorf("failed to update the workspace. Error: %q", err)
236238
w.WriteHeader(http.StatusInternalServerError)

internal/move2kubeapi/move2kubeapi.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,21 @@ func Serve() error {
8787
apiRouter.HandleFunc("/workspaces/{work-id}", handlers.HandleUpdateWorkspace).Methods("PUT")
8888
apiRouter.HandleFunc("/workspaces/{work-id}", handlers.HandleDeleteWorkspace).Methods("DELETE")
8989

90+
// workspace inputs
91+
apiRouter.HandleFunc("/workspaces/{work-id}/inputs", func(w http.ResponseWriter, r *http.Request) { handlers.HandleCreateProjectInput(w, r, true) }).Methods("POST")
92+
apiRouter.HandleFunc("/workspaces/{work-id}/inputs/{input-id}", func(w http.ResponseWriter, r *http.Request) { handlers.HandleReadProjectInput(w, r, true) }).Methods("GET")
93+
apiRouter.HandleFunc("/workspaces/{work-id}/inputs/{input-id}", func(w http.ResponseWriter, r *http.Request) { handlers.HandleDeleteProjectInput(w, r, true) }).Methods("DELETE")
94+
9095
// projects
9196
apiRouter.HandleFunc("/workspaces/{work-id}/projects", handlers.HandleListProjects).Methods("GET")
9297
apiRouter.HandleFunc("/workspaces/{work-id}/projects", handlers.HandleCreateProject).Methods("POST")
9398
apiRouter.HandleFunc("/workspaces/{work-id}/projects/{proj-id}", handlers.HandleReadProject).Methods("GET")
9499
apiRouter.HandleFunc("/workspaces/{work-id}/projects/{proj-id}", handlers.HandleDeleteProject).Methods("DELETE")
95100

96101
// project inputs
97-
apiRouter.HandleFunc("/workspaces/{work-id}/projects/{proj-id}/inputs", handlers.HandleCreateProjectInput).Methods("POST")
98-
apiRouter.HandleFunc("/workspaces/{work-id}/projects/{proj-id}/inputs/{input-id}", handlers.HandleReadProjectInput).Methods("GET")
99-
apiRouter.HandleFunc("/workspaces/{work-id}/projects/{proj-id}/inputs/{input-id}", handlers.HandleDeleteProjectInput).Methods("DELETE")
102+
apiRouter.HandleFunc("/workspaces/{work-id}/projects/{proj-id}/inputs", func(w http.ResponseWriter, r *http.Request) { handlers.HandleCreateProjectInput(w, r, false) }).Methods("POST")
103+
apiRouter.HandleFunc("/workspaces/{work-id}/projects/{proj-id}/inputs/{input-id}", func(w http.ResponseWriter, r *http.Request) { handlers.HandleReadProjectInput(w, r, false) }).Methods("GET")
104+
apiRouter.HandleFunc("/workspaces/{work-id}/projects/{proj-id}/inputs/{input-id}", func(w http.ResponseWriter, r *http.Request) { handlers.HandleDeleteProjectInput(w, r, false) }).Methods("DELETE")
100105

101106
// plan
102107
apiRouter.HandleFunc("/workspaces/{work-id}/projects/{proj-id}/plan", handlers.HandleStartPlanning).Methods("POST")

internal/types/types.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,8 @@ type UserInfo gocloak.UserInfo
248248
// Workspace holds information about a workspace
249249
type Workspace struct {
250250
Metadata
251-
ProjectIds []string `json:"project_ids,omitempty"`
251+
ProjectIds []string `json:"project_ids,omitempty"`
252+
Inputs map[string]ProjectInput `json:"inputs,omitempty"`
252253
}
253254

254255
// Project holds information about a project
@@ -342,6 +343,8 @@ const (
342343
ProjectStatusInputCustomizations ProjectStatus = "customizations"
343344
// ProjectStatusInputConfigs indicates the project has configs
344345
ProjectStatusInputConfigs ProjectStatus = "configs"
346+
// ProjectStatusInputReference indicates the project has references to workspace level inputs
347+
ProjectStatusInputReference ProjectStatus = "reference"
345348
// ProjectStatusPlanning indicates the project is currently generating a plan
346349
ProjectStatusPlanning ProjectStatus = "planning"
347350
// ProjectStatusPlan indicates the project has a plan
@@ -370,12 +373,14 @@ const (
370373
type ProjectInputType string
371374

372375
const (
373-
// ProjectInputSources is the type of project inputs that are folders containing source code
376+
// ProjectInputSources is the type for project inputs that are folders containing source code
374377
ProjectInputSources ProjectInputType = ProjectInputType(ProjectStatusInputSources)
375-
// ProjectInputCustomizations is the type of project inputs that are folders containing customization files
378+
// ProjectInputCustomizations is the type for project inputs that are folders containing customization files
376379
ProjectInputCustomizations ProjectInputType = ProjectInputType(ProjectStatusInputCustomizations)
377-
// ProjectInputConfigs is the type of project inputs that are config files
380+
// ProjectInputConfigs is the type for project inputs that are config files
378381
ProjectInputConfigs ProjectInputType = ProjectInputType(ProjectStatusInputConfigs)
382+
// ProjectInputReference is the type for project inputs that are references to workspace level inputs
383+
ProjectInputReference ProjectInputType = ProjectInputType(ProjectStatusInputReference)
379384
)
380385

381386
// ParseProjectInputType parses the string and returns a project input type if valid
@@ -387,6 +392,8 @@ func ParseProjectInputType(s string) (ProjectInputType, error) {
387392
return ProjectInputCustomizations, nil
388393
case string(ProjectInputConfigs):
389394
return ProjectInputConfigs, nil
395+
case string(ProjectInputReference):
396+
return ProjectInputReference, nil
390397
default:
391398
return "", fmt.Errorf("unknown project input type")
392399
}

0 commit comments

Comments
 (0)