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

Add podman backend #305

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ require (
github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/bmatcuk/doublestar/v4 v4.0.2
github.com/containerd/containerd v1.5.7 // indirect
github.com/containers/podman/v3 v3.4.1
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/docker/cli v20.10.10+incompatible
github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker v20.10.10+incompatible
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0
github.com/drone/envsubst v1.0.3
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568
Expand All @@ -33,7 +32,6 @@ require (
github.com/mattn/go-sqlite3 v1.14.9
github.com/moby/moby v20.10.10+incompatible
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
github.com/morikuni/aec v1.0.0 // indirect
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.11.0
Expand Down
227 changes: 224 additions & 3 deletions go.sum

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion pipeline/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"github.com/woodpecker-ci/woodpecker/pipeline/backend/docker"
"github.com/woodpecker-ci/woodpecker/pipeline/backend/podman"
"github.com/woodpecker-ci/woodpecker/pipeline/backend/types"
)

Expand All @@ -14,13 +15,19 @@ var (
func init() {
engines = make(map[string]types.Engine)

var engine types.Engine

// TODO: disabled for now as kubernetes backend has not been implemented yet
// kubernetes
// engine = kubernetes.New("", "", "")
// engines[engine.Name()] = engine

// podman
engine = podman.New()
engines[engine.Name()] = engine

// docker
engine := docker.New()
engine = docker.New()
engines[engine.Name()] = engine
}

Expand Down
77 changes: 77 additions & 0 deletions pipeline/backend/podman/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package podman

import (
"fmt"
"net"

"github.com/containers/podman/v3/pkg/specgen"

backend "github.com/woodpecker-ci/woodpecker/pipeline/backend/types"
)

// returns specgen.SpecGenerator container config
func toSpecGenerator(proc *backend.Step) (*specgen.SpecGenerator, error) {
var err error
specGen := specgen.NewSpecGenerator(proc.Image, false)
specGen.Terminal = true

specGen.Image = proc.Image
specGen.Name = proc.Name
specGen.Labels = proc.Labels
specGen.WorkDir = proc.WorkingDir

if len(proc.Environment) > 0 {
specGen.Env = proc.Environment
}
if len(proc.Command) > 0 {
specGen.Command = proc.Command
}
fmt.Printf("specgenentrypoint: %v\n", proc.Entrypoint)
if len(proc.Entrypoint) > 0 {
specGen.Entrypoint = proc.Entrypoint
}
fmt.Printf("specgenvolumes: %v\n", proc.Volumes)
if len(proc.Volumes) > 0 {
for _, v := range proc.Volumes {
fmt.Printf("proc.vol: %v\n", v)
}
_, vols, _, err := specgen.GenVolumeMounts(proc.Volumes)
if err != nil {
return nil, err
}
for _, vol := range vols {
fmt.Printf("specgenvol: %v\n", vol)
specGen.Volumes = append(specGen.Volumes, vol)
}
}

specGen.LogConfiguration = &specgen.LogConfig{
//Driver: "json-file",
}
// TODO: Privileged seems to be required
specGen.Privileged = true
specGen.ShmSize = new(int64)
*specGen.ShmSize = proc.ShmSize
specGen.Sysctl = proc.Sysctls

if len(proc.IpcMode) > 0 {
if specGen.IpcNS, err = specgen.ParseNamespace(proc.IpcMode); err != nil {
return nil, err
}
}
if len(proc.DNS) > 0 {
for _, dns := range proc.DNS {
if ip := net.ParseIP(dns); ip != nil {
specGen.DNSServers = append(specGen.DNSServers, ip)
}
}
}
if len(proc.DNSSearch) > 0 {
specGen.DNSSearch = proc.DNSSearch
}
if len(proc.ExtraHosts) > 0 {
specGen.HostAdd = proc.ExtraHosts
}

return specGen, err
}
261 changes: 261 additions & 0 deletions pipeline/backend/podman/podman.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
package podman

import (
"context"
"fmt"
"io"
"os"

backend "github.com/woodpecker-ci/woodpecker/pipeline/backend/types"

"github.com/containers/podman/v3/pkg/bindings"
"github.com/containers/podman/v3/pkg/bindings/containers"
"github.com/containers/podman/v3/pkg/bindings/images"
"github.com/containers/podman/v3/pkg/bindings/network"
"github.com/containers/podman/v3/pkg/bindings/volumes"
"github.com/containers/podman/v3/pkg/domain/entities"
)

var (
noContext = context.Background()

startOpts = containers.StartOptions{}

removeOpts = containers.RemoveOptions{
Ignore: new(bool),
Force: new(bool),
Volumes: new(bool),
}

logsOpts = containers.LogOptions{
Follow: new(bool),
//Since: new(string),
//Stderr: new(bool),
Stdout: new(bool),
//Tail: new(string),
//Timestamps: new(bool),
//Until: new(string),
}

killOpts = containers.KillOptions{
Signal: new(string),
}

volRemoveOpts = volumes.RemoveOptions{
Force: new(bool),
}
)

type engine struct {
conn context.Context
socket string
}

// New returns a new Podman Engine using the given client.
func New() backend.Engine {
return &engine{
conn: nil,
socket: "unix:" + os.Getenv("XDG_RUNTIME_DIR") + "/podman/podman.sock",
}
}

func (e *engine) Name() string {
return "podman"
}

func (e *engine) IsAvivable() bool {
_, err := os.Stat("/run/.containerenv")
return os.IsNotExist(err)
}

// Load new client for podman Engine using environment variables.
func (e *engine) Load() (err error) {
*removeOpts.Ignore = false
*removeOpts.Force = false
*removeOpts.Volumes = true

*logsOpts.Follow = true
*logsOpts.Stdout = true
//*logsOpts.Stderr = true
//*logsOpts.Timestamps = false

*killOpts.Signal = "SIGKILL"

*volRemoveOpts.Force = true

e.conn, err = bindings.NewConnection(context.Background(), e.socket)
fmt.Printf("e.socket: %s\n", e.socket)
fmt.Printf("e.conn: %v\n", e.conn)
fmt.Printf("err: %v\n", err)
return err
}

func (e *engine) Setup(_ context.Context, conf *backend.Config) error {
for _, vol := range conf.Volumes {
r, err := volumes.Create(e.conn, entities.VolumeCreateOptions{
Name: vol.Name,
Driver: vol.Driver,
Options: vol.DriverOpts,
// Labels: defaultLabels,
}, &volumes.CreateOptions{})
fmt.Printf("vol: %s\n", vol.Driver)
fmt.Printf("vol: %s\n", vol.DriverOpts)
fmt.Printf("vol: %s\n", vol.Name)
fmt.Printf("err: %v\n", err)
fmt.Printf("r: %v\n", r)
if err != nil {
return err
}
}
for _, netconf := range conf.Networks {
_, err := network.Create(e.conn, &network.CreateOptions{
Driver: &netconf.Driver,
Options: netconf.DriverOpts,
Name: &netconf.Name,
// Labels: defaultLabels,
})
if err != nil {
return err
}
}
return nil
}

func (e *engine) Exec(ctx context.Context, proc *backend.Step) error {
specGenerator, err := toSpecGenerator(proc)
if err != nil {
return err
}

fmt.Printf("Specgen: %v\n", specGenerator)

// create pull options with encoded authorization credentials.
pullopts := &images.PullOptions{}
if proc.AuthConfig.Username != "" && proc.AuthConfig.Password != "" {
pullopts.Username = &proc.AuthConfig.Username
pullopts.Password = &proc.AuthConfig.Password
}

pullImage := proc.Pull

// check if pull is disabled and pull image once if not existing
if !pullImage {
imageExists, err := images.Exists(e.conn, specGenerator.Image, &images.ExistsOptions{})
if err != nil {
return err
}

if !imageExists {
pullImage = true
}
}

// automatically pull the latest version of the image if requested
// by the process configuration or not existing.
if pullImage {
_, perr := images.Pull(e.conn, specGenerator.Image, pullopts)
// fix for drone/drone#1917
if perr != nil && proc.AuthConfig.Password != "" {
return perr
}
}

// fix for missing workdir
_workDir := specGenerator.WorkDir
_entryPoint := specGenerator.Entrypoint
specGenerator.WorkDir = "/"
specGenerator.Entrypoint = []string{"mkdir", "-p", proc.WorkingDir}
_, err = containers.CreateWithSpec(e.conn, specGenerator, &containers.CreateOptions{})
if err != nil {
return err
}
containers.Start(e.conn, specGenerator.Name, &startOpts)
containers.Wait(e.conn, specGenerator.Name, &containers.WaitOptions{})
containers.Remove(e.conn, specGenerator.Name, &removeOpts)

// normal start here
specGenerator.WorkDir = _workDir
specGenerator.Entrypoint = _entryPoint
_, err = containers.CreateWithSpec(e.conn, specGenerator, &containers.CreateOptions{})
if err != nil {
return err
}

return containers.Start(e.conn, specGenerator.Name, &startOpts)
}

func (e *engine) Kill(_ context.Context, proc *backend.Step) error {
return containers.Kill(e.conn, proc.Name, &killOpts)
}

func (e *engine) Wait(ctx context.Context, proc *backend.Step) (*backend.State, error) {
_, err := containers.Wait(e.conn, proc.Name, nil)
if err != nil {
return nil, err
}

info, err := containers.Inspect(e.conn, proc.Name, &containers.InspectOptions{})
if err != nil {
return nil, err
}

return &backend.State{
Exited: true,
ExitCode: int(info.State.ExitCode),
OOMKilled: info.State.OOMKilled,
}, nil
}

func (e *engine) Tail(ctx context.Context, proc *backend.Step) (io.ReadCloser, error) {
rc, wc := io.Pipe()
logChan := make(chan string, 10000)
logEnd := make(chan bool)
go func() {
defer func() {
containers.Wait(e.conn, proc.Name, nil)
logEnd <- true
}()
containers.Logs(e.conn, proc.Name, &logsOpts, logChan, nil)
}()

go func() {
for {
select {
case msg := <-logChan:
if msg != "" {
fmt.Fprint(wc, msg)
}
case <-logEnd:
for {
select {
case msg := <-logChan:
if msg != "" {
fmt.Fprint(wc, msg)
}
default:
wc.Close()
rc.Close()
return
}
}
}
}
}()
return rc, nil
}

func (e *engine) Destroy(_ context.Context, conf *backend.Config) error {
for _, stage := range conf.Stages {
for _, step := range stage.Steps {
containers.Kill(e.conn, step.Name, &killOpts)
containers.Remove(e.conn, step.Name, &removeOpts)
}
}
for _, volume := range conf.Volumes {
volumes.Remove(e.conn, volume.Name, &volRemoveOpts)
}
for _, netconf := range conf.Networks {
network.Remove(e.conn, netconf.Name, nil)
}
return nil
}
Loading