// Copyright (C) 2026 Dcntrlized // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package actions import ( "context" "fmt" "io" "net/http" "git.dcntrlized.org/Dcntrlized/Maestro/lib/docker" "git.dcntrlized.org/Dcntrlized/Maestro/lib/logger" ) func RunAction(w http.ResponseWriter, r *http.Request) { // Runs an action on the container specified by the actionName and containerId path parameters. actionName := r.PathValue("actionName") containerId := r.PathValue("containerId") logger.Debug("Received request to run action %s on container id %s", actionName, containerId) // Get IP address of the container using containerId dockerClient, err := docker.NewDockerClient() if err != nil { http.Error(w, "Failed to create Docker client", http.StatusInternalServerError) return } inspectData, err := dockerClient.ContainerInspect(context.Background(), containerId) if err != nil { http.Error(w, "Failed to inspect container", http.StatusInternalServerError) return } network, _, err := docker.ExtractNetworkInfo(context.Background(), dockerClient, inspectData) if err != nil { http.Error(w, "Failed to extract network info", http.StatusInternalServerError) return } // Construct the URL for the action endpoint actionURL := fmt.Sprintf("http://%s:%d/action/%s", network.IPAddress.Address, network.Ports[0].Host, actionName) // Assuming network.Ports[0] is the main port. Might change in the future // Forward the request to the container's action endpoint resp, err := http.Get(actionURL) if err != nil { logger.Debug("Failed to forward action request %s to container %s: %w", actionName, containerId, err) http.Error(w, "Failed to forward request to container", http.StatusInternalServerError) return } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { bodyBytes, err := io.ReadAll(resp.Body) if err != nil { logger.Debug("Failed to read container error response body: %v", err) } responseBodyString := string(bodyBytes) if resp.StatusCode == 404 { logger.Info("The requested action does not exist") http.Error(w, "The requested action does not exist", http.StatusNotFound) } logger.Info("Container responded with non-2xx code") logger.Debug("Container: %s | Called URL: %s | Status: %d | Response: %s", containerId, actionURL, resp.StatusCode, responseBodyString) http.Error(w, "Container responded with non-2xx code", http.StatusInternalServerError) return } // Copy the response from the container back to the original response writer w.WriteHeader(resp.StatusCode) _, err = io.Copy(w, resp.Body) if err != nil { http.Error(w, "Failed to read response from container", http.StatusInternalServerError) return } }