Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ONPREM-1829] [HACKWEEK] Add initial support for Windows containers #96

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
Refactor platform-specific code
  • Loading branch information
christian-stephen committed Feb 21, 2025
commit 413f1026f3e774e7c41438506b817f26938f9d56
4 changes: 3 additions & 1 deletion .goreleaser/dockers.yaml
Original file line number Diff line number Diff line change
@@ -31,7 +31,9 @@ dockers:
extra_files:
- ./target/bin/linux/arm64/orchestrator

# Windows images
# Windows Images: Note that Windows containers require a container OS and have nuanced version compatibility
# (see https://learn.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/version-compatibility).
# Therefore, we target various versions for the base image. Currently, we provide images for Server 2019, 2022, and 2025.
- id: init-windows-server-2019
image_templates: ["circleci/runner-init:agent-windows-server-2019{{.Env.IMAGE_TAG_SUFFIX}}"]
dockerfile: ./docker/windows.Dockerfile
9 changes: 9 additions & 0 deletions init/binaries_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//go:build !windows

package init

const (
binOrchestrator = "orchestrator"
binCircleciAgent = "circleci-agent"
binCircleci = "circleci"
)
7 changes: 7 additions & 0 deletions init/binaries_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package init

const (
binOrchestrator = "orchestrator.exe"
binCircleciAgent = "circleci-agent.exe"
binCircleci = "circleci.exe"
)
27 changes: 15 additions & 12 deletions init/init_unix.go → init/init.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
//go:build !windows

package init

import (
"errors"
"io"
"os"
"path/filepath"
"runtime"
)

const (
binOrchestrator = "orchestrator"
binCircleciAgent = "circleci-agent"
binCircleci = "circleci"
)

// Run function performs the copying of specific files and symlink creation
// Run function performs the copying of the orchestrator and task-agent binaries
func Run(srcDir, destDir string) error {
// Copy the orchestrator binary
orchestratorSrc := filepath.Join(srcDir, binOrchestrator)
@@ -30,9 +23,19 @@ func Run(srcDir, destDir string) error {
if err := copyFile(agentSrc, agentDest); err != nil {
return err
}
// Create symbolic link from "circleci-agent" to "circleci"
if err := os.Symlink(agentDest, filepath.Join(destDir, binCircleci)); err != nil {
return err

circleciDest := filepath.Join(destDir, binCircleci)
if runtime.GOOS != "windows" {
// Create symbolic link from "circleci-agent" to "circleci"
if err := os.Symlink(agentDest, circleciDest); err != nil {
return err
}
} else {
// We copy the binary instead of creating a symlink to `circleci` as we do on Linux,
// since we do not have the necessary privileges to create symlinks to the shared volume on Windows.
if err := copyFile(agentSrc, circleciDest); err != nil {
return err
}
}

return nil
68 changes: 0 additions & 68 deletions init/init_windows.go

This file was deleted.

132 changes: 132 additions & 0 deletions task/cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package cmd

import (
"context"
"fmt"
"io"
"os"
"os/exec"
"strings"
"sync/atomic"
"syscall"
)

type Command struct {
cmd *exec.Cmd
stderrSaver *prefixSuffixSaver
isStarted atomic.Bool
isCompleted atomic.Bool
forwardSignals bool
waitCh chan error
}

func New(ctx context.Context, cmd []string, forwardSignals bool, user string, env ...string) Command {
s := &prefixSuffixSaver{N: 160}
return Command{
cmd: newCmd(ctx, cmd, user, s, env...),
stderrSaver: s,
forwardSignals: forwardSignals,
waitCh: make(chan error, 1),
}
}

func (c *Command) Start() error {
cmd := c.cmd

if err := cmd.Start(); err != nil {
return err
}

if cmd.Process == nil {
return fmt.Errorf("no underlying process")
}

go func() {
c.waitCh <- c.wait()
}()

if c.forwardSignals {
forwardSignals(cmd)
}

c.isStarted.Store(true)

return nil
}

func (c *Command) StartWithStdin(b []byte) error {
w, err := c.cmd.StdinPipe()

if err != nil {
return fmt.Errorf("unexpected error on stdin pipe: %w", err)
}
defer func() {
_ = w.Close()
}()

_, err = w.Write(b)
if err != nil {
return fmt.Errorf("failed to write to stdin pipe: %w", err)
}

return c.Start()
}

func (c *Command) Wait() error {
return <-c.waitCh
}

func (c *Command) wait() error {
cmd := c.cmd
defer func() {
_ = cmd.Cancel()

c.isCompleted.Store(cmd.ProcessState != nil)
}()

err := cmd.Wait()
if err != nil {
stderr := c.stderrSaver.Bytes()
if len(stderr) > 0 {
return fmt.Errorf("%w: %s", err, string(stderr))
}
}
return err
}

func (c *Command) IsRunning() (bool, error) {
if !c.isStarted.Load() {
return false, nil
}

return !c.isCompleted.Load(), nil
}

func newCmd(ctx context.Context, argv []string, user string, stderrSaver *prefixSuffixSaver, env ...string) *exec.Cmd {
//#nosec:G204 // this is intentionally setting up a command
cmd := exec.CommandContext(ctx, argv[0], argv[1:]...)

for _, env := range os.Environ() {
if strings.HasPrefix(env, "CIRCLECI_GOAT") {
// Prevent internal configuration from being injected in the command environment
continue
}
cmd.Env = append(cmd.Env, env)
}
if env != nil {
cmd.Env = append(cmd.Env, env...)
}

cmd.Stdout = os.Stdout
cmd.Stderr = io.MultiWriter(os.Stderr, stderrSaver)

cmd.SysProcAttr = &syscall.SysProcAttr{}

if user != "" {
switchUser(ctx, cmd, user)
}

additionalSetup(ctx, cmd)

return cmd
}
Loading
Oops, something went wrong.