Refactor image storage: centralize in data/images, use UUIDs for filename, improve SaveImageToStorage and RemoveFromStorage functions
- commit
- cf1462d4f84048704d9b55c7ac2d71dc3900f53e
- author
- bamen · · 2024-08-27 17:21 UTC
- parent
- 8cf9b31
- signature
- unsigned
6 files · +103 -105
A build-dev.sh +18 -0
| @@ -0,0 +1,18 @@ | |||
| 1 | + | #!/bin/bash | |
| 2 | + | ||
| 3 | + | set -e | |
| 4 | + | ||
| 5 | + | REPO="ghcr.io/bnema/gordon" | |
| 6 | + | TAG="dev" | |
| 7 | + | DIST_DIR="./dist" | |
| 8 | + | ||
| 9 | + | # Ensure dist directory exists | |
| 10 | + | mkdir -p $DIST_DIR | |
| 11 | + | ||
| 12 | + | # Clean up previous builds | |
| 13 | + | rm -f $DIST_DIR/* | |
| 14 | + | ||
| 15 | + | # Build Go binaries for multiple platforms with CGO_ENABLED=0 | |
| 16 | + | echo "Building Go binaries..." | |
| 17 | + | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o $DIST_DIR/gordon-linux-amd64 ./cmd/cli | |
| 18 | + | CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o $DIST_DIR/gordon-linux-arm64 ./cmd/cli | |
| @@ -0,0 +1,18 @@ | |||||
| 1 | + | #!/bin/bash | |||
| 2 | + | ||||
| 3 | + | set -e | |||
| 4 | + | ||||
| 5 | + | REPO="ghcr.io/bnema/gordon" | |||
| 6 | + | TAG="dev" | |||
| 7 | + | DIST_DIR="./dist" | |||
| 8 | + | ||||
| 9 | + | # Ensure dist directory exists | |||
| 10 | + | mkdir -p $DIST_DIR | |||
| 11 | + | ||||
| 12 | + | # Clean up previous builds | |||
| 13 | + | rm -f $DIST_DIR/* | |||
| 14 | + | ||||
| 15 | + | # Build Go binaries for multiple platforms with CGO_ENABLED=0 | |||
| 16 | + | echo "Building Go binaries..." | |||
| 17 | + | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o $DIST_DIR/gordon-linux-amd64 ./cmd/cli | |||
| 18 | + | CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o $DIST_DIR/gordon-linux-arm64 ./cmd/cli | |||
M internal/cli/handler/http.go +11 -39
| @@ -48,8 +48,8 @@ func SendHTTPRequest(a *cli.App, rp *common.RequestPayload, method string, endpo | |||
| 48 | 48 | var req *http.Request | |
| 49 | 49 | var err error | |
| 50 | 50 | ||
| 51 | - | // Handle deploy type payload | |
| 52 | - | if rp.Type == "deploy" { | |
| 51 | + | switch rp.Type { | |
| 52 | + | case "deploy": | |
| 53 | 53 | deployPayload, ok := rp.Payload.(common.DeployPayload) | |
| 54 | 54 | if !ok { | |
| 55 | 55 | return nil, fmt.Errorf("invalid payload type for deploy") | |
| @@ -66,22 +66,7 @@ func SendHTTPRequest(a *cli.App, rp *common.RequestPayload, method string, endpo | |||
| 66 | 66 | req.Header.Set("X-Target-Domain", deployPayload.TargetDomain) | |
| 67 | 67 | req.Header.Set("X-Image-Name", deployPayload.ImageName) | |
| 68 | 68 | ||
| 69 | - | client := &http.Client{} | |
| 70 | - | resp, err := client.Do(req) | |
| 71 | - | if err != nil { | |
| 72 | - | return nil, fmt.Errorf("failed to send request: %w", err) | |
| 73 | - | } | |
| 74 | - | defer resp.Body.Close() | |
| 75 | - | ||
| 76 | - | body, err := io.ReadAll(resp.Body) | |
| 77 | - | if err != nil { | |
| 78 | - | return nil, fmt.Errorf("failed to read response body: %w", err) | |
| 79 | - | } | |
| 80 | - | return &Response{Body: body}, nil | |
| 81 | - | } | |
| 82 | - | ||
| 83 | - | // Handle other types of payloads | |
| 84 | - | if rp.Type != "push" { | |
| 69 | + | case "push": | |
| 85 | 70 | pushPayload, ok := rp.Payload.(common.PushPayload) | |
| 86 | 71 | if !ok { | |
| 87 | 72 | return nil, fmt.Errorf("invalid payload type for push") | |
| @@ -96,35 +81,21 @@ func SendHTTPRequest(a *cli.App, rp *common.RequestPayload, method string, endpo | |||
| 96 | 81 | req.Header.Set("Content-Type", "application/octet-stream") | |
| 97 | 82 | req.Header.Set("X-Image-Name", pushPayload.ImageName) | |
| 98 | 83 | ||
| 99 | - | client := &http.Client{} | |
| 100 | - | resp, err := client.Do(req) | |
| 84 | + | default: // This will handle "ping" and any other types | |
| 85 | + | jsonPayload, err := json.Marshal(rp) | |
| 101 | 86 | if err != nil { | |
| 102 | - | return nil, fmt.Errorf("failed to send request: %w", err) | |
| 87 | + | return nil, fmt.Errorf("failed to marshal payload: %w", err) | |
| 103 | 88 | } | |
| 104 | 89 | ||
| 105 | - | defer resp.Body.Close() | |
| 106 | - | ||
| 107 | - | body, err := io.ReadAll(resp.Body) | |
| 90 | + | req, err = http.NewRequest(method, apiUrl+endpoint, bytes.NewBuffer(jsonPayload)) | |
| 108 | 91 | if err != nil { | |
| 109 | - | return nil, fmt.Errorf("failed to read response body: %w", err) | |
| 92 | + | return nil, fmt.Errorf("failed to create new JSON request: %w", err) | |
| 110 | 93 | } | |
| 111 | 94 | ||
| 112 | - | return &Response{Body: body}, nil | |
| 113 | - | } | |
| 114 | - | ||
| 115 | - | jsonPayload, err := json.Marshal(rp) | |
| 116 | - | if err != nil { | |
| 117 | - | return nil, fmt.Errorf("failed to marshal payload: %w", err) | |
| 118 | - | } | |
| 119 | - | ||
| 120 | - | req, err = http.NewRequest(method, apiUrl+endpoint, bytes.NewBuffer(jsonPayload)) | |
| 121 | - | if err != nil { | |
| 122 | - | return nil, fmt.Errorf("failed to create new JSON request: %w", err) | |
| 95 | + | setAuthRequestHeaders(req, token) | |
| 96 | + | req.Header.Set("Content-Type", "application/json") | |
| 123 | 97 | } | |
| 124 | 98 | ||
| 125 | - | setAuthRequestHeaders(req, token) | |
| 126 | - | req.Header.Set("Content-Type", "application/json") | |
| 127 | - | ||
| 128 | 99 | client := &http.Client{} | |
| 129 | 100 | resp, err := client.Do(req) | |
| 130 | 101 | if err != nil { | |
| @@ -136,5 +107,6 @@ func SendHTTPRequest(a *cli.App, rp *common.RequestPayload, method string, endpo | |||
| 136 | 107 | if err != nil { | |
| 137 | 108 | return nil, fmt.Errorf("failed to read response body: %w", err) | |
| 138 | 109 | } | |
| 110 | + | ||
| 139 | 111 | return &Response{Http: resp, Body: body, StatusCode: resp.StatusCode}, nil | |
| 140 | 112 | } | |
| @@ -48,8 +48,8 @@ func SendHTTPRequest(a *cli.App, rp *common.RequestPayload, method string, endpo | |||||
| 48 | var req *http.Request | 48 | var req *http.Request | ||
| 49 | var err error | 49 | var err error | ||
| 50 | 50 | ||||
| 51 | - | // Handle deploy type payload | 51 | + | switch rp.Type { |
| 52 | - | if rp.Type == "deploy" { | 52 | + | case "deploy": |
| 53 | deployPayload, ok := rp.Payload.(common.DeployPayload) | 53 | deployPayload, ok := rp.Payload.(common.DeployPayload) | ||
| 54 | if !ok { | 54 | if !ok { | ||
| 55 | return nil, fmt.Errorf("invalid payload type for deploy") | 55 | return nil, fmt.Errorf("invalid payload type for deploy") | ||
| @@ -66,22 +66,7 @@ func SendHTTPRequest(a *cli.App, rp *common.RequestPayload, method string, endpo | |||||
| 66 | req.Header.Set("X-Target-Domain", deployPayload.TargetDomain) | 66 | req.Header.Set("X-Target-Domain", deployPayload.TargetDomain) | ||
| 67 | req.Header.Set("X-Image-Name", deployPayload.ImageName) | 67 | req.Header.Set("X-Image-Name", deployPayload.ImageName) | ||
| 68 | 68 | ||||
| 69 | - | client := &http.Client{} | 69 | + | case "push": |
| 70 | - | resp, err := client.Do(req) | |||
| 71 | - | if err != nil { | |||
| 72 | - | return nil, fmt.Errorf("failed to send request: %w", err) | |||
| 73 | - | } | |||
| 74 | - | defer resp.Body.Close() | |||
| 75 | - | ||||
| 76 | - | body, err := io.ReadAll(resp.Body) | |||
| 77 | - | if err != nil { | |||
| 78 | - | return nil, fmt.Errorf("failed to read response body: %w", err) | |||
| 79 | - | } | |||
| 80 | - | return &Response{Body: body}, nil | |||
| 81 | - | } | |||
| 82 | - | ||||
| 83 | - | // Handle other types of payloads | |||
| 84 | - | if rp.Type != "push" { | |||
| 85 | pushPayload, ok := rp.Payload.(common.PushPayload) | 70 | pushPayload, ok := rp.Payload.(common.PushPayload) | ||
| 86 | if !ok { | 71 | if !ok { | ||
| 87 | return nil, fmt.Errorf("invalid payload type for push") | 72 | return nil, fmt.Errorf("invalid payload type for push") | ||
| @@ -96,35 +81,21 @@ func SendHTTPRequest(a *cli.App, rp *common.RequestPayload, method string, endpo | |||||
| 96 | req.Header.Set("Content-Type", "application/octet-stream") | 81 | req.Header.Set("Content-Type", "application/octet-stream") | ||
| 97 | req.Header.Set("X-Image-Name", pushPayload.ImageName) | 82 | req.Header.Set("X-Image-Name", pushPayload.ImageName) | ||
| 98 | 83 | ||||
| 99 | - | client := &http.Client{} | 84 | + | default: // This will handle "ping" and any other types |
| 100 | - | resp, err := client.Do(req) | 85 | + | jsonPayload, err := json.Marshal(rp) |
| 101 | if err != nil { | 86 | if err != nil { | ||
| 102 | - | return nil, fmt.Errorf("failed to send request: %w", err) | 87 | + | return nil, fmt.Errorf("failed to marshal payload: %w", err) |
| 103 | } | 88 | } | ||
| 104 | 89 | ||||
| 105 | - | defer resp.Body.Close() | 90 | + | req, err = http.NewRequest(method, apiUrl+endpoint, bytes.NewBuffer(jsonPayload)) |
| 106 | - | ||||
| 107 | - | body, err := io.ReadAll(resp.Body) | |||
| 108 | if err != nil { | 91 | if err != nil { | ||
| 109 | - | return nil, fmt.Errorf("failed to read response body: %w", err) | 92 | + | return nil, fmt.Errorf("failed to create new JSON request: %w", err) |
| 110 | } | 93 | } | ||
| 111 | 94 | ||||
| 112 | - | return &Response{Body: body}, nil | 95 | + | setAuthRequestHeaders(req, token) |
| 113 | - | } | 96 | + | req.Header.Set("Content-Type", "application/json") |
| 114 | - | ||||
| 115 | - | jsonPayload, err := json.Marshal(rp) | |||
| 116 | - | if err != nil { | |||
| 117 | - | return nil, fmt.Errorf("failed to marshal payload: %w", err) | |||
| 118 | - | } | |||
| 119 | - | ||||
| 120 | - | req, err = http.NewRequest(method, apiUrl+endpoint, bytes.NewBuffer(jsonPayload)) | |||
| 121 | - | if err != nil { | |||
| 122 | - | return nil, fmt.Errorf("failed to create new JSON request: %w", err) | |||
| 123 | } | 97 | } | ||
| 124 | 98 | ||||
| 125 | - | setAuthRequestHeaders(req, token) | |||
| 126 | - | req.Header.Set("Content-Type", "application/json") | |||
| 127 | - | ||||
| 128 | client := &http.Client{} | 99 | client := &http.Client{} | ||
| 129 | resp, err := client.Do(req) | 100 | resp, err := client.Do(req) | ||
| 130 | if err != nil { | 101 | if err != nil { | ||
| @@ -136,5 +107,6 @@ func SendHTTPRequest(a *cli.App, rp *common.RequestPayload, method string, endpo | |||||
| 136 | if err != nil { | 107 | if err != nil { | ||
| 137 | return nil, fmt.Errorf("failed to read response body: %w", err) | 108 | return nil, fmt.Errorf("failed to read response body: %w", err) | ||
| 138 | } | 109 | } | ||
| 110 | + | ||||
| 139 | return &Response{Http: resp, Body: body, StatusCode: resp.StatusCode}, nil | 111 | return &Response{Http: resp, Body: body, StatusCode: resp.StatusCode}, nil | ||
| 140 | } | 112 | } | ||
M internal/cli/handler/pingrequest.go +4 -13
| @@ -18,30 +18,21 @@ type PingResponse struct { | |||
| 18 | 18 | func PerformPingRequest(a *cli.App) (PingResponse, error) { | |
| 19 | 19 | var pingResp PingResponse | |
| 20 | 20 | ||
| 21 | - | data := map[string]interface{}{"message": "ping"} | |
| 22 | - | payload, err := common.NewPingPayload(data) | |
| 23 | - | if err != nil { | |
| 24 | - | return pingResp, fmt.Errorf("failed to create new ping payload: %w", err) | |
| 25 | - | } | |
| 26 | - | ||
| 27 | - | // Create a RequestPayload and populate it | |
| 21 | + | pingPayload := common.PingPayload{Message: "ping"} | |
| 28 | 22 | reqPayload := common.RequestPayload{ | |
| 29 | 23 | Type: "ping", | |
| 30 | - | Payload: payload, | |
| 24 | + | Payload: pingPayload, | |
| 31 | 25 | } | |
| 32 | 26 | ||
| 33 | 27 | resp, err := SendHTTPRequest(a, &reqPayload, "GET", "/ping") | |
| 34 | 28 | if err != nil { | |
| 35 | - | return pingResp, fmt.Errorf("failed to reach backend") | |
| 29 | + | return pingResp, fmt.Errorf("failed to reach server: %w", err) | |
| 36 | 30 | } | |
| 37 | 31 | ||
| 38 | 32 | defer resp.Http.Body.Close() | |
| 39 | 33 | ||
| 40 | 34 | if resp.StatusCode != http.StatusOK { | |
| 41 | - | return PingResponse{ | |
| 42 | - | Uptime: "", | |
| 43 | - | Version: "", | |
| 44 | - | }, fmt.Errorf("expected status code 200, got %d", resp.StatusCode) | |
| 35 | + | return PingResponse{}, fmt.Errorf("expected status code 200, got %d", resp.StatusCode) | |
| 45 | 36 | } | |
| 46 | 37 | ||
| 47 | 38 | // Unmarshal the JSON response into the PingResponse struct | |
| @@ -18,30 +18,21 @@ type PingResponse struct { | |||||
| 18 | func PerformPingRequest(a *cli.App) (PingResponse, error) { | 18 | func PerformPingRequest(a *cli.App) (PingResponse, error) { | ||
| 19 | var pingResp PingResponse | 19 | var pingResp PingResponse | ||
| 20 | 20 | ||||
| 21 | - | data := map[string]interface{}{"message": "ping"} | 21 | + | pingPayload := common.PingPayload{Message: "ping"} |
| 22 | - | payload, err := common.NewPingPayload(data) | |||
| 23 | - | if err != nil { | |||
| 24 | - | return pingResp, fmt.Errorf("failed to create new ping payload: %w", err) | |||
| 25 | - | } | |||
| 26 | - | ||||
| 27 | - | // Create a RequestPayload and populate it | |||
| 28 | reqPayload := common.RequestPayload{ | 22 | reqPayload := common.RequestPayload{ | ||
| 29 | Type: "ping", | 23 | Type: "ping", | ||
| 30 | - | Payload: payload, | 24 | + | Payload: pingPayload, |
| 31 | } | 25 | } | ||
| 32 | 26 | ||||
| 33 | resp, err := SendHTTPRequest(a, &reqPayload, "GET", "/ping") | 27 | resp, err := SendHTTPRequest(a, &reqPayload, "GET", "/ping") | ||
| 34 | if err != nil { | 28 | if err != nil { | ||
| 35 | - | return pingResp, fmt.Errorf("failed to reach backend") | 29 | + | return pingResp, fmt.Errorf("failed to reach server: %w", err) |
| 36 | } | 30 | } | ||
| 37 | 31 | ||||
| 38 | defer resp.Http.Body.Close() | 32 | defer resp.Http.Body.Close() | ||
| 39 | 33 | ||||
| 40 | if resp.StatusCode != http.StatusOK { | 34 | if resp.StatusCode != http.StatusOK { | ||
| 41 | - | return PingResponse{ | 35 | + | return PingResponse{}, fmt.Errorf("expected status code 200, got %d", resp.StatusCode) |
| 42 | - | Uptime: "", | |||
| 43 | - | Version: "", | |||
| 44 | - | }, fmt.Errorf("expected status code 200, got %d", resp.StatusCode) | |||
| 45 | } | 36 | } | ||
| 46 | 37 | ||||
| 47 | // Unmarshal the JSON response into the PingResponse struct | 38 | // Unmarshal the JSON response into the PingResponse struct | ||
M internal/httpserve/handler/clientendpoins.go +18 -30
| @@ -1,9 +1,7 @@ | |||
| 1 | 1 | package handler | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | - | "bytes" | |
| 5 | 4 | "fmt" | |
| 6 | - | "io" | |
| 7 | 5 | "net/http" | |
| 8 | 6 | "regexp" | |
| 9 | 7 | ||
| @@ -27,13 +25,9 @@ func (info *InfoResponse) Populate(a *server.App) { | |||
| 27 | 25 | ||
| 28 | 26 | // Handle GET on /api/ping endpoint | |
| 29 | 27 | func GetInfos(c echo.Context, a *server.App) error { | |
| 30 | - | body, _ := io.ReadAll(c.Request().Body) | |
| 31 | - | c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body | |
| 32 | - | ||
| 33 | 28 | payload := new(common.RequestPayload) | |
| 34 | 29 | if err := c.Bind(payload); err != nil { | |
| 35 | - | fmt.Println("Bind Error:", err) | |
| 36 | - | return c.JSON(http.StatusBadRequest, err.Error()) | |
| 30 | + | return c.JSON(http.StatusBadRequest, "Invalid payload: "+err.Error()) | |
| 37 | 31 | } | |
| 38 | 32 | ||
| 39 | 33 | if payload.Type != "ping" { | |
| @@ -42,10 +36,11 @@ func GetInfos(c echo.Context, a *server.App) error { | |||
| 42 | 36 | ||
| 43 | 37 | pingPayload, ok := payload.Payload.(common.PingPayload) | |
| 44 | 38 | if !ok { | |
| 45 | - | return c.JSON(http.StatusBadRequest, "Invalid payload type") | |
| 39 | + | return c.JSON(http.StatusBadRequest, "Invalid payload structure") | |
| 46 | 40 | } | |
| 41 | + | ||
| 47 | 42 | if pingPayload.Message != "ping" { | |
| 48 | - | return c.JSON(http.StatusBadRequest, "Invalid payload data") | |
| 43 | + | return c.JSON(http.StatusBadRequest, "Invalid ping message") | |
| 49 | 44 | } | |
| 50 | 45 | ||
| 51 | 46 | // Prepare and populate the information | |
| @@ -53,11 +48,9 @@ func GetInfos(c echo.Context, a *server.App) error { | |||
| 53 | 48 | info.Populate(a) | |
| 54 | 49 | ||
| 55 | 50 | return c.JSON(http.StatusOK, info) | |
| 56 | - | ||
| 57 | 51 | } | |
| 58 | 52 | ||
| 59 | 53 | func PostDeploy(c echo.Context, a *server.App) error { | |
| 60 | - | ||
| 61 | 54 | // Initialize pushPayload object | |
| 62 | 55 | payload := &common.DeployPayload{ | |
| 63 | 56 | Ports: c.Request().Header.Get("X-Ports"), | |
| @@ -89,21 +82,24 @@ func PostDeploy(c echo.Context, a *server.App) error { | |||
| 89 | 82 | } | |
| 90 | 83 | ||
| 91 | 84 | // Save the image tar in the storage | |
| 92 | - | imageFilePath, err := store.SaveImageToStorage(&a.Config, imageFileName, imageReader) | |
| 85 | + | imagePath, err := store.SaveImageToStorage(&a.Config, imageFileName, imageReader) | |
| 93 | 86 | if err != nil { | |
| 94 | - | return sendJsonError(c, err) | |
| 87 | + | return sendJsonError(c, fmt.Errorf("failed to save image: %v", err)) | |
| 95 | 88 | } | |
| 96 | 89 | ||
| 90 | + | fmt.Printf("Image saved successfully to: %s\n", imagePath) | |
| 91 | + | ||
| 97 | 92 | // Import the tar in docker | |
| 98 | - | err = docker.ImportImageToEngine(imageFilePath) | |
| 93 | + | err = docker.ImportImageToEngine(imagePath) | |
| 99 | 94 | if err != nil { | |
| 100 | - | return sendJsonError(c, err) | |
| 95 | + | store.RemoveFromStorage(imagePath) // Clean up the saved image if import fails | |
| 96 | + | return sendJsonError(c, fmt.Errorf("failed to import image: %v", err)) | |
| 101 | 97 | } | |
| 102 | 98 | ||
| 103 | 99 | // Remove the image from the storage | |
| 104 | - | err = store.RemoveFromStorage() | |
| 100 | + | err = store.RemoveFromStorage(imagePath) | |
| 105 | 101 | if err != nil { | |
| 106 | - | return sendJsonError(c, err) | |
| 102 | + | return sendJsonError(c, fmt.Errorf("failed to remove temporary image file: %v", err)) | |
| 107 | 103 | } | |
| 108 | 104 | ||
| 109 | 105 | // append localhost to the image name since it's a manually imported image and not from a registry | |
| @@ -112,7 +108,7 @@ func PostDeploy(c echo.Context, a *server.App) error { | |||
| 112 | 108 | // Get the image ID | |
| 113 | 109 | imageID, err := docker.GetImageIDByName(payload.ImageName) | |
| 114 | 110 | if err != nil { | |
| 115 | - | return sendJsonError(c, err) | |
| 111 | + | return sendJsonError(c, fmt.Errorf("failed to get image ID: %v", err)) | |
| 116 | 112 | } | |
| 117 | 113 | ||
| 118 | 114 | // Update the payload with the image ID | |
| @@ -121,30 +117,22 @@ func PostDeploy(c echo.Context, a *server.App) error { | |||
| 121 | 117 | // Create the container using cmdparams.FromPayloadStructToCmdParams | |
| 122 | 118 | params, err := cmdparams.FromPayloadStructToCmdParams(payload, a) | |
| 123 | 119 | if err != nil { | |
| 124 | - | return sendJsonError(c, err) | |
| 120 | + | return sendJsonError(c, fmt.Errorf("failed to create command parameters: %v", err)) | |
| 125 | 121 | } | |
| 126 | 122 | ||
| 127 | 123 | // Create the container | |
| 128 | 124 | containerID, err := docker.CreateContainer(params) | |
| 129 | 125 | if err != nil { | |
| 130 | - | return sendJsonError(c, err) | |
| 126 | + | return sendJsonError(c, fmt.Errorf("failed to create container: %v", err)) | |
| 131 | 127 | } | |
| 132 | 128 | ||
| 133 | 129 | // Start the container | |
| 134 | 130 | err = docker.StartContainer(containerID) | |
| 135 | 131 | if err != nil { | |
| 136 | - | return sendJsonError(c, err) | |
| 132 | + | docker.RemoveContainer(containerID) // Clean up if start fails | |
| 133 | + | return sendJsonError(c, fmt.Errorf("failed to start container: %v", err)) | |
| 137 | 134 | } | |
| 138 | 135 | ||
| 139 | - | // Remove the container if it fails to start | |
| 140 | - | defer func() { | |
| 141 | - | if err != nil { | |
| 142 | - | docker.RemoveContainer(containerID) | |
| 143 | - | c.JSON(http.StatusInternalServerError, sendJsonError(c, err)) | |
| 144 | - | return | |
| 145 | - | } | |
| 146 | - | }() | |
| 147 | - | ||
| 148 | 136 | // If we arrive here, send back payload.TargetDomain so the client can test it | |
| 149 | 137 | return c.JSON(http.StatusOK, payload.TargetDomain) | |
| 150 | 138 | } | |
| @@ -1,9 +1,7 @@ | |||||
| 1 | package handler | 1 | package handler | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | - | "bytes" | |||
| 5 | "fmt" | 4 | "fmt" | ||
| 6 | - | "io" | |||
| 7 | "net/http" | 5 | "net/http" | ||
| 8 | "regexp" | 6 | "regexp" | ||
| 9 | 7 | ||||
| @@ -27,13 +25,9 @@ func (info *InfoResponse) Populate(a *server.App) { | |||||
| 27 | 25 | ||||
| 28 | // Handle GET on /api/ping endpoint | 26 | // Handle GET on /api/ping endpoint | ||
| 29 | func GetInfos(c echo.Context, a *server.App) error { | 27 | func GetInfos(c echo.Context, a *server.App) error { | ||
| 30 | - | body, _ := io.ReadAll(c.Request().Body) | |||
| 31 | - | c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body | |||
| 32 | - | ||||
| 33 | payload := new(common.RequestPayload) | 28 | payload := new(common.RequestPayload) | ||
| 34 | if err := c.Bind(payload); err != nil { | 29 | if err := c.Bind(payload); err != nil { | ||
| 35 | - | fmt.Println("Bind Error:", err) | 30 | + | return c.JSON(http.StatusBadRequest, "Invalid payload: "+err.Error()) |
| 36 | - | return c.JSON(http.StatusBadRequest, err.Error()) | |||
| 37 | } | 31 | } | ||
| 38 | 32 | ||||
| 39 | if payload.Type != "ping" { | 33 | if payload.Type != "ping" { | ||
| @@ -42,10 +36,11 @@ func GetInfos(c echo.Context, a *server.App) error { | |||||
| 42 | 36 | ||||
| 43 | pingPayload, ok := payload.Payload.(common.PingPayload) | 37 | pingPayload, ok := payload.Payload.(common.PingPayload) | ||
| 44 | if !ok { | 38 | if !ok { | ||
| 45 | - | return c.JSON(http.StatusBadRequest, "Invalid payload type") | 39 | + | return c.JSON(http.StatusBadRequest, "Invalid payload structure") |
| 46 | } | 40 | } | ||
| 41 | + | ||||
| 47 | if pingPayload.Message != "ping" { | 42 | if pingPayload.Message != "ping" { | ||
| 48 | - | return c.JSON(http.StatusBadRequest, "Invalid payload data") | 43 | + | return c.JSON(http.StatusBadRequest, "Invalid ping message") |
| 49 | } | 44 | } | ||
| 50 | 45 | ||||
| 51 | // Prepare and populate the information | 46 | // Prepare and populate the information | ||
| @@ -53,11 +48,9 @@ func GetInfos(c echo.Context, a *server.App) error { | |||||
| 53 | info.Populate(a) | 48 | info.Populate(a) | ||
| 54 | 49 | ||||
| 55 | return c.JSON(http.StatusOK, info) | 50 | return c.JSON(http.StatusOK, info) | ||
| 56 | - | ||||
| 57 | } | 51 | } | ||
| 58 | 52 | ||||
| 59 | func PostDeploy(c echo.Context, a *server.App) error { | 53 | func PostDeploy(c echo.Context, a *server.App) error { | ||
| 60 | - | ||||
| 61 | // Initialize pushPayload object | 54 | // Initialize pushPayload object | ||
| 62 | payload := &common.DeployPayload{ | 55 | payload := &common.DeployPayload{ | ||
| 63 | Ports: c.Request().Header.Get("X-Ports"), | 56 | Ports: c.Request().Header.Get("X-Ports"), | ||
| @@ -89,21 +82,24 @@ func PostDeploy(c echo.Context, a *server.App) error { | |||||
| 89 | } | 82 | } | ||
| 90 | 83 | ||||
| 91 | // Save the image tar in the storage | 84 | // Save the image tar in the storage | ||
| 92 | - | imageFilePath, err := store.SaveImageToStorage(&a.Config, imageFileName, imageReader) | 85 | + | imagePath, err := store.SaveImageToStorage(&a.Config, imageFileName, imageReader) |
| 93 | if err != nil { | 86 | if err != nil { | ||
| 94 | - | return sendJsonError(c, err) | 87 | + | return sendJsonError(c, fmt.Errorf("failed to save image: %v", err)) |
| 95 | } | 88 | } | ||
| 96 | 89 | ||||
| 90 | + | fmt.Printf("Image saved successfully to: %s\n", imagePath) | |||
| 91 | + | ||||
| 97 | // Import the tar in docker | 92 | // Import the tar in docker | ||
| 98 | - | err = docker.ImportImageToEngine(imageFilePath) | 93 | + | err = docker.ImportImageToEngine(imagePath) |
| 99 | if err != nil { | 94 | if err != nil { | ||
| 100 | - | return sendJsonError(c, err) | 95 | + | store.RemoveFromStorage(imagePath) // Clean up the saved image if import fails |
| 96 | + | return sendJsonError(c, fmt.Errorf("failed to import image: %v", err)) | |||
| 101 | } | 97 | } | ||
| 102 | 98 | ||||
| 103 | // Remove the image from the storage | 99 | // Remove the image from the storage | ||
| 104 | - | err = store.RemoveFromStorage() | 100 | + | err = store.RemoveFromStorage(imagePath) |
| 105 | if err != nil { | 101 | if err != nil { | ||
| 106 | - | return sendJsonError(c, err) | 102 | + | return sendJsonError(c, fmt.Errorf("failed to remove temporary image file: %v", err)) |
| 107 | } | 103 | } | ||
| 108 | 104 | ||||
| 109 | // append localhost to the image name since it's a manually imported image and not from a registry | 105 | // append localhost to the image name since it's a manually imported image and not from a registry | ||
| @@ -112,7 +108,7 @@ func PostDeploy(c echo.Context, a *server.App) error { | |||||
| 112 | // Get the image ID | 108 | // Get the image ID | ||
| 113 | imageID, err := docker.GetImageIDByName(payload.ImageName) | 109 | imageID, err := docker.GetImageIDByName(payload.ImageName) | ||
| 114 | if err != nil { | 110 | if err != nil { | ||
| 115 | - | return sendJsonError(c, err) | 111 | + | return sendJsonError(c, fmt.Errorf("failed to get image ID: %v", err)) |
| 116 | } | 112 | } | ||
| 117 | 113 | ||||
| 118 | // Update the payload with the image ID | 114 | // Update the payload with the image ID | ||
| @@ -121,30 +117,22 @@ func PostDeploy(c echo.Context, a *server.App) error { | |||||
| 121 | // Create the container using cmdparams.FromPayloadStructToCmdParams | 117 | // Create the container using cmdparams.FromPayloadStructToCmdParams | ||
| 122 | params, err := cmdparams.FromPayloadStructToCmdParams(payload, a) | 118 | params, err := cmdparams.FromPayloadStructToCmdParams(payload, a) | ||
| 123 | if err != nil { | 119 | if err != nil { | ||
| 124 | - | return sendJsonError(c, err) | 120 | + | return sendJsonError(c, fmt.Errorf("failed to create command parameters: %v", err)) |
| 125 | } | 121 | } | ||
| 126 | 122 | ||||
| 127 | // Create the container | 123 | // Create the container | ||
| 128 | containerID, err := docker.CreateContainer(params) | 124 | containerID, err := docker.CreateContainer(params) | ||
| 129 | if err != nil { | 125 | if err != nil { | ||
| 130 | - | return sendJsonError(c, err) | 126 | + | return sendJsonError(c, fmt.Errorf("failed to create container: %v", err)) |
| 131 | } | 127 | } | ||
| 132 | 128 | ||||
| 133 | // Start the container | 129 | // Start the container | ||
| 134 | err = docker.StartContainer(containerID) | 130 | err = docker.StartContainer(containerID) | ||
| 135 | if err != nil { | 131 | if err != nil { | ||
| 136 | - | return sendJsonError(c, err) | 132 | + | docker.RemoveContainer(containerID) // Clean up if start fails |
| 133 | + | return sendJsonError(c, fmt.Errorf("failed to start container: %v", err)) | |||
| 137 | } | 134 | } | ||
| 138 | 135 | ||||
| 139 | - | // Remove the container if it fails to start | |||
| 140 | - | defer func() { | |||
| 141 | - | if err != nil { | |||
| 142 | - | docker.RemoveContainer(containerID) | |||
| 143 | - | c.JSON(http.StatusInternalServerError, sendJsonError(c, err)) | |||
| 144 | - | return | |||
| 145 | - | } | |||
| 146 | - | }() | |||
| 147 | - | ||||
| 148 | // If we arrive here, send back payload.TargetDomain so the client can test it | 136 | // If we arrive here, send back payload.TargetDomain so the client can test it | ||
| 149 | return c.JSON(http.StatusOK, payload.TargetDomain) | 137 | return c.JSON(http.StatusOK, payload.TargetDomain) | ||
| 150 | } | 138 | } | ||
M internal/httpserve/handler/uploadcontainerimage.go +1 -1
| @@ -91,7 +91,7 @@ func UploadImagePOSTHandler(c echo.Context, a *server.App) error { | |||
| 91 | 91 | } | |
| 92 | 92 | ||
| 93 | 93 | // Remove the image from the storage directory | |
| 94 | - | err = store.RemoveFromStorage() | |
| 94 | + | err = store.RemoveFromStorage(saveInPath) | |
| 95 | 95 | if err != nil { | |
| 96 | 96 | return sendError(c, err) | |
| 97 | 97 | } | |
| @@ -91,7 +91,7 @@ func UploadImagePOSTHandler(c echo.Context, a *server.App) error { | |||||
| 91 | } | 91 | } | ||
| 92 | 92 | ||||
| 93 | // Remove the image from the storage directory | 93 | // Remove the image from the storage directory | ||
| 94 | - | err = store.RemoveFromStorage() | 94 | + | err = store.RemoveFromStorage(saveInPath) |
| 95 | if err != nil { | 95 | if err != nil { | ||
| 96 | return sendError(c, err) | 96 | return sendError(c, err) | ||
| 97 | } | 97 | } | ||
M pkg/store/storeimages.go +51 -22
| @@ -5,53 +5,82 @@ import ( | |||
| 5 | 5 | "io" | |
| 6 | 6 | "os" | |
| 7 | 7 | "path/filepath" | |
| 8 | + | "sync" | |
| 8 | 9 | ||
| 9 | 10 | "github.com/bnema/gordon/internal/common" | |
| 11 | + | "github.com/google/uuid" | |
| 10 | 12 | ) | |
| 11 | 13 | ||
| 12 | - | var imagePath string | |
| 14 | + | var ( | |
| 15 | + | imagePath string | |
| 16 | + | mu sync.Mutex | |
| 17 | + | ) | |
| 18 | + | ||
| 19 | + | // setImagePath sets the image path in a thread-safe manner | |
| 20 | + | func setImagePath(path string) { | |
| 21 | + | mu.Lock() | |
| 22 | + | defer mu.Unlock() | |
| 23 | + | imagePath = path | |
| 24 | + | } | |
| 13 | 25 | ||
| 14 | - | func SaveImageToStorage(config *common.Config, filename string, buf io.Reader) (string, error) { | |
| 15 | - | // Check if the folder exist if not create it | |
| 16 | - | if _, err := os.Stat(config.General.StorageDir); os.IsNotExist(err) { | |
| 17 | - | err := os.MkdirAll(config.General.StorageDir, 0755) | |
| 18 | - | if err != nil { | |
| 19 | - | return "", fmt.Errorf("failed to create storage directory: %v", err) | |
| 20 | - | } | |
| 26 | + | // getImagePath gets the image path in a thread-safe manner | |
| 27 | + | func getImagePath() string { | |
| 28 | + | mu.Lock() | |
| 29 | + | defer mu.Unlock() | |
| 30 | + | return imagePath | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | func SaveImageToStorage(config *common.Config, originalFilename string, buf io.Reader) (string, error) { | |
| 34 | + | // Ensure the images directory exists | |
| 35 | + | imagesDir := filepath.Join(config.General.StorageDir, "images") | |
| 36 | + | if err := os.MkdirAll(imagesDir, 0755); err != nil { | |
| 37 | + | return "", fmt.Errorf("failed to create images directory: %v", err) | |
| 21 | 38 | } | |
| 22 | 39 | ||
| 23 | - | // Define the path where the image will be saved | |
| 24 | - | imagePath = filepath.Join(config.General.StorageDir, filename) | |
| 40 | + | // Generate a unique ID for the image | |
| 41 | + | id := uuid.New().String() | |
| 42 | + | ||
| 43 | + | // Create a filename with the ID and original extension | |
| 44 | + | ext := filepath.Ext(originalFilename) | |
| 45 | + | filename := fmt.Sprintf("%s%s", id, ext) | |
| 25 | 46 | ||
| 26 | - | // Create or open a file for appending. | |
| 47 | + | // Full path for the new image | |
| 48 | + | imagePath := filepath.Join(imagesDir, filename) | |
| 49 | + | ||
| 50 | + | fmt.Printf("Saving image to: %s\n", imagePath) | |
| 51 | + | ||
| 52 | + | // Create the file | |
| 27 | 53 | outFile, err := os.Create(imagePath) | |
| 28 | 54 | if err != nil { | |
| 29 | - | return "", fmt.Errorf("failed to open file: %v", err) | |
| 55 | + | return "", fmt.Errorf("failed to create file: %v", err) | |
| 30 | 56 | } | |
| 57 | + | defer outFile.Close() | |
| 31 | 58 | ||
| 32 | - | // Write the uploaded file's content to the outFile | |
| 33 | - | _, err = io.Copy(outFile, buf) | |
| 59 | + | // Write the content | |
| 60 | + | written, err := io.Copy(outFile, buf) | |
| 34 | 61 | if err != nil { | |
| 62 | + | os.Remove(imagePath) // Clean up in case of error | |
| 35 | 63 | return "", fmt.Errorf("failed to write file: %v", err) | |
| 36 | 64 | } | |
| 37 | 65 | ||
| 38 | - | // Check if file exists | |
| 39 | - | if _, err := os.Stat(imagePath); os.IsNotExist(err) { | |
| 40 | - | return "", fmt.Errorf("file does not exist: %v", err) | |
| 41 | - | } | |
| 66 | + | fmt.Printf("Wrote %d bytes to %s\n", written, imagePath) | |
| 42 | 67 | ||
| 43 | 68 | return imagePath, nil | |
| 44 | 69 | } | |
| 45 | 70 | ||
| 46 | - | func RemoveFromStorage() error { | |
| 47 | - | // Delete the file | |
| 71 | + | func RemoveFromStorage(imagePath string) error { | |
| 72 | + | if imagePath == "" { | |
| 73 | + | return fmt.Errorf("no image path provided to remove") | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | fmt.Printf("Attempting to remove file: %s\n", imagePath) | |
| 77 | + | ||
| 48 | 78 | err := os.Remove(imagePath) | |
| 49 | 79 | if err != nil { | |
| 50 | 80 | return fmt.Errorf("failed to remove file: %v", err) | |
| 51 | 81 | } | |
| 52 | 82 | ||
| 53 | - | // clean up the path | |
| 54 | - | imagePath = "" | |
| 83 | + | fmt.Printf("Successfully removed file: %s\n", imagePath) | |
| 55 | 84 | ||
| 56 | 85 | return nil | |
| 57 | 86 | } | |
| @@ -5,53 +5,82 @@ import ( | |||||
| 5 | "io" | 5 | "io" | ||
| 6 | "os" | 6 | "os" | ||
| 7 | "path/filepath" | 7 | "path/filepath" | ||
| 8 | + | "sync" | |||
| 8 | 9 | ||||
| 9 | "github.com/bnema/gordon/internal/common" | 10 | "github.com/bnema/gordon/internal/common" | ||
| 11 | + | "github.com/google/uuid" | |||
| 10 | ) | 12 | ) | ||
| 11 | 13 | ||||
| 12 | - | var imagePath string | 14 | + | var ( |
| 15 | + | imagePath string | |||
| 16 | + | mu sync.Mutex | |||
| 17 | + | ) | |||
| 18 | + | ||||
| 19 | + | // setImagePath sets the image path in a thread-safe manner | |||
| 20 | + | func setImagePath(path string) { | |||
| 21 | + | mu.Lock() | |||
| 22 | + | defer mu.Unlock() | |||
| 23 | + | imagePath = path | |||
| 24 | + | } | |||
| 13 | 25 | ||||
| 14 | - | func SaveImageToStorage(config *common.Config, filename string, buf io.Reader) (string, error) { | 26 | + | // getImagePath gets the image path in a thread-safe manner |
| 15 | - | // Check if the folder exist if not create it | 27 | + | func getImagePath() string { |
| 16 | - | if _, err := os.Stat(config.General.StorageDir); os.IsNotExist(err) { | 28 | + | mu.Lock() |
| 17 | - | err := os.MkdirAll(config.General.StorageDir, 0755) | 29 | + | defer mu.Unlock() |
| 18 | - | if err != nil { | 30 | + | return imagePath |
| 19 | - | return "", fmt.Errorf("failed to create storage directory: %v", err) | 31 | + | } |
| 20 | - | } | 32 | + | |
| 33 | + | func SaveImageToStorage(config *common.Config, originalFilename string, buf io.Reader) (string, error) { | |||
| 34 | + | // Ensure the images directory exists | |||
| 35 | + | imagesDir := filepath.Join(config.General.StorageDir, "images") | |||
| 36 | + | if err := os.MkdirAll(imagesDir, 0755); err != nil { | |||
| 37 | + | return "", fmt.Errorf("failed to create images directory: %v", err) | |||
| 21 | } | 38 | } | ||
| 22 | 39 | ||||
| 23 | - | // Define the path where the image will be saved | 40 | + | // Generate a unique ID for the image |
| 24 | - | imagePath = filepath.Join(config.General.StorageDir, filename) | 41 | + | id := uuid.New().String() |
| 42 | + | ||||
| 43 | + | // Create a filename with the ID and original extension | |||
| 44 | + | ext := filepath.Ext(originalFilename) | |||
| 45 | + | filename := fmt.Sprintf("%s%s", id, ext) | |||
| 25 | 46 | ||||
| 26 | - | // Create or open a file for appending. | 47 | + | // Full path for the new image |
| 48 | + | imagePath := filepath.Join(imagesDir, filename) | |||
| 49 | + | ||||
| 50 | + | fmt.Printf("Saving image to: %s\n", imagePath) | |||
| 51 | + | ||||
| 52 | + | // Create the file | |||
| 27 | outFile, err := os.Create(imagePath) | 53 | outFile, err := os.Create(imagePath) | ||
| 28 | if err != nil { | 54 | if err != nil { | ||
| 29 | - | return "", fmt.Errorf("failed to open file: %v", err) | 55 | + | return "", fmt.Errorf("failed to create file: %v", err) |
| 30 | } | 56 | } | ||
| 57 | + | defer outFile.Close() | |||
| 31 | 58 | ||||
| 32 | - | // Write the uploaded file's content to the outFile | 59 | + | // Write the content |
| 33 | - | _, err = io.Copy(outFile, buf) | 60 | + | written, err := io.Copy(outFile, buf) |
| 34 | if err != nil { | 61 | if err != nil { | ||
| 62 | + | os.Remove(imagePath) // Clean up in case of error | |||
| 35 | return "", fmt.Errorf("failed to write file: %v", err) | 63 | return "", fmt.Errorf("failed to write file: %v", err) | ||
| 36 | } | 64 | } | ||
| 37 | 65 | ||||
| 38 | - | // Check if file exists | 66 | + | fmt.Printf("Wrote %d bytes to %s\n", written, imagePath) |
| 39 | - | if _, err := os.Stat(imagePath); os.IsNotExist(err) { | |||
| 40 | - | return "", fmt.Errorf("file does not exist: %v", err) | |||
| 41 | - | } | |||
| 42 | 67 | ||||
| 43 | return imagePath, nil | 68 | return imagePath, nil | ||
| 44 | } | 69 | } | ||
| 45 | 70 | ||||
| 46 | - | func RemoveFromStorage() error { | 71 | + | func RemoveFromStorage(imagePath string) error { |
| 47 | - | // Delete the file | 72 | + | if imagePath == "" { |
| 73 | + | return fmt.Errorf("no image path provided to remove") | |||
| 74 | + | } | |||
| 75 | + | ||||
| 76 | + | fmt.Printf("Attempting to remove file: %s\n", imagePath) | |||
| 77 | + | ||||
| 48 | err := os.Remove(imagePath) | 78 | err := os.Remove(imagePath) | ||
| 49 | if err != nil { | 79 | if err != nil { | ||
| 50 | return fmt.Errorf("failed to remove file: %v", err) | 80 | return fmt.Errorf("failed to remove file: %v", err) | ||
| 51 | } | 81 | } | ||
| 52 | 82 | ||||
| 53 | - | // clean up the path | 83 | + | fmt.Printf("Successfully removed file: %s\n", imagePath) |
| 54 | - | imagePath = "" | |||
| 55 | 84 | ||||
| 56 | return nil | 85 | return nil | ||
| 57 | } | 86 | } | ||