mirror of https://github.com/harness/drone.git
155 lines
3.7 KiB
Go
155 lines
3.7 KiB
Go
// 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 devcontainer
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/harness/gitness/types/enum"
|
|
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/docker/pkg/stdcopy"
|
|
)
|
|
|
|
const RootUser = "root"
|
|
|
|
type Exec struct {
|
|
ContainerName string
|
|
DockerClient *client.Client
|
|
HomeDir string
|
|
UserIdentifier string
|
|
AccessKey string
|
|
AccessType enum.GitspaceAccessType
|
|
}
|
|
|
|
type execResult struct {
|
|
StdOut []byte
|
|
StdErr []byte
|
|
ExitCode int
|
|
}
|
|
|
|
func (e *Exec) ExecuteCommand(
|
|
ctx context.Context,
|
|
command string,
|
|
root bool,
|
|
detach bool,
|
|
workingDir string,
|
|
) ([]byte, error) {
|
|
user := e.UserIdentifier
|
|
if root {
|
|
user = RootUser
|
|
}
|
|
|
|
cmd := []string{"/bin/sh", "-c", command}
|
|
|
|
execConfig := container.ExecOptions{
|
|
User: user,
|
|
AttachStdout: true,
|
|
AttachStderr: true,
|
|
Cmd: cmd,
|
|
Detach: detach,
|
|
WorkingDir: workingDir,
|
|
}
|
|
|
|
execID, err := e.DockerClient.ContainerExecCreate(ctx, e.ContainerName, execConfig)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create docker exec for container %s: %w", e.ContainerName, err)
|
|
}
|
|
|
|
resp, err := e.attachAndInspectExec(ctx, execID.ID, detach)
|
|
if err != nil && err.Error() != "unable to upgrade to tcp, received 200" {
|
|
return nil, fmt.Errorf("failed to start docker exec for container %s: %w", e.ContainerName, err)
|
|
}
|
|
|
|
if resp != nil && resp.ExitCode != 0 {
|
|
var errLog string
|
|
if resp.StdErr != nil {
|
|
errLog = string(resp.StdErr)
|
|
}
|
|
|
|
return nil, fmt.Errorf("error during command execution in container %s. exit code %d. log: %s",
|
|
e.ContainerName, resp.ExitCode, errLog)
|
|
}
|
|
|
|
var stdOutput []byte
|
|
if resp != nil {
|
|
stdOutput = resp.StdOut
|
|
}
|
|
|
|
return stdOutput, nil
|
|
}
|
|
|
|
func (e *Exec) ExecuteCommandInHomeDirectory(
|
|
ctx context.Context,
|
|
command string,
|
|
root bool,
|
|
detach bool,
|
|
) ([]byte, error) {
|
|
return e.ExecuteCommand(ctx, command, root, detach, e.HomeDir)
|
|
}
|
|
|
|
func (e *Exec) attachAndInspectExec(ctx context.Context, id string, detach bool) (*execResult, error) {
|
|
resp, attachErr := e.DockerClient.ContainerExecAttach(ctx, id, container.ExecStartOptions{Detach: detach})
|
|
if attachErr != nil {
|
|
return nil, attachErr
|
|
}
|
|
defer resp.Close()
|
|
|
|
var outBuf, errBuf bytes.Buffer
|
|
copyErr := make(chan error)
|
|
|
|
go func() {
|
|
// StdCopy demultiplexes the stream into two buffers
|
|
_, err := stdcopy.StdCopy(&outBuf, &errBuf, resp.Reader)
|
|
copyErr <- err
|
|
}()
|
|
|
|
select {
|
|
case err := <-copyErr:
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
break
|
|
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
}
|
|
|
|
stdout, err := io.ReadAll(&outBuf)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read stdout of exec for container %s: %w", e.ContainerName, err)
|
|
}
|
|
|
|
stderr, err := io.ReadAll(&errBuf)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read stderr of exec for container %s: %w", e.ContainerName, err)
|
|
}
|
|
|
|
inspectRes, err := e.DockerClient.ContainerExecInspect(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to inspect exec for container %s: %w", e.ContainerName, err)
|
|
}
|
|
|
|
return &execResult{
|
|
StdOut: stdout,
|
|
StdErr: stderr,
|
|
ExitCode: inspectRes.ExitCode,
|
|
}, nil
|
|
}
|