Skip to content

Commit 4971852

Browse files
drafts of namespace ops
1 parent 5d6a8f2 commit 4971852

9 files changed

Lines changed: 530 additions & 1 deletion

File tree

src/api/pc_api.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,70 @@ func (api *PcApi) EnableNamespace(c *gin.Context) {
350350
c.JSON(http.StatusOK, results)
351351
}
352352

353+
// @Schemes
354+
// @Id PostNamespace
355+
// @Description Merge processes from a partial config; all must share the same namespace
356+
// @Tags Namespace
357+
// @Summary Post config fragment with processes (single namespace)
358+
// @Accept json
359+
// @Produce json
360+
// @Param processes body types.Processes true "Processes, all in the same namespace"
361+
// @Success 200 {object} map[string]string "Process -> status (added)"
362+
// @Success 207 {object} map[string]string "Process -> status with failures"
363+
// @Failure 400 {object} map[string]string
364+
// @Router /namespace [post]
365+
func (api *PcApi) PostNamespace(c *gin.Context) {
366+
var processes types.Processes
367+
if err := c.ShouldBindJSON(&processes); err != nil {
368+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
369+
return
370+
}
371+
if len(processes) == 0 {
372+
c.JSON(http.StatusBadRequest, gin.H{"error": "no processes provided"})
373+
return
374+
}
375+
// validation of single namespace is performed in AddNamespace
376+
status, err := api.project.AddNamespace(&processes)
377+
if err != nil {
378+
if len(status) == 0 {
379+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
380+
} else {
381+
c.JSON(http.StatusMultiStatus, status)
382+
}
383+
return
384+
}
385+
c.JSON(http.StatusOK, status)
386+
}
387+
388+
// @Schemes
389+
// @Id DeleteNamespace
390+
// @Description Delete all processes from current config in the given namespace
391+
// @Tags Namespace
392+
// @Summary Delete namespace processes
393+
// @Produce json
394+
// @Param name query string true "Namespace Name"
395+
// @Success 200 {object} map[string]string "Process -> status (removed)"
396+
// @Success 207 {object} map[string]string "Process -> status with failures"
397+
// @Failure 400 {object} map[string]string
398+
// @Router /namespace [delete]
399+
func (api *PcApi) DeleteNamespace(c *gin.Context) {
400+
ns := c.Query("name")
401+
if ns == "" {
402+
c.JSON(http.StatusBadRequest, gin.H{"error": "missing namespace name"})
403+
return
404+
}
405+
status, err := api.project.RemoveNamespace(ns)
406+
if err != nil {
407+
if len(status) == 0 {
408+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
409+
} else {
410+
c.JSON(http.StatusMultiStatus, status)
411+
}
412+
return
413+
}
414+
c.JSON(http.StatusOK, status)
415+
}
416+
353417
// @Schemes
354418
// @Id StartProcess
355419
// @Description Starts the process if the state is not 'running' or 'pending'

src/api/routes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ func InitRoutes(useLogger bool, handler *PcApi) *gin.Engine {
3737
r.PATCH("/namespace/stop/:name", handler.StopNamespace)
3838
r.PATCH("/namespace/disable/:name", handler.DisableNamespace)
3939
r.PATCH("/namespace/enable/:name", handler.EnableNamespace)
40+
r.POST("/namespace", handler.PostNamespace)
41+
r.DELETE("/namespace", handler.DeleteNamespace)
4042
r.POST("/process/start/:name", handler.StartProcess)
4143
r.POST("/process/restart/:name", handler.RestartProcess)
4244
r.POST("/project/stop", handler.ShutDownProject)

src/app/project_interface.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,11 @@ type IProject interface {
3737
UpdateProcess(updated *types.ProcessConfig) error
3838
ReloadProject() (map[string]string, error)
3939
TruncateProcessLogs(name string) error
40+
// Updates project config.
41+
// All processes must have same namespace.
42+
// Namespace already exists, error is returned.
43+
AddNamespace(processes *types.Processes) (map[string]string, error)
44+
// Updates project config.
45+
// If namespace does not exist, error is returned.
46+
RemoveNamespace(name string) (map[string]string, error)
4047
}

src/app/project_runner.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,74 @@ func (p *ProjectRunner) UpdateProcess(updated *types.ProcessConfig) error {
11391139
return nil
11401140
}
11411141

1142+
// AddNamespace adds processes that all belong to the same namespace.
1143+
// If the namespace already exists in the current project, an error is returned.
1144+
func (p *ProjectRunner) AddNamespace(processes *types.Processes) (map[string]string, error) {
1145+
status := make(map[string]string)
1146+
if processes == nil || len(*processes) == 0 {
1147+
return status, fmt.Errorf("no processes provided")
1148+
}
1149+
1150+
// Determine namespace from first process and validate uniformity
1151+
ns := ""
1152+
for _, pr := range *processes {
1153+
if ns == "" {
1154+
ns = pr.Namespace
1155+
} else if pr.Namespace != ns {
1156+
return status, fmt.Errorf("all processes must be in the same namespace")
1157+
}
1158+
}
1159+
// Check if namespace already exists
1160+
for _, exist := range p.project.Processes {
1161+
if exist.Namespace == ns {
1162+
return status, fmt.Errorf("namespace %s already exists", ns)
1163+
}
1164+
}
1165+
1166+
// Render, assign, and add all processes
1167+
tpl := templater.New(p.project.Vars)
1168+
for name, pr := range *processes {
1169+
if pr.Name == "" {
1170+
pr.Name = name
1171+
}
1172+
if pr.Namespace == "" {
1173+
pr.Namespace = ns
1174+
}
1175+
pr.ReplicaName = pr.CalculateReplicaName()
1176+
validateProbes(pr.LivenessProbe)
1177+
validateProbes(pr.ReadinessProbe)
1178+
tpl.RenderProcess(&pr)
1179+
pr.AssignProcessExecutableAndArgs(p.project.ShellConfig, p.project.GetElevatedShellArg())
1180+
p.addProcessAndRun(pr)
1181+
status[pr.ReplicaName] = types.ProcessUpdateAdded
1182+
}
1183+
return status, nil
1184+
}
1185+
1186+
// RemoveNamespace deletes all processes belonging to the given namespace.
1187+
func (p *ProjectRunner) RemoveNamespace(namespace string) (map[string]string, error) {
1188+
removed := make(map[string]string)
1189+
var errs []error
1190+
names := make([]string, 0)
1191+
1192+
// Collect names first to avoid mutating during iteration
1193+
for name, proc := range p.project.Processes {
1194+
if proc.Namespace == namespace {
1195+
names = append(names, name)
1196+
}
1197+
}
1198+
1199+
for _, name := range names {
1200+
if err := p.removeProcess(name); err != nil {
1201+
removed[name] = err.Error()
1202+
errs = append(errs, err)
1203+
} else {
1204+
removed[name] = types.ProcessUpdateRemoved
1205+
}
1206+
}
1207+
return removed, errors.Join(errs...)
1208+
}
1209+
11421210
func (p *ProjectRunner) prepareEnvCmds() {
11431211
for env, cmd := range p.project.EnvCommands {
11441212
output, err := runCmd(cmd)

src/client/client.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,5 +194,14 @@ func (p *PcClient) ReloadProject() (map[string]string, error) {
194194
}
195195

196196
func (p *PcClient) TruncateProcessLogs(name string) error {
197-
return p.truncateProcessLogs(name)
197+
return p.truncateProcessLogs(name)
198+
}
199+
200+
// New methods to satisfy app.IProject
201+
func (p *PcClient) AddNamespace(processes *types.Processes) (map[string]string, error) {
202+
return p.addNamespace(processes)
203+
}
204+
205+
func (p *PcClient) RemoveNamespace(namespace string) (map[string]string, error) {
206+
return p.removeNamespace(namespace)
198207
}

src/client/namespace.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package client
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"errors"
67
"fmt"
78
"github.com/rs/zerolog/log"
9+
"github.com/f1bonacc1/process-compose/src/types"
810
"net/http"
911
)
1012

@@ -61,3 +63,58 @@ func (p *PcClient) enableNamespace(name string) (map[string]string, error) {
6163
}
6264
return nil, errors.New(respErr.Error)
6365
}
66+
67+
func (p *PcClient) addNamespace(processes *types.Processes) (map[string]string, error) {
68+
url := fmt.Sprintf("http://%s/namespace", p.address)
69+
payload, err := json.Marshal(processes)
70+
if err != nil {
71+
return nil, err
72+
}
73+
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(payload))
74+
if err != nil {
75+
return nil, err
76+
}
77+
req.Header.Set("Content-Type", "application/json")
78+
resp, err := p.client.Do(req)
79+
if err != nil {
80+
return nil, err
81+
}
82+
defer resp.Body.Close()
83+
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusMultiStatus {
84+
status := map[string]string{}
85+
if err = json.NewDecoder(resp.Body).Decode(&status); err != nil {
86+
return status, err
87+
}
88+
return status, nil
89+
}
90+
var respErr pcError
91+
if err = json.NewDecoder(resp.Body).Decode(&respErr); err != nil {
92+
return nil, err
93+
}
94+
return nil, errors.New(respErr.Error)
95+
}
96+
97+
func (p *PcClient) removeNamespace(name string) (map[string]string, error) {
98+
url := fmt.Sprintf("http://%s/namespace?name=%s", p.address, name)
99+
req, err := http.NewRequest(http.MethodDelete, url, nil)
100+
if err != nil {
101+
return nil, err
102+
}
103+
resp, err := p.client.Do(req)
104+
if err != nil {
105+
return nil, err
106+
}
107+
defer resp.Body.Close()
108+
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusMultiStatus {
109+
status := map[string]string{}
110+
if err = json.NewDecoder(resp.Body).Decode(&status); err != nil {
111+
return status, err
112+
}
113+
return status, nil
114+
}
115+
var respErr pcError
116+
if err = json.NewDecoder(resp.Body).Decode(&respErr); err != nil {
117+
return nil, err
118+
}
119+
return nil, errors.New(respErr.Error)
120+
}

src/docs/docs.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,111 @@ const docTemplate = `{
4040
}
4141
}
4242
},
43+
"/namespace": {
44+
"post": {
45+
"description": "Merge processes from a partial config; all must share the same namespace",
46+
"consumes": [
47+
"application/json"
48+
],
49+
"produces": [
50+
"application/json"
51+
],
52+
"tags": [
53+
"Namespace"
54+
],
55+
"summary": "Post config fragment with processes (single namespace)",
56+
"operationId": "PostNamespace",
57+
"parameters": [
58+
{
59+
"description": "Processes, all in the same namespace",
60+
"name": "processes",
61+
"in": "body",
62+
"required": true,
63+
"schema": {
64+
"$ref": "#/definitions/types.Processes"
65+
}
66+
}
67+
],
68+
"responses": {
69+
"200": {
70+
"description": "Process -\u003e status (added)",
71+
"schema": {
72+
"type": "object",
73+
"additionalProperties": {
74+
"type": "string"
75+
}
76+
}
77+
},
78+
"207": {
79+
"description": "Process -\u003e status with failures",
80+
"schema": {
81+
"type": "object",
82+
"additionalProperties": {
83+
"type": "string"
84+
}
85+
}
86+
},
87+
"400": {
88+
"description": "Bad Request",
89+
"schema": {
90+
"type": "object",
91+
"additionalProperties": {
92+
"type": "string"
93+
}
94+
}
95+
}
96+
}
97+
},
98+
"delete": {
99+
"description": "Delete all processes from current config in the given namespace",
100+
"produces": [
101+
"application/json"
102+
],
103+
"tags": [
104+
"Namespace"
105+
],
106+
"summary": "Delete namespace processes",
107+
"operationId": "DeleteNamespace",
108+
"parameters": [
109+
{
110+
"type": "string",
111+
"description": "Namespace Name",
112+
"name": "name",
113+
"in": "query",
114+
"required": true
115+
}
116+
],
117+
"responses": {
118+
"200": {
119+
"description": "Process -\u003e status (removed)",
120+
"schema": {
121+
"type": "object",
122+
"additionalProperties": {
123+
"type": "string"
124+
}
125+
}
126+
},
127+
"207": {
128+
"description": "Process -\u003e status with failures",
129+
"schema": {
130+
"type": "object",
131+
"additionalProperties": {
132+
"type": "string"
133+
}
134+
}
135+
},
136+
"400": {
137+
"description": "Bad Request",
138+
"schema": {
139+
"type": "object",
140+
"additionalProperties": {
141+
"type": "string"
142+
}
143+
}
144+
}
145+
}
146+
}
147+
},
43148
"/namespace/disable/{name}": {
44149
"patch": {
45150
"description": "Disables all processes in the given namespace",
@@ -1259,6 +1364,12 @@ const docTemplate = `{
12591364
}
12601365
}
12611366
},
1367+
"types.Processes": {
1368+
"type": "object",
1369+
"additionalProperties": {
1370+
"$ref": "#/definitions/types.ProcessConfig"
1371+
}
1372+
},
12621373
"types.ProcessesState": {
12631374
"type": "object",
12641375
"properties": {

0 commit comments

Comments
 (0)