command-runner/internal/containers/finch/finch_service.go (114 lines of code) (raw):
package finch
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"os"
"os/exec"
"strings"
"sync"
"github.com/aws/codecatalyst-runner-cli/command-runner/internal/containers/types"
"github.com/aws/codecatalyst-runner-cli/command-runner/pkg/common"
"github.com/rs/zerolog/log"
)
type ServiceProvider struct {
available *bool
mutex sync.Mutex
}
func (f *ServiceProvider) NewContainerService() types.ContainerService {
return &finchContainerService{}
}
type finchContainerService struct{}
func (f *ServiceProvider) Available(ctx context.Context) bool {
f.mutex.Lock()
defer f.mutex.Unlock()
if f.available != nil {
return *f.available
}
var avail bool
if os.Getenv("NOFINCH") != "" {
avail = false
} else if f, err := newFinch(finchInstallDir); err != nil {
log.Ctx(ctx).Debug().Err(err).Msg("finch is not installed")
avail = false
} else if rout, rerr, err := f.RunWithoutStdio(ctx, "container", "ls"); err != nil {
log.Ctx(ctx).Debug().Err(err).Msgf("finch is unavailable: %s\n%s", rout, rerr)
avail = false
} else {
avail = true
}
f.available = &avail
return *f.available
}
func (fcs *finchContainerService) NewContainer(input types.NewContainerInput) types.Container {
cr := new(finchContainer)
cr.input = input
return cr
}
// ImageExistsLocally returns a boolean indicating if an image with the
// requested name, tag and architecture exists in the local docker image store
func (fcs *finchContainerService) ImageExistsLocally(ctx context.Context, imageName string, platform string) (bool, error) {
return imageExistsLocally(ctx, imageName, platform)
}
const finchInstallDir = "/Applications/Finch"
type finch struct {
installDir string
}
func newFinch(installDir string) (*finch, error) {
if stat, err := os.Stat(installDir); err != nil {
return nil, err
} else if !stat.IsDir() {
return nil, fmt.Errorf("invalid finch installation directory %s", installDir)
}
return &finch{
installDir: installDir,
}, nil
}
func (f *finch) RunWithoutStdio(ctx context.Context, args ...string) ([]byte, []byte, error) {
return f.RunWithStdin(ctx, nil, args...)
}
func (f *finch) RunWithStdin(ctx context.Context, in io.Reader, args ...string) ([]byte, []byte, error) {
var bout bytes.Buffer
var berr bytes.Buffer
err := f.RunWithStdio(ctx, in, &bout, &berr, args...)
return bout.Bytes(), berr.Bytes(), err
}
func (f *finch) RunWithStdio(ctx context.Context, stdin io.Reader, stdout io.Writer, stderr io.Writer, args ...string) error {
finchCmd := fmt.Sprintf("%s/bin/finch", f.installDir)
args = append([]string{finchCmd}, args...)
cmd := exec.CommandContext(ctx, finchCmd) //#nosec G204
cmd.Path = finchCmd
cmd.Args = args
cmd.Stdin = stdin
cmdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
cmderr, err := cmd.StderrPipe()
if err != nil {
return err
}
log.Ctx(ctx).Debug().Msgf("🐦 %s", strings.Join(args, " "))
if common.Dryrun(ctx) {
log.Ctx(ctx).Debug().Msgf("exit for dryrun")
return nil
}
if err := cmd.Start(); err != nil {
return fmt.Errorf("unable to start command: %w", err)
}
if stdout != nil {
go streamPipe(stdout, cmdout)
}
if stderr != nil {
go streamPipe(stderr, cmderr)
}
err = cmd.Wait()
if err != nil {
return fmt.Errorf("finch command failed: %w", err)
}
return nil
}
func streamPipe(dst io.Writer, src io.ReadCloser) {
reader := bufio.NewReader(src)
_, _ = io.Copy(dst, reader)
}