Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
c03c290
feat: add CORS middleware to enable cross-origin requests
ovvesley Jan 11, 2026
f6ae12c
feat: implement HPC runtime support and refactor related components
ovvesley Jan 11, 2026
f2c8aa6
fix: correct value trimming logic in loadDotEnv function
ovvesley Jan 11, 2026
faaca30
feat: update HPC runtime support and refactor related components
ovvesley Jan 11, 2026
28085cc
feat: add BuildRemoteCommand method and refactor command execution in…
ovvesley Jan 11, 2026
5b88e50
fix: update sensitive key detection regex in isSensitiveKey function
ovvesley Jan 11, 2026
f3663b4
feat: add default Slurm batch script and implement template retrieval…
ovvesley Jan 11, 2026
ee7ea03
feat: update Dockerfile and devcontainer configuration for improved c…
ovvesley Jan 11, 2026
3134c8b
fix: update delve version to 1.23.1 in Dockerfile
ovvesley Jan 11, 2026
f751c8c
fix: remove unnecessary flag from buildFlags in AkoFlow Server launch…
ovvesley Jan 11, 2026
188c747
feat: add local connector to AppContainer for enhanced connectivity o…
ovvesley Jan 11, 2026
3393798
feat: implement local runtime and connector services for command exec…
ovvesley Jan 11, 2026
12e4c28
feat: add SSH key handling in ConnectorHPCRuntime for remote command …
ovvesley Jan 11, 2026
ed22d32
feat: add MountPath field to Activity and WorkflowActivities models f…
ovvesley Jan 11, 2026
4ed1c48
feat: enhance runtime management by adding runtimeType and runtimeNam…
ovvesley Jan 11, 2026
791e53e
feat: update entry point in MakeContainerCommandActivityToHPC for sha…
ovvesley Jan 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .devcontainer/dev.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
FROM docker:26-cli AS dockercli

FROM golang:1.23-bullseye

WORKDIR /app
Expand All @@ -19,13 +21,15 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
pkg-config \
&& rm -rf /var/lib/apt/lists/*

COPY --from=dockercli /usr/local/bin/docker /usr/local/bin/docker

COPY go.mod go.sum ./
RUN go mod download

RUN go install golang.org/x/tools/gopls@v0.16.2 && \
go install honnef.co/go/tools/cmd/staticcheck@v0.5.0

RUN CGO_ENABLED=1 go install -ldflags "-s -w -extldflags '-static'" github.com/go-delve/delve/cmd/dlv@latest
RUN go install github.com/go-delve/delve/cmd/dlv@v1.23.1

RUN mkdir -p storage && chmod 777 storage

Expand Down
12 changes: 9 additions & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,26 @@
},
"workspaceFolder": "/app",
"workspaceMount": "source=${localWorkspaceFolder},target=/app,type=bind",
"appPort": ["8080:8080"],
"appPort": [
"8080:8080"
],
"postCreateCommand": "go mod tidy",
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "sh"
},
"extensions": [
"golang.go",
"golang.go",
"ms-vscode-remote.remote-containers",
"ms-azuretools.vscode-docker"
]
}
},
"remoteUser": "root",
"runArgs": ["--privileged"]
"runArgs": [
"--privileged",
"-v",
"/var/run/docker.sock:/var/run/docker.sock"
]
}
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"request": "launch",
"mode": "debug",
"program": "cmd/server/main.go",
"buildFlags": ["-gcflags=all=-N -l"]
"buildFlags": ["-gcflags=all=-N"]
},
{
"name": "Launch AkoFlow Client Run",
Expand Down
14 changes: 9 additions & 5 deletions pkg/server/config/app_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import (
"github.com/ovvesley/akoflow/pkg/server/config/http_helper"
"github.com/ovvesley/akoflow/pkg/server/config/http_render_view"
"github.com/ovvesley/akoflow/pkg/server/config/logger"
"github.com/ovvesley/akoflow/pkg/server/connector/connector_hpc"
"github.com/ovvesley/akoflow/pkg/server/connector/connector_k8s"
"github.com/ovvesley/akoflow/pkg/server/connector/connector_sdumont"
"github.com/ovvesley/akoflow/pkg/server/connector/connector_local"
"github.com/ovvesley/akoflow/pkg/server/connector/connector_singularity"
"github.com/ovvesley/akoflow/pkg/server/database/repository/activity_repository"
"github.com/ovvesley/akoflow/pkg/server/database/repository/logs_repository"
Expand Down Expand Up @@ -56,7 +57,8 @@ type AppContainerRepository struct {
type AppContainerConnector struct {
K8sConnector connector_k8s.IConnector
SingularityConnector connector_singularity.IConnectorSingularity
SDumontConnector connector_sdumont.IConnectorSDumont
HPCRuntimeConnector connector_hpc.IConnectorHPCRuntime
LocalConnector connector_local.IConnectorLocal
}

type AppContainerTemplateRenderer struct {
Expand All @@ -74,7 +76,7 @@ func GetEnvVars() (map[string]string, map[string]map[string]string) {
envVars := make(map[string]string)
envVarByRuntime := make(map[string]map[string]string)

runtimes_avaibles := []string{"k8s", "singularity", "sdumont"}
runtimes_avaibles := []string{"k8s", "singularity", "hpc"}

for _, v := range os.Environ() {
splitted := strings.Split(v, "=")
Expand Down Expand Up @@ -120,7 +122,8 @@ func MakeAppContainer() AppContainer {
// create the Connector instances
k8sConnector := connector_k8s.New()
singularityConnector := connector_singularity.New()
sdumontConnector := connector_sdumont.New()
hpcConnector := connector_hpc.New()
localConnector := connector_local.New()

renderViewprovider := http_render_view.New()

Expand All @@ -145,7 +148,8 @@ func MakeAppContainer() AppContainer {
Connector: AppContainerConnector{
K8sConnector: k8sConnector,
SingularityConnector: singularityConnector,
SDumontConnector: sdumontConnector,
HPCRuntimeConnector: hpcConnector,
LocalConnector: localConnector,
},
TemplateRenderer: AppContainerTemplateRenderer{
RenderViewProvider: renderViewprovider,
Expand Down
10 changes: 9 additions & 1 deletion pkg/server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,16 @@ func loadDotEnv() {
if line != "" {
env := strings.Split(line, "=")
key := strings.TrimSpace(env[0])
value := strings.Trim(strings.TrimSpace(env[1]), "'")
value := ""

if len(env) == 2 {
value = strings.TrimSpace(env[1])
} else if len(env) > 2 {
value = strings.TrimSpace(strings.Join(env[1:], "="))
}
value = strings.TrimSpace(value)
os.Setenv(key, value)

}
}

Expand Down
243 changes: 243 additions & 0 deletions pkg/server/connector/connector_hpc/connector_hpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
package connector_hpc

import (
"encoding/base64"
"fmt"
"os/exec"
"sync"
"syscall"

"github.com/ovvesley/akoflow/pkg/server/entities/runtime_entity"
)

type ConnectorHPCRuntime struct {
Runtime runtime_entity.Runtime
}

func New() IConnectorHPCRuntime {
return &ConnectorHPCRuntime{}
}

func (c *ConnectorHPCRuntime) SetRuntime(runtime runtime_entity.Runtime) *ConnectorHPCRuntime {
c.Runtime = runtime
return c
}

type IConnectorHPCRuntime interface {
RunCommand(command string, args ...string) (string, error)
RunCommandWithOutput(command string, args ...string) (string, error)
RunCommandWithOutputRemote(command string, args ...string) (string, error)
IsVPNConnected() (bool, error)
ExecuteMultiplesCommand(commands []string)
SetRuntime(runtime runtime_entity.Runtime) *ConnectorHPCRuntime
BuildRemoteCommand(runtime runtime_entity.Runtime, command string) (string, error)
}

func (c *ConnectorHPCRuntime) RunCommandWithOutputRemote(command string, args ...string) (string, error) {
fmt.Printf("Executing command: %s %v\n", command, args)

shell := getAvailableShell()

remoteCommand, err := c.BuildRemoteCommand(c.Runtime, command)
if err != nil {
return "", err
}

fullCommand := append([]string{"-c", remoteCommand}, args...)
cmd := exec.Command(shell, fullCommand...)
output, err := cmd.CombinedOutput()

println(string(output))
if err != nil {
return "", fmt.Errorf("failed to execute command: %w", err)
}

return string(output), nil
}

func (c *ConnectorHPCRuntime) handleCreateSSHKey(privateKey string, publicKey string, sshConfig string) error {
if privateKey == "" || publicKey == "" || sshConfig == "" {
return nil
}

privateKeyDecoded, err := decodeBase64(privateKey)
if err != nil {
return err
}

publicKeyDecoded, err := decodeBase64(publicKey)
if err != nil {
return err
}

sshConfigDecoded, err := decodeBase64(sshConfig)
if err != nil {
return err
}

privateKeyFile, err := writeTempSSHKey(privateKeyDecoded, "id_rsa")
if err != nil {
return err
}

publicKeyFile, err := writeTempSSHKey(publicKeyDecoded, "id_rsa.pub")
if err != nil {
removeTempSSHKey(privateKeyFile)
return err
}

_, err = writeTempSSHKey(sshConfigDecoded, "config")

if err != nil {
removeTempSSHKey(privateKeyFile)
removeTempSSHKey(publicKeyFile)
return err
}

return nil
}

func (c ConnectorHPCRuntime) BuildRemoteCommand(runtime runtime_entity.Runtime, command string) (string, error) {
username := runtime.GetCurrentRuntimeMetadata("USER")
hostname := runtime.GetCurrentRuntimeMetadata("HOST_CLUSTER")
sshKeyPrivateKey := runtime.GetCurrentRuntimeMetadata("SSHKEYPRIVK")
sshKeyPublicKey := runtime.GetCurrentRuntimeMetadata("SSHKEYPUBLK")
sshConfig := runtime.GetCurrentRuntimeMetadata("SSHCONFIG")
password := runtime.GetCurrentRuntimeMetadata("PASSWORD")

// create .ssh/id_rsa file with the private key
c.handleCreateSSHKey(sshKeyPrivateKey, sshKeyPublicKey, sshConfig)

if sshKeyPrivateKey != "" && sshKeyPublicKey != "" && sshConfig != "" {
return fmt.Sprintf("ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ConnectTimeout=10 %s@%s '%s'", username, hostname, command), nil
} else if password != "" {
return fmt.Sprintf("sshpass -p '%s' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ConnectTimeout=10 %s@%s '%s'", password, username, hostname, command), nil
}

return "", fmt.Errorf("no authentication method provided")
}

func decodeBase64(encoded string) (string, error) {
decoded, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
return "", fmt.Errorf("failed to decode base64: %w", err)
}
return string(decoded), nil
}

func writeTempSSHKey(key string, filename string) (string, error) {
path := "/root/.ssh/"

keyFile := path + filename

_, err := exec.Command("bash", "-c", fmt.Sprintf("test -f %s", keyFile)).Output()
if err == nil {
return keyFile, nil
}

err = exec.Command("bash", "-c", fmt.Sprintf("echo '%s' > %s && chmod 600 %s", key, keyFile, keyFile)).Run()
if err != nil {
return "", fmt.Errorf("failed to write SSH key to file: %w", err)
}

return keyFile, nil
}

func removeTempSSHKey(file string) {
exec.Command("bash", "-c", fmt.Sprintf("rm -f %s", file)).Run()
}

func (s *ConnectorHPCRuntime) ExecuteMultiplesCommand(commands []string) {
var wg sync.WaitGroup

responses := make(chan string, len(commands)) // Create a channel to receive the responses

for _, command := range commands {
wg.Add(1)
go func(command string) {
defer wg.Done()

shell := getAvailableShell()

fullCommand := append([]string{"-c", command})
cmd := exec.Command(shell, fullCommand...)
output, err := cmd.CombinedOutput()

fmt.Printf("Executing command: %s %v\n", command, fullCommand)

if err != nil {
fmt.Printf("failed to execute command: %s\n", err)
}

fmt.Printf("Output: %s\n", output)

responses <- string(output)

}(command)
}

wg.Wait()

close(responses)

for response := range responses {
fmt.Printf("Response: %s\n", response)
}
}

func (c *ConnectorHPCRuntime) RunCommand(command string, args ...string) (string, error) {
return executeCommand(command, args...)
}

func (c *ConnectorHPCRuntime) RunCommandWithOutput(command string, args ...string) (string, error) {
fmt.Printf("Executing command: %s %v\n", command, args)

shell := getAvailableShell()

fullCommand := append([]string{"-c", command}, args...)
cmd := exec.Command(shell, fullCommand...)
output, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("failed to execute command: %w", err)
}

return string(output), nil

}

func (c *ConnectorHPCRuntime) IsVPNConnected() (bool, error) {
output, err := c.RunCommandWithOutput("ps aux | grep vpnc")
if err != nil {
return false, err
}

if len(output) > 0 {
return true, nil
}

return false, nil
}

func executeCommand(command string, args ...string) (string, error) {
fmt.Printf("Executing command: %s %v\n", command, args)

shell := getAvailableShell()
fullCommand := append([]string{"-c", command}, args...)
cmd := exec.Command(shell, fullCommand...)
cmd.SysProcAttr = &syscall.SysProcAttr{}
if err := cmd.Start(); err != nil {
return "", fmt.Errorf("failed to start command: %w", err)
}

pid := cmd.Process.Pid
fmt.Printf("Started process with PID: %d\n", pid)

return fmt.Sprintf("%d", pid), nil
}

func getAvailableShell() string {
if _, err := exec.LookPath("bash"); err == nil {
return "bash"
}
return "sh"
}
Loading