mirror of https://github.com/harness/drone.git
feat: [CDE-472]: add status code to the channel for gitspace exec (#3096)
* feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channel * feat: [CDE-472]: add status code to the channelpull/3597/head
parent
384fb7a7d2
commit
74ff87178f
|
@ -17,6 +17,7 @@ package common
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/harness/gitness/app/gitspace/orchestrator/devcontainer"
|
"github.com/harness/gitness/app/gitspace/orchestrator/devcontainer"
|
||||||
|
@ -156,16 +157,50 @@ func ExecuteCommandInHomeDirAndLog(
|
||||||
gitspaceLogger types.GitspaceLogger,
|
gitspaceLogger types.GitspaceLogger,
|
||||||
verbose bool,
|
verbose bool,
|
||||||
) error {
|
) error {
|
||||||
outputCh := make(chan []byte)
|
// Buffer upto a thousand messages
|
||||||
err := exec.ExecuteCommandInHomeDirectory(ctx, script, root, false, outputCh)
|
outputCh := make(chan []byte, 1000)
|
||||||
for output := range outputCh {
|
err := exec.ExecuteCmdInHomeDirectoryAsyncStream(ctx, script, root, false, outputCh)
|
||||||
msg := string(output)
|
if err != nil {
|
||||||
// Log output from the command as a string
|
return err
|
||||||
if len(output) > 0 {
|
}
|
||||||
if verbose || strings.HasPrefix(msg, devcontainer.LoggerErrorPrefix) {
|
// Use select to wait for the output and exit status
|
||||||
gitspaceLogger.Info(msg)
|
for {
|
||||||
|
select {
|
||||||
|
case output := <-outputCh:
|
||||||
|
done, chErr := handleOutputChannel(output, verbose, gitspaceLogger)
|
||||||
|
if done {
|
||||||
|
return chErr
|
||||||
}
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
// Handle context cancellation or timeout
|
||||||
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
}
|
||||||
|
|
||||||
|
func handleOutputChannel(output []byte, verbose bool, gitspaceLogger types.GitspaceLogger) (bool, error) {
|
||||||
|
// Handle the exit status first
|
||||||
|
if strings.HasPrefix(string(output), devcontainer.ChannelExitStatus) {
|
||||||
|
// Extract the exit code from the message
|
||||||
|
exitCodeStr := strings.TrimPrefix(string(output), devcontainer.ChannelExitStatus)
|
||||||
|
exitCode, err := strconv.Atoi(exitCodeStr)
|
||||||
|
if err != nil {
|
||||||
|
return true, fmt.Errorf("invalid exit status format: %w", err)
|
||||||
|
}
|
||||||
|
if exitCode != 0 {
|
||||||
|
gitspaceLogger.Info("Process Exited with status " + exitCodeStr)
|
||||||
|
return true, fmt.Errorf("command exited with non-zero status: %d", exitCode)
|
||||||
|
}
|
||||||
|
// If exit status is zero, just continue processing
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
// Handle regular command output
|
||||||
|
msg := string(output)
|
||||||
|
if len(output) > 0 {
|
||||||
|
// Log output if verbose or if it's an error
|
||||||
|
if verbose || strings.HasPrefix(msg, devcontainer.LoggerErrorPrefix) {
|
||||||
|
gitspaceLogger.Info(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,9 +53,9 @@ func ValidateSupportedOS(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExecuteCommands(
|
func ExecuteLifecycleCommands(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
exec *devcontainer.Exec,
|
exec devcontainer.Exec,
|
||||||
codeRepoDir string,
|
codeRepoDir string,
|
||||||
gitspaceLogger gitspaceTypes.GitspaceLogger,
|
gitspaceLogger gitspaceTypes.GitspaceLogger,
|
||||||
commands []string,
|
commands []string,
|
||||||
|
@ -69,18 +69,12 @@ func ExecuteCommands(
|
||||||
gitspaceLogger.Info(fmt.Sprintf("Executing %s command: %s", actionType, command))
|
gitspaceLogger.Info(fmt.Sprintf("Executing %s command: %s", actionType, command))
|
||||||
gitspaceLogger.Info(fmt.Sprintf("%s command execution output...", actionType))
|
gitspaceLogger.Info(fmt.Sprintf("%s command execution output...", actionType))
|
||||||
|
|
||||||
// Create a channel to stream command output
|
exec.DefaultWorkingDir = codeRepoDir
|
||||||
outputCh := make(chan []byte)
|
err := common.ExecuteCommandInHomeDirAndLog(ctx, &exec, command, false, gitspaceLogger, true)
|
||||||
err := exec.ExecuteCommand(ctx, command, false, false, codeRepoDir, outputCh)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return logStreamWrapError(
|
return logStreamWrapError(
|
||||||
gitspaceLogger, fmt.Sprintf("Error while executing %s command: %s", actionType, command), err)
|
gitspaceLogger, fmt.Sprintf("Error while executing %s command: %s", actionType, command), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for output := range outputCh {
|
|
||||||
gitspaceLogger.Info(string(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
gitspaceLogger.Info(fmt.Sprintf("Completed execution %s command: %s", actionType, command))
|
gitspaceLogger.Info(fmt.Sprintf("Completed execution %s command: %s", actionType, command))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
// Copyright 2023 Harness, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package container
|
|
|
@ -197,12 +197,12 @@ func (e *EmbeddedDockerOrchestrator) startStoppedGitspace(
|
||||||
codeRepoDir := filepath.Join(homeDir, resolvedRepoDetails.RepoName)
|
codeRepoDir := filepath.Join(homeDir, resolvedRepoDetails.RepoName)
|
||||||
|
|
||||||
exec := &devcontainer.Exec{
|
exec := &devcontainer.Exec{
|
||||||
ContainerName: containerName,
|
ContainerName: containerName,
|
||||||
DockerClient: dockerClient,
|
DockerClient: dockerClient,
|
||||||
HomeDir: homeDir,
|
DefaultWorkingDir: homeDir,
|
||||||
RemoteUser: remoteUser,
|
RemoteUser: remoteUser,
|
||||||
AccessKey: accessKey,
|
AccessKey: accessKey,
|
||||||
AccessType: gitspaceConfig.GitspaceInstance.AccessType,
|
AccessType: gitspaceConfig.GitspaceInstance.AccessType,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up git credentials if needed
|
// Set up git credentials if needed
|
||||||
|
@ -220,7 +220,7 @@ func (e *EmbeddedDockerOrchestrator) startStoppedGitspace(
|
||||||
// Execute post-start command
|
// Execute post-start command
|
||||||
devcontainerConfig := resolvedRepoDetails.DevcontainerConfig
|
devcontainerConfig := resolvedRepoDetails.DevcontainerConfig
|
||||||
command := ExtractLifecycleCommands(PostStartAction, devcontainerConfig)
|
command := ExtractLifecycleCommands(PostStartAction, devcontainerConfig)
|
||||||
startErr = ExecuteCommands(ctx, exec, codeRepoDir, logStreamInstance, command, PostStartAction)
|
startErr = ExecuteLifecycleCommands(ctx, *exec, codeRepoDir, logStreamInstance, command, PostStartAction)
|
||||||
if startErr != nil {
|
if startErr != nil {
|
||||||
log.Warn().Msgf("Error is post-start command, continuing : %s", startErr.Error())
|
log.Warn().Msgf("Error is post-start command, continuing : %s", startErr.Error())
|
||||||
}
|
}
|
||||||
|
@ -454,12 +454,12 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps(
|
||||||
|
|
||||||
// Setup and run commands
|
// Setup and run commands
|
||||||
exec := &devcontainer.Exec{
|
exec := &devcontainer.Exec{
|
||||||
ContainerName: containerName,
|
ContainerName: containerName,
|
||||||
DockerClient: dockerClient,
|
DockerClient: dockerClient,
|
||||||
HomeDir: homeDir,
|
DefaultWorkingDir: homeDir,
|
||||||
RemoteUser: remoteUser,
|
RemoteUser: remoteUser,
|
||||||
AccessKey: *gitspaceConfig.GitspaceInstance.AccessKey,
|
AccessKey: *gitspaceConfig.GitspaceInstance.AccessKey,
|
||||||
AccessType: gitspaceConfig.GitspaceInstance.AccessType,
|
AccessType: gitspaceConfig.GitspaceInstance.AccessType,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := e.setupGitspaceAndIDE(
|
if err := e.setupGitspaceAndIDE(
|
||||||
|
@ -598,7 +598,7 @@ func (e *EmbeddedDockerOrchestrator) buildSetupSteps(
|
||||||
gitspaceLogger gitspaceTypes.GitspaceLogger,
|
gitspaceLogger gitspaceTypes.GitspaceLogger,
|
||||||
) error {
|
) error {
|
||||||
command := ExtractLifecycleCommands(PostCreateAction, devcontainerConfig)
|
command := ExtractLifecycleCommands(PostCreateAction, devcontainerConfig)
|
||||||
return ExecuteCommands(ctx, exec, codeRepoDir, gitspaceLogger, command, PostCreateAction)
|
return ExecuteLifecycleCommands(ctx, *exec, codeRepoDir, gitspaceLogger, command, PostCreateAction)
|
||||||
},
|
},
|
||||||
StopOnFailure: false,
|
StopOnFailure: false,
|
||||||
},
|
},
|
||||||
|
@ -610,7 +610,7 @@ func (e *EmbeddedDockerOrchestrator) buildSetupSteps(
|
||||||
gitspaceLogger gitspaceTypes.GitspaceLogger,
|
gitspaceLogger gitspaceTypes.GitspaceLogger,
|
||||||
) error {
|
) error {
|
||||||
command := ExtractLifecycleCommands(PostStartAction, devcontainerConfig)
|
command := ExtractLifecycleCommands(PostStartAction, devcontainerConfig)
|
||||||
return ExecuteCommands(ctx, exec, codeRepoDir, gitspaceLogger, command, PostStartAction)
|
return ExecuteLifecycleCommands(ctx, *exec, codeRepoDir, gitspaceLogger, command, PostStartAction)
|
||||||
},
|
},
|
||||||
StopOnFailure: false,
|
StopOnFailure: false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,47 +16,93 @@ package devcontainer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
|
|
||||||
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const RootUser = "root"
|
const RootUser = "root"
|
||||||
const ErrMsgTCP = "unable to upgrade to tcp, received 200"
|
const ErrMsgTCP = "unable to upgrade to tcp, received 200"
|
||||||
const LoggerErrorPrefix = "ERR>> "
|
const LoggerErrorPrefix = "ERR>> "
|
||||||
|
const ChannelExitStatus = "DEVCONTAINER_EXIT_STATUS"
|
||||||
|
|
||||||
type Exec struct {
|
type Exec struct {
|
||||||
ContainerName string
|
ContainerName string
|
||||||
DockerClient *client.Client
|
DockerClient *client.Client
|
||||||
HomeDir string
|
DefaultWorkingDir string
|
||||||
RemoteUser string
|
RemoteUser string
|
||||||
AccessKey string
|
AccessKey string
|
||||||
AccessType enum.GitspaceAccessType
|
AccessType enum.GitspaceAccessType
|
||||||
}
|
}
|
||||||
|
|
||||||
type execResult struct {
|
type execResult struct {
|
||||||
StdOut io.Reader
|
ExecID string
|
||||||
StdErr io.Reader
|
StdOut io.Reader
|
||||||
ExitCode int
|
StdErr io.Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exec) ExecuteCommand(
|
func (e *Exec) ExecuteCommand(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
command string,
|
command string,
|
||||||
root bool,
|
root bool,
|
||||||
detach bool,
|
|
||||||
workingDir string,
|
workingDir string,
|
||||||
outputCh chan []byte, // channel to stream output as []byte
|
) (string, error) {
|
||||||
) error {
|
containerExecCreate, err := e.createExecution(ctx, command, root, workingDir, false)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Attach and inspect exec session to get the output
|
||||||
|
inspectExec, err := e.attachAndInspectExec(ctx, containerExecCreate.ID, false)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to start docker exec for container %s: %w", e.ContainerName, err)
|
||||||
|
}
|
||||||
|
var stdoutBuf bytes.Buffer
|
||||||
|
var stderrBuf bytes.Buffer
|
||||||
|
|
||||||
|
stdoutData, err := io.ReadAll(inspectExec.StdOut)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error reading stdout: %w", err)
|
||||||
|
}
|
||||||
|
stdoutBuf.Write(stdoutData)
|
||||||
|
stderrData, err := io.ReadAll(inspectExec.StdErr)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error reading stderr: %w", err)
|
||||||
|
}
|
||||||
|
stderrBuf.Write(stderrData)
|
||||||
|
inspect, err := e.DockerClient.ContainerExecInspect(ctx, containerExecCreate.ID)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to inspect exec session: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the exit code is non-zero, return both stdout and stderr
|
||||||
|
if inspect.ExitCode != 0 {
|
||||||
|
// Combine stdout and stderr
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"STDOUT:\n%s\nSTDERR:\n%s", stdoutBuf.String(), stderrBuf.String()),
|
||||||
|
fmt.Errorf("command exited with non-zero status: %d", inspect.ExitCode)
|
||||||
|
}
|
||||||
|
// If the exit code is zero, only return stdout
|
||||||
|
return stdoutBuf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exec) createExecution(
|
||||||
|
ctx context.Context,
|
||||||
|
command string,
|
||||||
|
root bool,
|
||||||
|
workingDir string,
|
||||||
|
detach bool,
|
||||||
|
) (*dockerTypes.IDResponse, error) {
|
||||||
user := e.RemoteUser
|
user := e.RemoteUser
|
||||||
if root {
|
if root {
|
||||||
user = RootUser
|
user = RootUser
|
||||||
|
@ -73,11 +119,26 @@ func (e *Exec) ExecuteCommand(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create exec instance for the container
|
// Create exec instance for the container
|
||||||
|
log.Debug().Msgf("Creating execution for container %s", e.ContainerName)
|
||||||
containerExecCreate, err := e.DockerClient.ContainerExecCreate(ctx, e.ContainerName, execConfig)
|
containerExecCreate, err := e.DockerClient.ContainerExecCreate(ctx, e.ContainerName, execConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create docker exec for container %s: %w", e.ContainerName, err)
|
return nil, fmt.Errorf("failed to create docker exec for container %s: %w", e.ContainerName, err)
|
||||||
}
|
}
|
||||||
|
return &containerExecCreate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exec) executeCmdAsyncStream(
|
||||||
|
ctx context.Context,
|
||||||
|
command string,
|
||||||
|
root bool,
|
||||||
|
detach bool,
|
||||||
|
workingDir string,
|
||||||
|
outputCh chan []byte, // channel to stream output as []byte
|
||||||
|
) error {
|
||||||
|
containerExecCreate, err := e.createExecution(ctx, command, root, workingDir, detach)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// Attach and inspect exec session to get the output
|
// Attach and inspect exec session to get the output
|
||||||
inspectExec, err := e.attachAndInspectExec(ctx, containerExecCreate.ID, detach)
|
inspectExec, err := e.attachAndInspectExec(ctx, containerExecCreate.ID, detach)
|
||||||
if err != nil && !strings.Contains(err.Error(), ErrMsgTCP) {
|
if err != nil && !strings.Contains(err.Error(), ErrMsgTCP) {
|
||||||
|
@ -88,25 +149,18 @@ func (e *Exec) ExecuteCommand(
|
||||||
close(outputCh)
|
close(outputCh)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the exit code after the command completes
|
|
||||||
if inspectExec != nil && inspectExec.ExitCode != 0 {
|
|
||||||
return fmt.Errorf("error during command execution in container %s. exit code %d",
|
|
||||||
e.ContainerName, inspectExec.ExitCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
e.streamResponse(inspectExec, outputCh)
|
e.streamResponse(inspectExec, outputCh)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exec) ExecuteCommandInHomeDirectory(
|
func (e *Exec) ExecuteCmdInHomeDirectoryAsyncStream(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
command string,
|
command string,
|
||||||
root bool,
|
root bool,
|
||||||
detach bool,
|
detach bool,
|
||||||
outputCh chan []byte, // channel to stream output as []byte
|
outputCh chan []byte, // channel to stream output as []byte
|
||||||
) error {
|
) error {
|
||||||
return e.ExecuteCommand(ctx, command, root, detach, e.HomeDir, outputCh)
|
return e.executeCmdAsyncStream(ctx, command, root, detach, e.DefaultWorkingDir, outputCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exec) attachAndInspectExec(ctx context.Context, id string, detach bool) (*execResult, error) {
|
func (e *Exec) attachAndInspectExec(ctx context.Context, id string, detach bool) (*execResult, error) {
|
||||||
|
@ -117,7 +171,6 @@ func (e *Exec) attachAndInspectExec(ctx context.Context, id string, detach bool)
|
||||||
|
|
||||||
// If in detach mode, we just need to close the connection, not process output
|
// If in detach mode, we just need to close the connection, not process output
|
||||||
if detach {
|
if detach {
|
||||||
// No need to process output in detach mode, so we simply close the connection
|
|
||||||
resp.Close()
|
resp.Close()
|
||||||
return nil, nil //nolint:nilnil
|
return nil, nil //nolint:nilnil
|
||||||
}
|
}
|
||||||
|
@ -130,6 +183,7 @@ func (e *Exec) attachAndInspectExec(ctx context.Context, id string, detach bool)
|
||||||
|
|
||||||
// Return the output streams and the response
|
// Return the output streams and the response
|
||||||
return &execResult{
|
return &execResult{
|
||||||
|
ExecID: id,
|
||||||
StdOut: stdoutPipe, // Pipe for stdout
|
StdOut: stdoutPipe, // Pipe for stdout
|
||||||
StdErr: stderrPipe, // Pipe for stderr
|
StdErr: stderrPipe, // Pipe for stderr
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -138,6 +192,7 @@ func (e *Exec) attachAndInspectExec(ctx context.Context, id string, detach bool)
|
||||||
func (e *Exec) streamResponse(resp *execResult, outputCh chan []byte) {
|
func (e *Exec) streamResponse(resp *execResult, outputCh chan []byte) {
|
||||||
// Stream the output asynchronously if not in detach mode
|
// Stream the output asynchronously if not in detach mode
|
||||||
go func() {
|
go func() {
|
||||||
|
defer close(outputCh)
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
@ -153,20 +208,31 @@ func (e *Exec) streamResponse(resp *execResult, outputCh chan []byte) {
|
||||||
}
|
}
|
||||||
// Wait for all readers to finish before closing the channel
|
// Wait for all readers to finish before closing the channel
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
// Close the output channel after all output has been processed
|
|
||||||
close(outputCh)
|
// Now that streaming is finished, inspect the exit status
|
||||||
|
log.Debug().Msgf("Inspecting container for status: %s", resp.ExecID)
|
||||||
|
inspect, err := e.DockerClient.ContainerExecInspect(context.Background(), resp.ExecID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msgf("Failed to inspect exec session: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the exit status as a final message
|
||||||
|
exitStatusMsg := fmt.Sprintf(ChannelExitStatus+"%d", inspect.ExitCode)
|
||||||
|
outputCh <- []byte(exitStatusMsg)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// copyOutput copies the output from the exec response to the pipes, and is blocking.
|
|
||||||
func (e *Exec) copyOutput(reader io.Reader, stdoutWriter, stderrWriter io.WriteCloser) {
|
func (e *Exec) copyOutput(reader io.Reader, stdoutWriter, stderrWriter io.WriteCloser) {
|
||||||
|
defer func() {
|
||||||
|
stdoutWriter.Close()
|
||||||
|
stderrWriter.Close()
|
||||||
|
}()
|
||||||
_, err := stdcopy.StdCopy(stdoutWriter, stderrWriter, reader)
|
_, err := stdcopy.StdCopy(stdoutWriter, stderrWriter, reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error copying output: %v", err)
|
log.Error().Err(err).Msg("Error in stdcopy.StdCopy " + err.Error())
|
||||||
}
|
}
|
||||||
stdoutWriter.Close()
|
|
||||||
stderrWriter.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// streamStdOut reads from the stdout pipe and sends each line to the output channel.
|
// streamStdOut reads from the stdout pipe and sends each line to the output channel.
|
||||||
|
@ -174,10 +240,16 @@ func (e *Exec) streamStdOut(stdout io.Reader, outputCh chan []byte, wg *sync.Wai
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
stdoutReader := bufio.NewScanner(stdout)
|
stdoutReader := bufio.NewScanner(stdout)
|
||||||
for stdoutReader.Scan() {
|
for stdoutReader.Scan() {
|
||||||
outputCh <- stdoutReader.Bytes()
|
select {
|
||||||
|
case <-context.Background().Done():
|
||||||
|
log.Info().Msg("Context canceled, stopping stdout streaming")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
outputCh <- stdoutReader.Bytes()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := stdoutReader.Err(); err != nil {
|
if err := stdoutReader.Err(); err != nil {
|
||||||
log.Println("Error reading stdout:", err)
|
log.Error().Err(err).Msg("Error reading stdout " + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,9 +258,15 @@ func (e *Exec) streamStdErr(stderr io.Reader, outputCh chan []byte, wg *sync.Wai
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
stderrReader := bufio.NewScanner(stderr)
|
stderrReader := bufio.NewScanner(stderr)
|
||||||
for stderrReader.Scan() {
|
for stderrReader.Scan() {
|
||||||
outputCh <- []byte(LoggerErrorPrefix + stderrReader.Text())
|
select {
|
||||||
|
case <-context.Background().Done():
|
||||||
|
log.Info().Msg("Context canceled, stopping stderr streaming")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
outputCh <- []byte(LoggerErrorPrefix + stderrReader.Text())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := stderrReader.Err(); err != nil {
|
if err := stderrReader.Err(); err != nil {
|
||||||
log.Println("Error reading stderr:", err)
|
log.Error().Err(err).Msg("Error reading stderr " + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/app/gitspace/orchestrator/common"
|
||||||
"github.com/harness/gitness/app/gitspace/orchestrator/devcontainer"
|
"github.com/harness/gitness/app/gitspace/orchestrator/devcontainer"
|
||||||
"github.com/harness/gitness/app/gitspace/orchestrator/template"
|
"github.com/harness/gitness/app/gitspace/orchestrator/template"
|
||||||
gitspaceTypes "github.com/harness/gitness/app/gitspace/types"
|
gitspaceTypes "github.com/harness/gitness/app/gitspace/types"
|
||||||
|
@ -81,18 +82,10 @@ func (v *VSCodeWeb) Setup(
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
outputCh := make(chan []byte)
|
err = common.ExecuteCommandInHomeDirAndLog(ctx, exec, setupScript, false, gitspaceLogger, false)
|
||||||
err = exec.ExecuteCommandInHomeDirectory(ctx, setupScript, false, false, outputCh)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to install VSCode Web: %w", err)
|
return fmt.Errorf("failed to install VSCode Web: %w", err)
|
||||||
}
|
}
|
||||||
for chunk := range outputCh {
|
|
||||||
_, err := io.Discard.Write(chunk)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = v.updateMediaContent(ctx, exec); err != nil {
|
if err = v.updateMediaContent(ctx, exec); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -101,18 +94,11 @@ func (v *VSCodeWeb) Setup(
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VSCodeWeb) updateMediaContent(ctx context.Context, exec *devcontainer.Exec) error {
|
func (v *VSCodeWeb) updateMediaContent(ctx context.Context, exec *devcontainer.Exec) error {
|
||||||
findCh := make(chan []byte)
|
findOutput, err := exec.ExecuteCommand(ctx, findPathScript, true, exec.DefaultWorkingDir)
|
||||||
err := exec.ExecuteCommandInHomeDirectory(ctx, findPathScript, true, false, findCh)
|
|
||||||
var findOutput []byte
|
|
||||||
|
|
||||||
for chunk := range findCh {
|
|
||||||
findOutput = append(findOutput, chunk...) // Concatenate each chunk of data
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to find VSCode Web install path: %w", err)
|
return fmt.Errorf("failed to find VSCode Web install path: %w", err)
|
||||||
}
|
}
|
||||||
path := string(findOutput)
|
path := findOutput
|
||||||
startIndex := strings.Index(path, startMarker)
|
startIndex := strings.Index(path, startMarker)
|
||||||
endIndex := strings.Index(path, endMarker)
|
endIndex := strings.Index(path, endMarker)
|
||||||
if startIndex == -1 || endIndex == -1 || startIndex >= endIndex {
|
if startIndex == -1 || endIndex == -1 || startIndex >= endIndex {
|
||||||
|
@ -153,10 +139,8 @@ func (v *VSCodeWeb) Run(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
gitspaceLogger.Info("Starting IDE ...")
|
gitspaceLogger.Info("Starting IDE ...")
|
||||||
outputCh := make(chan []byte)
|
|
||||||
|
|
||||||
// Execute the script in the home directory
|
// Execute the script in the home directory
|
||||||
err = exec.ExecuteCommandInHomeDirectory(ctx, runScript, false, false, outputCh)
|
err = common.ExecuteCommandInHomeDirAndLog(ctx, exec, runScript, false, gitspaceLogger, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to run VSCode Web: %w", err)
|
return fmt.Errorf("failed to run VSCode Web: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,11 @@ port={{ .Port }}
|
||||||
proxyuri="{{ .ProxyURI }}"
|
proxyuri="{{ .ProxyURI }}"
|
||||||
|
|
||||||
# Ensure the configuration directory exists
|
# Ensure the configuration directory exists
|
||||||
mkdir -p $HOME/.config/code-server
|
config_dir="$HOME/.config/code-server"
|
||||||
|
mkdir -p "$config_dir"
|
||||||
|
|
||||||
# Create or overwrite the config file with new settings
|
# Create or overwrite the config file with new settings
|
||||||
cat > $HOME/.config/code-server/config.yaml <<EOF
|
cat > "$config_dir/config.yaml" <<EOF
|
||||||
bind-addr: 0.0.0.0:$port
|
bind-addr: 0.0.0.0:$port
|
||||||
auth: none
|
auth: none
|
||||||
cert: false
|
cert: false
|
||||||
|
@ -22,5 +23,26 @@ if [ -n "$proxyuri" ]; then
|
||||||
echo "Exported VSCODE_PROXY_URI: $proxyuri"
|
echo "Exported VSCODE_PROXY_URI: $proxyuri"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run code-server with templated arguments
|
# Start code-server in the background
|
||||||
eval "code-server --disable-workspace-trust"
|
nohup code-server --disable-workspace-trust > "$HOME/code-server.log" 2>&1 &
|
||||||
|
code_server_pid=$!
|
||||||
|
|
||||||
|
# Wait for the code-server IPC socket file to exist
|
||||||
|
echo "Waiting for vscode web to start..."
|
||||||
|
while true; do
|
||||||
|
# Check if the process is still running
|
||||||
|
if ! kill -0 "$code_server_pid" 2>/dev/null; then
|
||||||
|
echo "Error: code-server process has stopped unexpectedly."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for the IPC socket
|
||||||
|
ipc_socket=$(find "$HOME/.local/" -type s -name "code-server-ipc.sock" 2>/dev/null)
|
||||||
|
if [ -n "$ipc_socket" ]; then
|
||||||
|
echo "vscode web is now running and ready."
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
|
||||||
|
exit 0
|
|
@ -45,7 +45,7 @@ func (u *ServiceImpl) Manage(
|
||||||
Username: exec.RemoteUser,
|
Username: exec.RemoteUser,
|
||||||
AccessKey: exec.AccessKey,
|
AccessKey: exec.AccessKey,
|
||||||
AccessType: exec.AccessType,
|
AccessType: exec.AccessType,
|
||||||
HomeDir: exec.HomeDir,
|
HomeDir: exec.DefaultWorkingDir,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
|
|
Loading…
Reference in New Issue