Skip to content

Run COSA as an OpenShift custom build strategy #1752

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

Merged
merged 5 commits into from
Oct 19, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
  •  
  •  
  •  
1 change: 1 addition & 0 deletions entrypoint/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
bin/*
srv/*
5 changes: 2 additions & 3 deletions entrypoint/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ version = $(shell date +%Y-%m-%d).$(shell git rev-parse --short HEAD)~$(shell te
cosa_dir = $(shell test -d /usr/lib/coreos-assembler && echo /usr/lib/coreos-assembler)
ldflags=-X main.version=${version} -X main.cosaDir=${cosa_dir}


PREFIX ?= /usr
DESTDIR ?=
ARCH:=$(shell uname -m)
Expand All @@ -21,8 +20,8 @@ fmt:

.PHONY: test
test: fmt
go test -mod=vendor -i ${pkgs}
go test -mod=vendor -cover ${pkgs}
go test -mod=vendor -tags ci -i ${pkgs} && \
go test -mod=vendor -tags ci -cover ${pkgs}

.PHONY: clean
clean:
Expand Down
51 changes: 51 additions & 0 deletions entrypoint/cmd/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package main

/*
Command interface for OpenShift.

"builder" is the sub-command that should be used as an
the container entrypoint, i.e.:
/usr/bin/dumbinit /usr/bin/entry builder
*/

import (
"github.com/coreos/entrypoint/ocp"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

var cmdOCP = &cobra.Command{
Use: "builder",
Short: "Execute as an OCP Builder",
Run: runOCP,
}

func init() {
cmdRoot.AddCommand(cmdOCP)
}

// runOCP executes the Custom Build Strategy based on
// source or binary build strategies.
func runOCP(c *cobra.Command, args []string) {
b, err := ocp.NewBuilder()
if err != nil {
log.Fatal("Failed to find the OCP build environment.")
}

if err := b.PrepareEnv(); err != nil {
log.WithFields(log.Fields{
"err": err,
}).Fatal("Failed to prepare environment.")
}

if b.JobSpec != nil {
spec = *b.JobSpec
log.Info("Jobspec will apply to templated commands.")
}

entryEnvVars = append(entryEnvVars, b.EnvVars...)

b.Exec(func(v []string) error {
return runScripts(c, v)
})
}
105 changes: 45 additions & 60 deletions entrypoint/cmd/entry.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
package main

/*
Definition for the main entry command. This defined the "human"
interfaces for `run` and `run-steps`
*/

import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sync"
"text/template"

ee "github.com/coreos/entrypoint/exec"
rhjobspec "github.com/coreos/entrypoint/spec"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
)

const cosaContainerDir = "/usr/lib/coreos-assembler"
Expand All @@ -32,6 +34,10 @@ var (
spec rhjobspec.JobSpec
specFile string

// entryEnvars are set for command execution
entryEnvVars []string

// shellCmd is the default command to execute commands.
shellCmd = []string{"/bin/bash", "-x"}

cmdRoot = &cobra.Command{
Expand Down Expand Up @@ -59,10 +65,11 @@ Wrapper for COSA commands and templates`,
}

cmdSteps = &cobra.Command{
Use: "run-steps",
Short: "Run Steps from [file]",
Args: cobra.MinimumNArgs(1),
Run: runSteps,
Use: "run-scripts",
Short: "Run Steps from [file]",
Args: cobra.MinimumNArgs(1),
RunE: runScripts,
SilenceUsage: true,
}
)

Expand All @@ -76,6 +83,8 @@ func init() {
}
}

entryEnvVars = os.Environ()

log.SetOutput(os.Stdout)
log.SetLevel(log.DebugLevel)
newPath := fmt.Sprintf("%s:%s", cosaDir, os.Getenv("PATH"))
Expand All @@ -100,116 +109,92 @@ func main() {
os.Exit(0)
}

// runSteps reads ARGs as files and executes the rendered templates.
func runSteps(c *cobra.Command, args []string) {
// runScripts reads ARGs as files and executes the rendered templates.
func runScripts(c *cobra.Command, args []string) error {
rendered := make(map[string]*os.File)
for _, v := range args {
in, err := ioutil.ReadFile(v)
if err != nil {
log.Fatal(err)
return err
}
t, err := ioutil.TempFile("", "rendered")
if err != nil {
log.Fatal(err)
return err
}
defer os.Remove(t.Name())
rendered[v] = t

tmpl, err := template.New("args").Parse(string(in))
if err != nil {
log.Fatal("failed to parse")
return fmt.Errorf("Failed to parse %s", err)
}

err = tmpl.Execute(t, spec)
if err != nil {
log.Fatal("failed render template", err)
return fmt.Errorf("Failed render template: %v", err)
}
}

log.Infof("executing %d script(s)", len(rendered))
log.Infof("Executing %d script(s)", len(rendered))
for i, v := range rendered {
log.Infof("executing script %q", i)

rc, err := runCmds(append(shellCmd, v.Name()))
log.WithFields(log.Fields{"script": i}).Info("Startig script")
cArgs := append(shellCmd, v.Name())
cmd := exec.Command(cArgs[0], cArgs[1:]...)
cmd.Env = entryEnvVars
rc, err := ee.RunCmds(cmd)
if rc != 0 {
log.WithFields(log.Fields{
"script": i,
"return code": rc,
"error": err,
}).Error("failed")
os.Exit(rc)
return fmt.Errorf("Script exited with return code %d", rc)
}
if err != nil {
return err
}
log.WithFields(log.Fields{"script": i}).Info("Script complete")
}
log.Info("done")
log.Infof("Execution complete")
return nil
}

// runSingle renders args as templates and executes the command.
func runSingle(c *cobra.Command, args []string) {
for i, v := range args {
tmpl, err := template.New("args").Parse(v)
if err != nil {
log.WithFields(log.Fields{"input": v}).Fatalf("failed to parse template")
log.WithFields(log.Fields{"input": v}).Fatalf("Failed to parse template")
}

var out bytes.Buffer
err = tmpl.Execute(&out, spec)
if err != nil {
log.WithFields(log.Fields{"error": err}).Fatal("failed to render template")
log.WithFields(log.Fields{"error": err}).Fatal("Failed to render template")
}
args[i] = out.String()
}

log.Infof("executing commands: %v", args)
rc, err := runCmds(args)
log.Infof("Executing commands: %v", args)
cmd := exec.Command(args[0], args[1:]...)
cmd.Env = entryEnvVars
rc, err := ee.RunCmds(cmd)
if rc != 0 || err != nil {
log.WithFields(log.Fields{
"return code": rc,
"error": err,
"command": args,
}).Error("failed")
}).Error("Failed")
os.Exit(rc)
}
log.Infof("Done")
}

// runCmds runs args as a command and ensures that each
func runCmds(args []string) (int, error) {
if len(args) <= 1 {
os.Exit(0)
}

ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(1)
go ee.RemoveZombies(ctx, &wg)

var rc int
cmd := exec.Command(args[0], args[1:]...)
err := ee.Run(cmd)
if err != nil {
rc = 1
}

cancel()
wg.Wait()
return rc, err
}

// preRun processes the spec file.
func preRun(c *cobra.Command, args []string) {
if specFile == "" {
log.Debug("no spec configuration found")
return
}

in, err := ioutil.ReadFile(specFile)
ns, err := rhjobspec.JobSpecFromFile(specFile)
if err != nil {
log.WithFields(log.Fields{"input file": specFile, "error": err}).Fatal(
"failed reading file")
}

if err = yaml.Unmarshal(in, &spec); err != nil {
log.WithFields(log.Fields{"input file": specFile, "error": err}).Fatal(
"failed unmarshalling yaml")
"Failed reading file")
}
spec = *ns
}
19 changes: 1 addition & 18 deletions entrypoint/exec/go_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"time"
)

// RemoveZombies cleans up any zombie processes
func RemoveZombies(ctx context.Context, wg *sync.WaitGroup) {
for {
var status syscall.WaitStatus
Expand Down Expand Up @@ -83,24 +84,6 @@ func Run(cmd *exec.Cmd) error {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

// Create a dedicated pidgroup
// used to forward signals to
// main process and all children
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

// Goroutine for signals forwarding
go func() {
for sig := range sigs {
// Ignore SIGCHLD signals since
// thez are only usefull for go-init
if sig != syscall.SIGCHLD {
// Forward signal to main process and all children
syscall.Kill(-cmd.Process.Pid, sig.(syscall.Signal))
os.Exit(1)
}
}
}()

// Start defined command
err := cmd.Start()
if err != nil {
Expand Down
29 changes: 29 additions & 0 deletions entrypoint/exec/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package exec

import (
"context"
"errors"
"os/exec"
"sync"
)

// RunCmds runs args as a command and ensures that each
func RunCmds(cmd *exec.Cmd) (int, error) {
if cmd == nil {
return 1, errors.New("No command to execute")
}
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(1)
go RemoveZombies(ctx, &wg)

var rc int
err := Run(cmd)
if err != nil {
rc = 1
}

cancel()
wg.Wait()
return rc, err
}
20 changes: 16 additions & 4 deletions entrypoint/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,21 @@ module github.com/coreos/entrypoint
go 1.14

require (
github.com/sirupsen/logrus v1.2.0
github.com/openshift/api v0.0.0-20201005153912-821561a7f2a2
github.com/openshift/client-go v3.9.0+incompatible
github.com/sirupsen/logrus v1.7.0
github.com/spf13/cobra v1.0.0
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae // indirect
gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v2 v2.2.8
k8s.io/apimachinery v0.19.0
k8s.io/client-go v0.0.0-00010101000000-000000000000
k8s.io/klog v1.0.0 // indirect
k8s.io/utils v0.0.0-20201005171033-6301aaf42dc7 // indirect

)

replace (
github.com/googleapis/gnostic => github.com/googleapis/gnostic v0.4.0
k8s.io/api => k8s.io/api v0.17.0
k8s.io/apimachinery => k8s.io/apimachinery v0.17.0
k8s.io/client-go => k8s.io/client-go v0.17.0
)
Loading