diff --git a/Dockerfile b/Dockerfile index fcae204..a3ca32d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,4 +3,4 @@ FROM quay.io/centos/centos:stream8@sha256:ce6ec049788dd34c9fd99cf6c319a1cc695799 COPY tests/test_script.sh / RUN dnf install net-tools -y -ENTRYPOINT [ "bash", "test_script.sh" ] \ No newline at end of file +ENTRYPOINT [ "bash", "test_script.sh" ] diff --git a/config.go b/config.go index 1ef02d4..e980e45 100644 --- a/config.go +++ b/config.go @@ -34,10 +34,6 @@ type Podman struct { Path string `json:"path"` // Constant prefix prepended to the randomized container name string. ContainerNamePrefix string `json:"containerNamePrefix"` - CgroupNs string `json:"cgroupNs"` - NetworkMode string `json:"networkMode"` - ImageArchitecture string `json:"imageArchitecture"` - ImageOS string `json:"imageOS"` // The initial integer that is the starting point for a // Random Number Generator's algorithm. RngSeed int64 `json:"rngSeed"` @@ -48,6 +44,7 @@ type Deployment struct { ContainerConfig *container.Config `json:"container"` HostConfig *container.HostConfig `json:"host"` ImagePullPolicy ImagePullPolicy `json:"imagePullPolicy"` + ImagePlatform *string `json:"imagePlatform"` } // Timeouts drive the timeouts for various interactions in relation to Docker. diff --git a/connector.go b/connector.go index d09dc28..f62f9b1 100644 --- a/connector.go +++ b/connector.go @@ -46,8 +46,8 @@ func (c *Connector) Deploy(ctx context.Context, image string) (deployer.Plugin, SetContainerName(containerName). SetEnv(containerConfig.Env). SetVolumes(hostConfig.Binds). - SetCgroupNs(c.config.Podman.CgroupNs). - SetNetworkMode(c.config.Podman.NetworkMode) + SetCgroupNs(string(hostConfig.CgroupnsMode)). + SetNetworkMode(string(hostConfig.NetworkMode)) stdin, stdout, err := c.podmanCliWrapper.Deploy(image, commandArgs, []string{"--atp"}) @@ -74,19 +74,15 @@ func (c *Connector) pullImage(_ context.Context, image string) error { } if c.config.Deployment.ImagePullPolicy == ImagePullPolicyIfNotPresent { imageExists, err := c.podmanCliWrapper.ImageExists(image) - podmanPlatform := c.config.Podman.ImageOS + "/" + c.config.Podman.ImageArchitecture if err != nil { return err } - if *imageExists { c.logger.Debugf("%s: image already present skipping pull", image) return nil } - // TODO:fix default values in configuration - - c.logger.Debugf("Pulling image: %s", image) - if err := c.podmanCliWrapper.PullImage(image, &podmanPlatform); err != nil { + c.logger.Debugf("Pulling image '%s'", image) + if err := c.podmanCliWrapper.PullImage(image, c.config.Deployment.ImagePlatform); err != nil { return err } } diff --git a/connector_test.go b/connector_test.go index d89a143..8cd058a 100644 --- a/connector_test.go +++ b/connector_test.go @@ -8,6 +8,7 @@ import ( "io" "os" "os/exec" + "runtime" "strings" "sync" "testing" @@ -29,13 +30,17 @@ func getConnector(t *testing.T, configJSON string) (deployer.Connector, *Config) assert.NoError(t, err) connector, err := factory.Create(unserializedConfig, log.NewTestLogger(t)) assert.NoError(t, err) + unserializedConfig.Podman.Path, err = binaryCheck(unserializedConfig.Podman.Path) + if err != nil { + t.Fatalf("Error checking Podman path (%s)", err) + } return connector, unserializedConfig } var inOutConfig = ` { "podman":{ - "path":"/usr/bin/podman" + "path":"podman" } } ` @@ -48,7 +53,7 @@ func TestSimpleInOut(t *testing.T) { connector, _ := getConnector(t, inOutConfig) plugin, err := connector.Deploy( context.Background(), - "quay.io/tsebastiani/arcaflow-engine-deployer-podman-test:latest") + "quay.io/arcalot/podman-deployer-test-helper:0.1.0") assert.NoError(t, err) var containerInput = []byte("ping abc\n") @@ -74,7 +79,6 @@ var envConfig = ` { "deployment":{ "container":{ - "NetworkDisabled":true, "Env":[ "DEPLOYER_PODMAN_TEST_1=TEST1", "DEPLOYER_PODMAN_TEST_2=TEST2" @@ -82,7 +86,7 @@ var envConfig = ` } }, "podman":{ - "path":"/usr/bin/podman" + "path":"podman" } } ` @@ -90,14 +94,14 @@ var envConfig = ` func TestEnv(t *testing.T) { envVars := "DEPLOYER_PODMAN_TEST_1=TEST1\nDEPLOYER_PODMAN_TEST_2=TEST2" connector, _ := getConnector(t, envConfig) - container, err := connector.Deploy(context.Background(), "quay.io/tsebastiani/arcaflow-engine-deployer-podman-test:latest") + container, err := connector.Deploy(context.Background(), "quay.io/arcalot/podman-deployer-test-helper:0.1.0") assert.NoError(t, err) var containerInput = []byte("env\n") assert.NoErrorR[int](t)(container.Write(containerInput)) readBuffer := readOutputUntil(t, container, envVars) - assert.Equals(t, len(readBuffer) > 0, true) + assert.GreaterThan(t, len(readBuffer), 0) t.Cleanup(func() { assert.NoError(t, container.Close()) @@ -114,7 +118,7 @@ var volumeConfig = ` } }, "podman":{ - "path":"/usr/bin/podman" + "path":"podman" } } ` @@ -131,20 +135,21 @@ func TestSimpleVolume(t *testing.T) { cmd := exec.Command("chcon", "-Rt", "svirt_sandbox_file_t", fmt.Sprintf("%s/tests/volume", cwd)) //nolint:gosec err = cmd.Run() if err != nil { - logger.Warningf("failed to set SELinux permissions on folder, chcon error: %s, this may cause test failure, let's see...", err.Error()) + logger.Warningf("failed to set SELinux permissions on folder, chcon error: %s, this may cause test failure if SELinux is enabled.", err.Error()) } container, err := connector.Deploy( context.Background(), - "quay.io/tsebastiani/arcaflow-engine-deployer-podman-test:latest") + "quay.io/arcalot/podman-deployer-test-helper:0.1.0") assert.NoError(t, err) var containerInput = []byte("volume\n") _, err = container.Write(containerInput) assert.NoError(t, err) - + // Note: If it ends up with length zero buffer, restarting the VM may help: + // https://stackoverflow.com/questions/71977532/podman-mount-host-volume-return-error-statfs-no-such-file-or-directory-in-ma readBuffer := readOutputUntil(t, container, string(fileContent)) - assert.Equals(t, len(readBuffer) > 0, true) + assert.GreaterThan(t, len(readBuffer), 0) t.Cleanup(func() { assert.NoError(t, container.Close()) @@ -154,7 +159,7 @@ func TestSimpleVolume(t *testing.T) { var nameTemplate = ` { "podman":{ - "path":"/usr/bin/podman", + "path":"podman", "containerNamePrefix":"%s" } } @@ -171,12 +176,12 @@ func TestContainerName(t *testing.T) { container1, err := connector1.Deploy( ctx, - "quay.io/tsebastiani/arcaflow-engine-deployer-podman-test:latest") + "quay.io/arcalot/podman-deployer-test-helper:0.1.0") assert.NoError(t, err) container2, err := connector2.Deploy( ctx, - "quay.io/tsebastiani/arcaflow-engine-deployer-podman-test:latest") + "quay.io/arcalot/podman-deployer-test-helper:0.1.0") assert.NoError(t, err) var wg sync.WaitGroup @@ -207,9 +212,13 @@ func TestContainerName(t *testing.T) { var cgroupTemplate = ` { "podman":{ - "path":"/usr/bin/podman", - "containerNamePrefix":"%s", - "cgroupNs":"%s" + "path":"podman", + "containerNamePrefix":"%s" + }, + "deployment":{ + "host":{ + "CgroupnsMode":"%s" + } } } ` @@ -226,7 +235,7 @@ func TestCgroupNsByContainerName(t *testing.T) { connector1, config := getConnector(t, configtemplate1) container1, err := connector1.Deploy( context.Background(), - "quay.io/tsebastiani/arcaflow-engine-deployer-podman-test:latest") + "quay.io/arcalot/podman-deployer-test-helper:0.1.0") assert.NoError(t, err) containerNamePrefix2 := "test_2" @@ -235,7 +244,7 @@ func TestCgroupNsByContainerName(t *testing.T) { connector2, _ := getConnector(t, configtemplate2) container2, err := connector2.Deploy( context.Background(), - "quay.io/tsebastiani/arcaflow-engine-deployer-podman-test:latest") + "quay.io/arcalot/podman-deployer-test-helper:0.1.0") assert.NoError(t, err) var wg sync.WaitGroup @@ -271,7 +280,8 @@ func TestPrivateCgroupNs(t *testing.T) { logger := log.NewTestLogger(t) var wg sync.WaitGroup - userCgroupNs := tests.GetCommmandCgroupNs(logger, "/usr/bin/sleep", []string{"3"}) + // Assume sleep is in the path. Because it's not in the same location for every user. + userCgroupNs := tests.GetCommmandCgroupNs(logger, "sleep", []string{"3"}) assert.NotNil(t, userCgroupNs) logger.Debugf("Detected cgroup namespace for user: %s", userCgroupNs) @@ -281,7 +291,7 @@ func TestPrivateCgroupNs(t *testing.T) { connector, config := getConnector(t, configtemplate) container, err := connector.Deploy( context.Background(), - "quay.io/tsebastiani/arcaflow-engine-deployer-podman-test:latest") + "quay.io/arcalot/podman-deployer-test-helper:0.1.0") assert.NoError(t, err) wg.Add(1) @@ -304,10 +314,15 @@ func TestPrivateCgroupNs(t *testing.T) { } func TestHostCgroupNs(t *testing.T) { + if runtime.GOOS != "linux" { + t.Skipf("Not running on Linux. Skipping cgroup test.") + return + } logger := log.NewTestLogger(t) var wg sync.WaitGroup - userCgroupNs := tests.GetCommmandCgroupNs(logger, "/usr/bin/sleep", []string{"3"}) + // Assume sleep is in the path. Because it's not in the same location for every user. + userCgroupNs := tests.GetCommmandCgroupNs(logger, "sleep", []string{"3"}) assert.NotNil(t, userCgroupNs) logger.Debugf("Detected cgroup namespace for user: %s", userCgroupNs) @@ -317,7 +332,7 @@ func TestHostCgroupNs(t *testing.T) { connector, config := getConnector(t, configtemplate) container, err := connector.Deploy( context.Background(), - "quay.io/tsebastiani/arcaflow-engine-deployer-podman-test:latest") + "quay.io/arcalot/podman-deployer-test-helper:0.1.0") assert.NoError(t, err) wg.Add(1) @@ -334,7 +349,7 @@ func TestHostCgroupNs(t *testing.T) { assert.NotNil(t, podmanCgroupNs) wg.Wait() - assert.Equals(t, userCgroupNs == podmanCgroupNs, true) + assert.Equals(t, userCgroupNs, podmanCgroupNs) t.Cleanup(func() { assert.NoError(t, container.Close()) @@ -350,7 +365,7 @@ func TestCgroupNsByNamespacePath(t *testing.T) { // The first container will run with a private namespace that will be created at startup configtemplate1 := fmt.Sprintf(cgroupTemplate, containerNamePrefix1, "private") connector1, config := getConnector(t, configtemplate1) - container1, err := connector1.Deploy(context.Background(), "quay.io/tsebastiani/arcaflow-engine-deployer-podman-test:latest") + container1, err := connector1.Deploy(context.Background(), "quay.io/arcalot/podman-deployer-test-helper:0.1.0") assert.NoError(t, err) var wg sync.WaitGroup @@ -374,7 +389,7 @@ func TestCgroupNsByNamespacePath(t *testing.T) { container2, err := connector2.Deploy( context.Background(), - "quay.io/tsebastiani/arcaflow-engine-deployer-podman-test:latest") + "quay.io/arcalot/podman-deployer-test-helper:0.1.0") assert.NoError(t, err) wg.Add(1) @@ -400,8 +415,12 @@ var networkTemplate = ` { "podman":{ "containerNamePrefix":"%s", - "path":"/usr/bin/podman", - "networkMode":"%s" + "path":"podman" + }, + "deployment":{ + "host":{ + "NetworkMode":"%s" + } } } ` @@ -414,7 +433,7 @@ func TestNetworkHost(t *testing.T) { connector, _ := getConnector(t, configtemplate) plugin, err := connector.Deploy( context.Background(), - "quay.io/tsebastiani/arcaflow-engine-deployer-podman-test:latest") + "quay.io/arcalot/podman-deployer-test-helper:0.1.0") assert.NoError(t, err) var containerInput = []byte("network host\n") @@ -475,7 +494,7 @@ func TestClose(t *testing.T) { container, err := connector.Deploy( context.Background(), - "quay.io/tsebastiani/arcaflow-engine-deployer-podman-test:latest") + "quay.io/arcalot/podman-deployer-test-helper:0.1.0") assert.NoError(t, err) var wg sync.WaitGroup @@ -532,7 +551,7 @@ func testNetworking(t *testing.T, podmanNetworking string, containerTest string, connector, _ := getConnector(t, configtemplate) plugin, err := connector.Deploy( context.Background(), - "quay.io/tsebastiani/arcaflow-engine-deployer-podman-test:latest") + "quay.io/arcalot/podman-deployer-test-helper:0.1.0") assert.NoError(t, err) var containerInput = []byte(containerTest) diff --git a/go.mod b/go.mod index 02d52f0..7ba4ec7 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.18 require ( github.com/docker/docker v24.0.7+incompatible github.com/docker/go-connections v0.4.0 - github.com/joho/godotenv v1.5.1 go.arcalot.io/assert v1.6.0 go.arcalot.io/lang v1.0.0 go.flow.arcalot.io/deployer v0.4.0 diff --git a/schema.go b/schema.go index 741af13..94c1240 100644 --- a/schema.go +++ b/schema.go @@ -10,6 +10,8 @@ import ( "go.flow.arcalot.io/pluginsdk/schema" ) +const notImplemented = "Not implemented yet in Podman deployer" + // Schema describes the deployment options of the Docker deployment mechanism. var Schema = schema.NewTypedScopeSchema[*Config]( schema.NewStructMappedObjectSchema[*Config]( @@ -84,46 +86,6 @@ var Schema = schema.NewTypedScopeSchema[*Config]( nil, nil, ), - "cgroupNs": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, regexp.MustCompile(`^host|ns:/proc/\d+/ns/cgroup|container:.+|private$`)), - schema.NewDisplayValue(schema.PointerTo("CGroup namespace"), schema.PointerTo("Provides the Cgroup Namespace settings for the container"), nil), - false, - nil, - nil, - nil, - nil, - nil, - ), - "networkMode": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, regexp.MustCompile("^bridge:.*|host|none$")), - schema.NewDisplayValue(schema.PointerTo("Network Mode"), schema.PointerTo("Provides network settings for the container"), nil), - false, - nil, - nil, - nil, - nil, - nil, - ), - "imageOS": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, regexp.MustCompile("^.*$")), - schema.NewDisplayValue(schema.PointerTo("Podman Image OS"), schema.PointerTo("Provides Podman Image Operating System"), nil), - false, - nil, - nil, - nil, - nil, - nil, - ), - "imageArchitecture": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, regexp.MustCompile("^.*$")), - schema.NewDisplayValue(schema.PointerTo("Podman image Architecture"), schema.PointerTo("Provides Podman Image Architecture"), nil), - false, - nil, - nil, - nil, - nil, - nil, - ), }, ), schema.NewStructMappedObjectSchema[Deployment]( @@ -163,6 +125,16 @@ var Schema = schema.NewTypedScopeSchema[*Config]( schema.PointerTo(util.JSONEncode(string(ImagePullPolicyIfNotPresent))), nil, ), + "imagePlatform": schema.NewPropertySchema( + schema.NewStringSchema(nil, nil, regexp.MustCompile("^.+/.+$")), + schema.NewDisplayValue(schema.PointerTo("Podman Image OS"), schema.PointerTo("Provides Podman Image Operating System and architecture"), nil), + false, + nil, + nil, + nil, + nil, + []string{"linux/amd64", "linux/arm64"}, + ), }, ), schema.NewStructMappedObjectSchema[*container.Config]( @@ -177,7 +149,7 @@ var Schema = schema.NewTypedScopeSchema[*Config]( nil, nil, nil, - ), + ).Disable(notImplemented), "Domainname": schema.NewPropertySchema( schema.NewStringSchema(schema.IntPointer(1), schema.IntPointer(255), regexp.MustCompile("^[a-zA-Z0-9-_.]+$")), schema.NewDisplayValue(schema.PointerTo("Domain name"), schema.PointerTo("Domain name for the plugin container."), nil), @@ -187,7 +159,7 @@ var Schema = schema.NewTypedScopeSchema[*Config]( nil, nil, nil, - ), + ).Disable(notImplemented), "User": schema.NewPropertySchema( schema.NewStringSchema(schema.IntPointer(1), schema.IntPointer(255), regexp.MustCompile("^[a-z_][a-z0-9_-]*[$]?(:[a-z_][a-z0-9_-]*[$]?)$")), schema.NewDisplayValue(schema.PointerTo("Username"), schema.PointerTo("User that will run the command inside the container. Optionally, a group can be specified in the user:group format."), nil), @@ -197,7 +169,7 @@ var Schema = schema.NewTypedScopeSchema[*Config]( nil, nil, nil, - ), + ).Disable(notImplemented), "Env": schema.NewPropertySchema( schema.NewListSchema(schema.NewStringSchema(schema.IntPointer(1), schema.IntPointer(32760), regexp.MustCompile("^.+=.+$")), nil, nil), schema.NewDisplayValue(schema.PointerTo("Environment variables"), schema.PointerTo("Environment variables to set on the plugin container."), nil), @@ -217,7 +189,7 @@ var Schema = schema.NewTypedScopeSchema[*Config]( nil, nil, nil, - ), + ).Disable(notImplemented), "MacAddress": schema.NewPropertySchema( schema.NewStringSchema(nil, nil, regexp.MustCompile("^[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}$")), schema.NewDisplayValue(schema.PointerTo("MAC address"), schema.PointerTo("Media Access Control address for the container."), nil), @@ -227,7 +199,7 @@ var Schema = schema.NewTypedScopeSchema[*Config]( nil, nil, nil, - ), + ).Disable(notImplemented), }, ), schema.NewStructMappedObjectSchema[*container.HostConfig]( @@ -244,7 +216,7 @@ var Schema = schema.NewTypedScopeSchema[*Config]( nil, ), "NetworkMode": schema.NewPropertySchema( - schema.NewStringSchema(nil, nil, regexp.MustCompile("^(none|bridge|host|container:[a-zA-Z0-9][a-zA-Z0-9_.-]+|[a-zA-Z0-9][a-zA-Z0-9_.-]+)$")), + schema.NewStringSchema(nil, nil, regexp.MustCompile("^(none|private|bridge(:.+)?|host|container:[a-zA-Z0-9][a-zA-Z0-9_.-]+|[a-zA-Z0-9][a-zA-Z0-9_.-]+|pasta:.+|slirp4netns:.+|ns:.+)$")), schema.NewDisplayValue(schema.PointerTo("Network mode"), schema.PointerTo("Specifies either the network mode, the container network to attach to, or a name of a Docker network to use."), nil), false, nil, @@ -277,7 +249,7 @@ var Schema = schema.NewTypedScopeSchema[*Config]( nil, nil, nil, - ), + ).Disable(notImplemented), "CapAdd": schema.NewPropertySchema( schema.NewListSchema(schema.NewStringSchema(nil, nil, nil), nil, nil), schema.NewDisplayValue(schema.PointerTo("Add capabilities"), schema.PointerTo("Add capabilities to the container."), nil), @@ -287,7 +259,7 @@ var Schema = schema.NewTypedScopeSchema[*Config]( nil, nil, nil, - ), + ).Disable(notImplemented), "CapDrop": schema.NewPropertySchema( schema.NewListSchema(schema.NewStringSchema(nil, nil, nil), nil, nil), schema.NewDisplayValue(schema.PointerTo("Drop capabilities"), schema.PointerTo("Drop capabilities from the container."), nil), @@ -297,13 +269,9 @@ var Schema = schema.NewTypedScopeSchema[*Config]( nil, nil, nil, - ), + ).Disable(notImplemented), "CgroupnsMode": schema.NewPropertySchema( - schema.NewStringEnumSchema(map[string]*schema.DisplayValue{ - "private": {NameValue: schema.PointerTo("Private")}, - "host": {NameValue: schema.PointerTo("Host")}, - "": {NameValue: schema.PointerTo("Empty")}, - }), + schema.NewStringSchema(nil, nil, regexp.MustCompile("host|private|ns:.+|container:.+")), schema.NewDisplayValue(schema.PointerTo("CGroup namespace mode"), schema.PointerTo("CGroup namespace mode to use for the container."), nil), false, nil, @@ -321,7 +289,7 @@ var Schema = schema.NewTypedScopeSchema[*Config]( nil, nil, nil, - ), + ).Disable(notImplemented), "DnsOptions": schema.NewPropertySchema( schema.NewListSchema(schema.NewStringSchema(nil, nil, nil), nil, nil), schema.NewDisplayValue(schema.PointerTo("DNS options"), schema.PointerTo("DNS options to look for."), nil), @@ -331,7 +299,7 @@ var Schema = schema.NewTypedScopeSchema[*Config]( nil, nil, nil, - ), + ).Disable(notImplemented), "DnsSearch": schema.NewPropertySchema( schema.NewListSchema(schema.NewStringSchema(nil, nil, nil), nil, nil), schema.NewDisplayValue(schema.PointerTo("DNS search"), schema.PointerTo("DNS search domain."), nil), @@ -341,7 +309,7 @@ var Schema = schema.NewTypedScopeSchema[*Config]( nil, nil, nil, - ), + ).Disable(notImplemented), "ExtraHosts": schema.NewPropertySchema( schema.NewListSchema(schema.NewStringSchema(nil, nil, nil), nil, nil), schema.NewDisplayValue(schema.PointerTo("Extra hosts"), schema.PointerTo("Extra hosts entries to add"), nil), @@ -351,7 +319,7 @@ var Schema = schema.NewTypedScopeSchema[*Config]( nil, nil, nil, - ), + ).Disable(notImplemented), }, ), schema.NewStructMappedObjectSchema[*nat.PortBinding]( @@ -366,7 +334,7 @@ var Schema = schema.NewTypedScopeSchema[*Config]( nil, nil, nil, - ), + ).Disable(notImplemented), "HostPort": schema.NewPropertySchema( schema.NewStringSchema(nil, nil, regexp.MustCompile("^0-9+$")), schema.NewDisplayValue(schema.PointerTo("Host port"), nil, nil), @@ -376,7 +344,7 @@ var Schema = schema.NewTypedScopeSchema[*Config]( nil, nil, nil, - ), + ).Disable(notImplemented), }, ), ) diff --git a/tests/common.go b/tests/common.go index 899a251..fba4cac 100644 --- a/tests/common.go +++ b/tests/common.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/joho/godotenv" log "go.arcalot.io/log/v2" "os" "os/exec" @@ -27,10 +26,11 @@ type BasicInspection struct { } func GetPodmanPath() string { - if err := godotenv.Load("../../tests/env/test.env"); err != nil { - panic(err) + envPath := os.Getenv("PODMAN_PATH") + if len(envPath) > 0 { + return envPath } - return os.Getenv("PODMAN_PATH") + return "podman" } func RemoveImage(logger log.Logger, image string) { diff --git a/tests/env/test.env b/tests/env/test.env deleted file mode 100644 index 7ccf0b8..0000000 --- a/tests/env/test.env +++ /dev/null @@ -1 +0,0 @@ -PODMAN_PATH=/usr/bin/podman