github.com/thediveo/morbyd@v0.11.1/run/options_test.go (about) 1 // Copyright 2024 Harald Albrecht. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package run 16 17 import ( 18 "bytes" 19 "net" 20 "os" 21 "strings" 22 23 "github.com/docker/docker/api/types/container" 24 "github.com/docker/docker/api/types/mount" 25 "github.com/docker/docker/api/types/network" 26 "github.com/docker/go-connections/nat" 27 28 . "github.com/onsi/ginkgo/v2" 29 . "github.com/onsi/gomega" 30 "github.com/onsi/gomega/gstruct" 31 . "github.com/thediveo/success" 32 ) 33 34 func opts(opts ...Opt) Options { 35 GinkgoHelper() 36 o := Options{} 37 for _, opt := range opts { 38 Expect(opt(&o)).To(Succeed()) 39 } 40 return o 41 } 42 43 var _ = Describe("run (container) options", func() { 44 45 It("processes input/output options", func() { 46 var ( 47 stdin strings.Reader 48 stdout bytes.Buffer 49 stderr bytes.Buffer 50 ) 51 52 Expect(opts()).To(And( 53 HaveField("In", BeNil()), 54 HaveField("Out", BeNil()), 55 HaveField("Err", BeNil()), 56 )) 57 58 Expect(opts(WithCombinedOutput(&stdout))).To(And( 59 HaveField("Conf.Tty", BeFalse()), 60 HaveField("In", BeNil()), 61 HaveField("Out", BeIdenticalTo(&stdout)), 62 HaveField("Err", BeIdenticalTo(&stdout)), 63 )) 64 65 Expect(opts(WithDemuxedOutput(&stdout, &stderr))).To(And( 66 HaveField("Conf.Tty", BeFalse()), 67 HaveField("In", BeNil()), 68 HaveField("Out", BeIdenticalTo(&stdout)), 69 HaveField("Err", BeIdenticalTo(&stderr)), 70 )) 71 72 Expect(opts(WithInput(&stdin))).To(And( 73 HaveField("Conf.Tty", BeFalse()), 74 HaveField("In", BeIdenticalTo(&stdin)), 75 HaveField("Out", BeNil()), 76 HaveField("Err", BeNil()), 77 )) 78 }) 79 80 It("processes run options", func() { 81 o := opts( 82 WithName("loosing_lattice"), 83 WithCommand("/bin/bash", "-c", "false"), 84 WithEnvVars("foo=bar", "baz="), 85 WithLabels("hellorld="), 86 ClearLabels(), 87 WithLabels("foo=bar", "baz="), 88 WithStopSignal("SIGDOOZE"), 89 WithStopTimeout(42), 90 WithTTY(), 91 WithAutoRemove(), 92 WithPrivileged(), 93 WithCapAdd("CAP_SUCCESS"), 94 WithCapDropAll(), 95 WithCgroupnsMode("c-host"), 96 WithIPCMode("i-host"), 97 WithNetworkMode("n-host"), 98 WithPIDMode("p-host"), 99 WithTmpfs("/tmp"), 100 WithTmpfsOpts("/temp", "tmpfs-size=42"), 101 WithDevice("/dev/foo"), 102 WithDevice("/dev/foo:/dev/fool"), 103 WithDevice("/dev/foo:/dev/fool:r"), 104 WithReadOnlyRootfs(), 105 WithSecurityOpt("all=unconfined"), 106 WithNetwork("one"), 107 WithNetwork("two"), 108 WithConsoleSize(666, 42), 109 WithHostname("foohost"), 110 WithRestartPolicy("always", 666), 111 WithAllPortsPublished(), 112 WithPublishedPort("[fe80::dead:beef]:2345:1234"), 113 WithPublishedPort("127.0.0.1:1234"), 114 WithPublishedPort("127.0.0.2:12345/udp"), 115 WithCustomInit(), 116 ) 117 118 Expect(o.Name).To(Equal("loosing_lattice")) 119 Expect(o.Conf.Cmd).To(ConsistOf("/bin/bash", "-c", "false")) 120 Expect(o.Conf.Env).To(ConsistOf("foo=bar", "baz=")) 121 Expect(o.Conf.Labels).NotTo(HaveKey("hellorld")) 122 Expect(o.Conf.Labels).To(And( 123 HaveKeyWithValue("foo", "bar"), 124 HaveKeyWithValue("baz", ""), 125 )) 126 Expect(o.Conf.StopSignal).To(Equal("SIGDOOZE")) 127 Expect(*o.Conf.StopTimeout).To(Equal(42)) 128 Expect(o.Conf.Tty).To(BeTrue()) 129 Expect(o.Host.Privileged).To(BeTrue()) 130 Expect(o.Host.CapAdd).To(ConsistOf("CAP_SUCCESS")) 131 Expect(o.Host.CapDrop).To(ConsistOf("ALL")) 132 Expect(o.Host.CgroupnsMode).To(Equal(container.CgroupnsMode("c-host"))) 133 Expect(o.Host.IpcMode).To(Equal(container.IpcMode("i-host"))) 134 Expect(o.Host.NetworkMode).To(Equal(container.NetworkMode("n-host"))) 135 Expect(o.Host.PidMode).To(Equal(container.PidMode("p-host"))) 136 Expect(o.Host.Tmpfs).To(HaveKeyWithValue("/tmp", "")) 137 Expect(o.Host.Tmpfs).To(HaveKeyWithValue("/temp", "tmpfs-size=42")) 138 Expect(o.Host.Devices).To(ConsistOf( 139 container.DeviceMapping{PathOnHost: "/dev/foo", PathInContainer: "/dev/foo", CgroupPermissions: "rwm"}, 140 container.DeviceMapping{PathOnHost: "/dev/foo", PathInContainer: "/dev/fool", CgroupPermissions: "rwm"}, 141 container.DeviceMapping{PathOnHost: "/dev/foo", PathInContainer: "/dev/fool", CgroupPermissions: "r"}, 142 )) 143 Expect(o.Host.ReadonlyRootfs).To(BeTrue()) 144 Expect(o.Host.SecurityOpt).To(ConsistOf("all=unconfined")) 145 Expect(o.Net.EndpointsConfig).To(And( 146 HaveKeyWithValue("one", &network.EndpointSettings{NetworkID: "one"}), 147 HaveKeyWithValue("two", &network.EndpointSettings{NetworkID: "two"}), 148 )) 149 Expect(o.Host.ConsoleSize).To(Equal([2]uint{42, 666})) 150 Expect(o.Conf.Hostname).To(Equal("foohost")) 151 Expect(o.Host.RestartPolicy).To(Equal(container.RestartPolicy{ 152 Name: "always", 153 MaximumRetryCount: 666, 154 })) 155 156 Expect(o.Host.PublishAllPorts).To(BeTrue()) 157 Expect(o.Conf.ExposedPorts).To(HaveLen(2)) 158 Expect(o.Conf.ExposedPorts).To(HaveKey(nat.Port("1234/tcp"))) 159 Expect(o.Conf.ExposedPorts).To(HaveKey(nat.Port("12345/udp"))) 160 Expect(o.Host.PortBindings).To(HaveLen(2)) 161 Expect(o.Host.PortBindings).To(HaveKeyWithValue( 162 nat.Port("1234/tcp"), 163 ConsistOf( 164 nat.PortBinding{HostIP: "127.0.0.1", HostPort: "0"}, 165 nat.PortBinding{HostIP: "fe80::dead:beef", HostPort: "2345"}))) 166 Expect(o.Host.PortBindings).To(HaveKeyWithValue( 167 nat.Port("12345/udp"), 168 ConsistOf(nat.PortBinding{HostIP: "127.0.0.2", HostPort: "0"}))) 169 170 Expect(o.Host.Init).To(gstruct.PointTo(BeTrue())) 171 172 o = opts(WithLabel("foo=bar")) 173 Expect(o.Conf.Labels).To(HaveKeyWithValue("foo", "bar")) 174 175 o = Options{} 176 Expect(WithLabels("=")(&o)).NotTo(Succeed()) 177 178 o = opts(WithTmpfsOpts("/temp", "tmpfs-size=42")) 179 Expect(o.Host.Tmpfs).To(HaveKeyWithValue("/temp", "tmpfs-size=42")) 180 }) 181 182 It("rejects invalid published port mappings", func() { 183 var o Options 184 Expect(WithPublishedPort("abcd")(&o)).To(HaveOccurred()) 185 }) 186 187 It("rejects invalid volume specs", func() { 188 var o Options 189 Expect(WithVolume("rappel:zappel:humba:tätärä")(&o)).To( 190 MatchError("malformed WithVolume parameter \"rappel:zappel:humba:tätärä\", reason: invalid spec: rappel:zappel:humba:tätärä: too many colons")) 191 }) 192 193 It("rejects invalid mount specs", func() { 194 var o Options 195 Expect(WithMount("type=bind,source=/foo,target=/bar,private")(&o)).To( 196 MatchError("invalid WithMount parameter, reason: invalid field 'private' must be a key=value pair")) 197 }) 198 199 It("rejects invalid devices", func() { 200 var o Options 201 Expect(WithDevice(":::::")(&o)).Error().To( 202 MatchError(ContainSubstring("malformed WithDevice parameter"))) 203 Expect(WithDevice("::")(&o)).Error().To( 204 MatchError("WithDevice host path parameter must not be empty")) 205 }) 206 207 It("splits into volumes, binds, and mounts", func() { 208 o := opts( 209 WithVolume("/foo"), 210 WithVolume("/run:/run2:ro"), 211 WithVolume("/fool:/bar:z"), 212 WithVolume(".:/run"), 213 WithMount("type=volume,source=/foo,target=/bar,readonly"), 214 ) 215 216 Expect(o.Conf.Volumes).To(HaveLen(1)) 217 Expect(o.Conf.Volumes).To(HaveKey("/foo")) 218 219 Expect(o.Host.Binds).To(ConsistOf( 220 "/run:/run2:ro", 221 "/fool:/bar:z", 222 Successful(os.Getwd())+":/run", 223 )) 224 225 Expect(o.Host.Mounts).To(ConsistOf( 226 mount.Mount{ 227 Type: "volume", 228 Source: "/foo", 229 Target: "/bar", 230 ReadOnly: true, 231 }, 232 )) 233 }) 234 235 It("returns invalid volume string when converting to binds unmodified", Serial, func() { 236 Expect(bindVolumeToBind("")).To(BeEmpty()) 237 238 cwd := Successful(os.Getwd()) 239 defer os.Chdir(cwd) //nolint:golint,errcheck 240 tmpdir := Successful(os.MkdirTemp("", "on-my-way-out-*")) 241 defer os.RemoveAll(tmpdir) //nolint:golint,errcheck 242 Expect(os.Chdir(tmpdir)).To(Succeed()) 243 Expect(os.RemoveAll(tmpdir)).To(Succeed()) 244 Expect(bindVolumeToBind("./relative:/absolute")).To(Equal("./relative:/absolute")) 245 }) 246 247 It("rejects when given a network in invalid long form", func() { 248 var o Options 249 Expect(WithNetwork("foo=bar")(&o)).NotTo(Succeed()) 250 }) 251 252 DescribeTable("published port mapping syntax", 253 func(mapping string, expectedIP net.IP, expectedHostPort int, expectedCntrPort int, expectedL4Proto string, ok bool) { 254 ip, hp, cp, l4p, err := parsePortMapping(mapping) 255 if !ok { 256 Expect(err).To(HaveOccurred()) 257 Expect(ip).To(BeNil()) 258 Expect(hp).To(BeZero()) 259 Expect(cp).To(BeZero()) 260 Expect(l4p).To(BeEmpty()) 261 return 262 } 263 Expect(err).NotTo(HaveOccurred()) 264 Expect(ip).To(Equal(expectedIP)) 265 Expect(hp).To(Equal(uint16(expectedHostPort))) 266 Expect(cp).To(Equal(uint16(expectedCntrPort))) 267 Expect(l4p).To(Equal(expectedL4Proto)) 268 }, 269 270 Entry(nil, "", nil, 0, 0, "", false), 271 272 // Nope 273 Entry(nil, "0", nil, 0, 0, "", false), 274 Entry(nil, "123abc", nil, 0, 0, "", false), 275 Entry(nil, "2345:123abc", nil, 0, 0, "", false), 276 Entry(nil, "2345xyz:123abc", nil, 0, 0, "", false), 277 278 // Everything wrong with a potential IPv6 host address... 279 Entry(nil, "[1234:", nil, 0, 0, "", false), 280 Entry(nil, "[1234]", nil, 0, 0, "", false), 281 Entry(nil, "[1234]:", nil, 0, 0, "", false), 282 283 // Aisle of Plenty :D 284 Entry(nil, "[::1]:2345:1234:7890", nil, 0, 0, "", false), 285 Entry(nil, "127.0.0.1:2345:1234:7890", nil, 0, 0, "", false), 286 287 // Oddballs, odd, yet fine. 288 Entry(nil, "1234/tcp/udp", nil, 0, 1234, "tcp/udp", true), 289 290 // Good cases. 291 Entry(nil, "1234", nil, 0, 1234, "tcp", true), 292 Entry(nil, "1234/udp", nil, 0, 1234, "udp", true), 293 294 Entry(nil, "127.0.0.1:1234", net.ParseIP("127.0.0.1").To4(), 0, 1234, "tcp", true), 295 Entry(nil, "[::1]:1234", net.ParseIP("::1"), 0, 1234, "tcp", true), 296 297 Entry(nil, "2345:1234", nil, 2345, 1234, "tcp", true), 298 Entry(nil, "127.0.0.1:2345:1234", net.ParseIP("127.0.0.1").To4(), 2345, 1234, "tcp", true), 299 Entry(nil, "[::1]:2345:1234", net.ParseIP("::1"), 2345, 1234, "tcp", true), 300 ) 301 })