Merge "Implement airship container type"
This commit is contained in:
commit
9ab2ed96b7
2
go.mod
2
go.mod
@ -6,6 +6,8 @@ require (
|
||||
github.com/Azure/go-autorest/autorest v0.11.7 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.0
|
||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
||||
github.com/ahmetalpbalkan/dlog v0.0.0-20170105205344-4fb5f8204f26 // indirect
|
||||
github.com/ahmetb/dlog v0.0.0-20170105205344-4fb5f8204f26
|
||||
github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 // indirect
|
||||
github.com/containerd/containerd v1.4.1 // indirect
|
||||
github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce
|
||||
|
4
go.sum
4
go.sum
@ -66,6 +66,10 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||
github.com/ahmetalpbalkan/dlog v0.0.0-20170105205344-4fb5f8204f26 h1:pzStYMLAXM7CNQjS/Wn+zK9MUxDhSUNfVvnHsyQyjs0=
|
||||
github.com/ahmetalpbalkan/dlog v0.0.0-20170105205344-4fb5f8204f26/go.mod h1:ilK+u7u1HoqaDk0mjhh27QJB7PyWMreGffEvOCoEKiY=
|
||||
github.com/ahmetb/dlog v0.0.0-20170105205344-4fb5f8204f26 h1:3YVZUqkoev4mL+aCwVOSWV4M7pN+NURHL38Z2zq5JKA=
|
||||
github.com/ahmetb/dlog v0.0.0-20170105205344-4fb5f8204f26/go.mod h1:ymXt5bw5uSNu4jveerFxE0vNYxF8ncqbptntMaFMg3k=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
|
@ -15,18 +15,24 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
// TODO this small library needs to be moved to airshipctl and extended
|
||||
// with splitting streams into Stderr and Stdout
|
||||
"github.com/ahmetb/dlog"
|
||||
"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/runfn"
|
||||
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/errors"
|
||||
"opendev.org/airship/airshipctl/pkg/log"
|
||||
)
|
||||
|
||||
// ClientV1Alpha1 provides airship generic container API
|
||||
@ -73,7 +79,7 @@ func (c *clientV1Alpha1) Run() error {
|
||||
// set default runtime
|
||||
switch c.conf.Spec.Type {
|
||||
case v1alpha1.GenericContainerTypeAirship, "":
|
||||
return errors.ErrNotImplemented{What: "airship generic container type"}
|
||||
return c.runAirship()
|
||||
case v1alpha1.GenericContainerTypeKrm:
|
||||
return c.runKRM()
|
||||
default:
|
||||
@ -81,6 +87,104 @@ func (c *clientV1Alpha1) Run() error {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *clientV1Alpha1) runAirship() error {
|
||||
if c.conf.Spec.Airship.ContainerRuntime == "" {
|
||||
c.conf.Spec.Airship.ContainerRuntime = ContainerDriverDocker
|
||||
}
|
||||
|
||||
var cont Container
|
||||
if c.containerFunc == nil {
|
||||
c.containerFunc = NewContainer
|
||||
}
|
||||
|
||||
cont, err := c.containerFunc(
|
||||
context.Background(),
|
||||
c.conf.Spec.Airship.ContainerRuntime,
|
||||
c.conf.Spec.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// this will split the env vars into the ones to be exported and the ones that have values
|
||||
contEnv := runtimeutil.NewContainerEnvFromStringSlice(c.conf.Spec.EnvVars)
|
||||
|
||||
envs := []string{}
|
||||
for _, key := range contEnv.VarsToExport {
|
||||
envs = append(envs, strings.Join([]string{key, os.Getenv(key)}, "="))
|
||||
}
|
||||
|
||||
for key, value := range contEnv.EnvVars {
|
||||
envs = append(envs, strings.Join([]string{key, value}, "="))
|
||||
}
|
||||
|
||||
node, err := kyaml.Parse(c.conf.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decoratedInput := bytes.NewBuffer([]byte{})
|
||||
pipeline := &kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: c.input}},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{
|
||||
Writer: decoratedInput,
|
||||
KeepReaderAnnotations: true,
|
||||
WrappingKind: kio.ResourceListKind,
|
||||
WrappingAPIVersion: kio.ResourceListAPIVersion,
|
||||
FunctionConfig: node,
|
||||
}},
|
||||
}
|
||||
|
||||
err = pipeline.Execute()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Starting container with image: '%s', cmd: '%s'",
|
||||
c.conf.Spec.Image,
|
||||
c.conf.Spec.Airship.Cmd)
|
||||
err = cont.RunCommand(RunCommandOptions{
|
||||
Privileged: c.conf.Spec.Airship.Privileged,
|
||||
Cmd: c.conf.Spec.Airship.Cmd,
|
||||
Mounts: convertDockerMount(c.conf.Spec.StorageMounts),
|
||||
EnvVars: envs,
|
||||
Input: decoratedInput,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Waiting for container run to finish, image: '%s', cmd: '%s'",
|
||||
c.conf.Spec.Image,
|
||||
c.conf.Spec.Airship.Cmd)
|
||||
|
||||
err = cont.WaitUntilFinished()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rOut, err := cont.GetContainerLogs(GetLogOptions{Stdout: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rOut.Close()
|
||||
|
||||
rErr, err := cont.GetContainerLogs(GetLogOptions{Stderr: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rOut.Close()
|
||||
|
||||
parsedOut := dlog.NewReader(rOut)
|
||||
parsedErr := dlog.NewReader(rErr)
|
||||
|
||||
// write container stderr to airship log output
|
||||
_, err = io.Copy(log.Writer(), parsedErr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeSink(c.resultsDir, parsedOut, c.output)
|
||||
}
|
||||
|
||||
func (c *clientV1Alpha1) runKRM() error {
|
||||
mounts := convertKRMMount(c.conf.Spec.StorageMounts)
|
||||
fns := &runfn.RunFns{
|
||||
@ -120,6 +224,24 @@ func (c *clientV1Alpha1) runKRM() error {
|
||||
return fns.Execute()
|
||||
}
|
||||
|
||||
// writeSink output to directory on filesystem sink
|
||||
func writeSink(path string, rc io.Reader, out io.Writer) error {
|
||||
inputs := []kio.Reader{&kio.ByteReader{Reader: rc}}
|
||||
var outputs []kio.Writer
|
||||
switch {
|
||||
case out == nil && path != "":
|
||||
log.Debugf("writing container output to files in directory %s", path)
|
||||
outputs = []kio.Writer{&kio.LocalPackageWriter{PackagePath: path}}
|
||||
case out != nil:
|
||||
log.Debugf("writing container output to provided writer")
|
||||
outputs = []kio.Writer{&kio.ByteWriter{Writer: out}}
|
||||
default:
|
||||
log.Debugf("writing container output to stdout")
|
||||
outputs = []kio.Writer{&kio.ByteWriter{Writer: os.Stdout}}
|
||||
}
|
||||
return kio.Pipeline{Inputs: inputs, Outputs: outputs}.Execute()
|
||||
}
|
||||
|
||||
func convertKRMMount(airMounts []v1alpha1.StorageMount) (fnsMounts []runtimeutil.StorageMount) {
|
||||
for _, mount := range airMounts {
|
||||
fnsMounts = append(fnsMounts, runtimeutil.StorageMount{
|
||||
@ -131,3 +253,18 @@ func convertKRMMount(airMounts []v1alpha1.StorageMount) (fnsMounts []runtimeutil
|
||||
}
|
||||
return fnsMounts
|
||||
}
|
||||
|
||||
func convertDockerMount(airMounts []v1alpha1.StorageMount) (mounts []Mount) {
|
||||
for _, mount := range airMounts {
|
||||
mnt := Mount{
|
||||
Type: mount.MountType,
|
||||
Src: mount.Src,
|
||||
Dst: mount.DstPath,
|
||||
}
|
||||
if !mount.ReadWriteMode {
|
||||
mnt.ReadOnly = true
|
||||
}
|
||||
mounts = append(mounts, mnt)
|
||||
}
|
||||
return mounts
|
||||
}
|
||||
|
@ -16,10 +16,13 @@ package container
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@ -91,15 +94,117 @@ func TestGenericContainer(t *testing.T) {
|
||||
expectedErr: "no such file or directory",
|
||||
outputPath: "directory doesn't exist",
|
||||
},
|
||||
{
|
||||
name: "error output directory does not exist",
|
||||
outputPath: "doesn't exist",
|
||||
containerAPI: &v1alpha1.GenericContainer{
|
||||
Spec: v1alpha1.GenericContainerSpec{
|
||||
Type: v1alpha1.GenericContainerTypeAirship,
|
||||
Image: "some image",
|
||||
StorageMounts: []v1alpha1.StorageMount{
|
||||
{
|
||||
MountType: "bind",
|
||||
Src: "test",
|
||||
DstPath: "/mount",
|
||||
},
|
||||
},
|
||||
},
|
||||
Config: `kind: ConfigMap`,
|
||||
},
|
||||
expectedErr: "no such file or directory",
|
||||
execFunc: func(ctx context.Context, driver, url string) (Container, error) {
|
||||
return getDockerContainerMock(mockDockerClient{
|
||||
containerAttach: func() (types.HijackedResponse, error) {
|
||||
conn := types.HijackedResponse{
|
||||
Conn: mockConn{WData: make([]byte, len([]byte("foo: bar")))},
|
||||
}
|
||||
return conn, nil
|
||||
},
|
||||
imageList: func() ([]types.ImageSummary, error) {
|
||||
return []types.ImageSummary{{ID: "imgid"}}, nil
|
||||
},
|
||||
imageInspectWithRaw: func() (types.ImageInspect, []byte, error) {
|
||||
return types.ImageInspect{
|
||||
Config: &container.Config{
|
||||
Cmd: []string{"testCmd"},
|
||||
},
|
||||
}, nil, nil
|
||||
},
|
||||
}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "basic success airship container",
|
||||
containerAPI: &v1alpha1.GenericContainer{
|
||||
Spec: v1alpha1.GenericContainerSpec{
|
||||
Type: v1alpha1.GenericContainerTypeAirship,
|
||||
Image: "some image",
|
||||
StorageMounts: []v1alpha1.StorageMount{
|
||||
{
|
||||
MountType: "bind",
|
||||
Src: "test",
|
||||
DstPath: "/mount",
|
||||
},
|
||||
},
|
||||
},
|
||||
Config: `kind: ConfigMap`,
|
||||
},
|
||||
execFunc: func(ctx context.Context, driver, url string) (Container, error) {
|
||||
return getDockerContainerMock(mockDockerClient{
|
||||
containerAttach: func() (types.HijackedResponse, error) {
|
||||
conn := types.HijackedResponse{
|
||||
Conn: mockConn{WData: make([]byte, len([]byte("foo: bar")))},
|
||||
}
|
||||
return conn, nil
|
||||
},
|
||||
imageList: func() ([]types.ImageSummary, error) {
|
||||
return []types.ImageSummary{{ID: "imgid"}}, nil
|
||||
},
|
||||
imageInspectWithRaw: func() (types.ImageInspect, []byte, error) {
|
||||
return types.ImageInspect{
|
||||
Config: &container.Config{
|
||||
Cmd: []string{"testCmd"},
|
||||
},
|
||||
}, nil, nil
|
||||
},
|
||||
}), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "basic success airship success written to provided output Writer",
|
||||
containerAPI: &v1alpha1.GenericContainer{
|
||||
Spec: v1alpha1.GenericContainerSpec{
|
||||
Type: v1alpha1.GenericContainerTypeAirship,
|
||||
Type: v1alpha1.GenericContainerTypeAirship,
|
||||
Image: "some image",
|
||||
StorageMounts: []v1alpha1.StorageMount{
|
||||
{
|
||||
MountType: "bind",
|
||||
Src: "test",
|
||||
DstPath: "/mount",
|
||||
},
|
||||
},
|
||||
},
|
||||
Config: `kind: ConfigMap`,
|
||||
},
|
||||
output: ioutil.Discard,
|
||||
expectedErr: "airship generic container type",
|
||||
execFunc: func(ctx context.Context, driver, url string) (Container, error) {
|
||||
return getDockerContainerMock(mockDockerClient{
|
||||
containerAttach: func() (types.HijackedResponse, error) {
|
||||
conn := types.HijackedResponse{
|
||||
Conn: mockConn{WData: make([]byte, len([]byte("foo: bar")))},
|
||||
}
|
||||
return conn, nil
|
||||
},
|
||||
imageList: func() ([]types.ImageSummary, error) {
|
||||
return []types.ImageSummary{{ID: "imgid"}}, nil
|
||||
},
|
||||
imageInspectWithRaw: func() (types.ImageInspect, []byte, error) {
|
||||
return types.ImageInspect{
|
||||
Config: &container.Config{},
|
||||
}, nil, nil
|
||||
},
|
||||
}), nil
|
||||
},
|
||||
output: ioutil.Discard,
|
||||
},
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user