PageRenderTime 51ms CodeModel.GetById 2ms RepoModel.GetById 0ms app.codeStats 0ms

/manifest/manifest_test.go

https://gitlab.com/convox/rack
Go | 492 lines | 400 code | 90 blank | 2 comment | 80 complexity | b47fe26d500d85d8976dce0dd1608a21 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause, 0BSD, LGPL-3.0
  1. package manifest_test
  2. import (
  3. "fmt"
  4. "math/rand"
  5. "net"
  6. "os"
  7. "testing"
  8. "time"
  9. "gopkg.in/yaml.v2"
  10. "github.com/convox/rack/manifest"
  11. "github.com/stretchr/testify/assert"
  12. )
  13. // WARNING: make sure to use spaces for the yaml indentations
  14. func TestLoadVersion1(t *testing.T) {
  15. m, err := manifestFixture("v1")
  16. if assert.Nil(t, err) {
  17. assert.Equal(t, m.Version, "1")
  18. assert.Equal(t, len(m.Services), 1)
  19. if web := m.Services["web"]; assert.NotNil(t, web) {
  20. assert.Equal(t, web.Image, "test")
  21. }
  22. }
  23. }
  24. func TestLoadVersion2(t *testing.T) {
  25. m, err := manifestFixture("v2-number")
  26. if assert.Nil(t, err) {
  27. assert.Equal(t, m.Version, "2")
  28. assert.Equal(t, len(m.Services), 1)
  29. if web := m.Services["web"]; assert.NotNil(t, web) {
  30. assert.Equal(t, web.Image, "test")
  31. }
  32. }
  33. m, err = manifestFixture("v2-string")
  34. if assert.Nil(t, err) {
  35. assert.Equal(t, m.Version, "2")
  36. assert.Equal(t, len(m.Services), 1)
  37. if web := m.Services["web"]; assert.NotNil(t, web) {
  38. assert.Equal(t, web.Image, "test")
  39. }
  40. }
  41. }
  42. func TestLoadCommandString(t *testing.T) {
  43. m, err := manifestFixture("command-string")
  44. if assert.Nil(t, err) {
  45. if web := m.Services["web"]; assert.NotNil(t, web) {
  46. assert.Equal(t, web.Command.String, manifest.Command{String: "ls -la"}.String)
  47. }
  48. }
  49. }
  50. func TestLoadCommandArray(t *testing.T) {
  51. m, err := manifestFixture("command-array")
  52. if assert.Nil(t, err) {
  53. if web := m.Services["web"]; assert.NotNil(t, web) {
  54. assert.Equal(t, web.Command.Array, manifest.Command{Array: []string{"ls", "-la"}}.Array)
  55. }
  56. }
  57. }
  58. func TestLoadFullVersion1(t *testing.T) {
  59. m, err := manifestFixture("full-v1")
  60. if assert.Nil(t, err) {
  61. if web := m.Services["web"]; assert.NotNil(t, web) {
  62. assert.Equal(t, web.Build.Context, ".")
  63. assert.Equal(t, web.Command.String, manifest.Command{String: "bin/web"}.String)
  64. assert.Equal(t, web.Dockerfile, "Dockerfile.dev")
  65. assert.Equal(t, web.Entrypoint, "/sbin/init")
  66. assert.Equal(t, len(web.Environment), 2)
  67. assert.Equal(t, web.Environment["FOO"], "bar")
  68. assert.Equal(t, web.Environment["BAZ"], "")
  69. assert.Equal(t, len(web.Labels), 2)
  70. assert.Equal(t, web.Labels["convox.foo"], "bar")
  71. assert.Equal(t, web.Labels["convox.baz"], "4")
  72. assert.Equal(t, web.Privileged, true)
  73. if assert.Equal(t, len(web.Links), 1) {
  74. assert.Equal(t, web.Links[0], "database")
  75. }
  76. if assert.Equal(t, len(web.Ports), 2) {
  77. assert.True(t, web.Ports.External())
  78. assert.True(t, web.Ports[0].External())
  79. assert.Equal(t, web.Ports[0].Balancer, 80)
  80. assert.Equal(t, web.Ports[0].Container, 5000)
  81. assert.True(t, web.Ports[1].External())
  82. assert.Equal(t, web.Ports[1].Balancer, 443)
  83. assert.Equal(t, web.Ports[1].Container, 5001)
  84. }
  85. if assert.Equal(t, len(web.ExtraHosts), 2) {
  86. assert.Equal(t, web.ExtraHosts[0], "foo:10.10.10.10")
  87. assert.Equal(t, web.ExtraHosts[1], "bar:20.20.20.20")
  88. }
  89. if assert.Equal(t, len(web.Volumes), 1) {
  90. assert.Equal(t, web.Volumes[0], "/var/db")
  91. }
  92. }
  93. if db := m.Services["database"]; assert.NotNil(t, db) {
  94. assert.Equal(t, len(db.Environment), 2)
  95. assert.Equal(t, db.Environment["FOO"], "bar")
  96. assert.Equal(t, db.Environment["BAZ"], "qux")
  97. assert.Equal(t, db.Image, "convox/postgres")
  98. assert.Equal(t, len(db.Labels), 2)
  99. assert.Equal(t, db.Labels["convox.aaa"], "4")
  100. assert.Equal(t, db.Labels["convox.ccc"], "ddd")
  101. if assert.Equal(t, len(db.Ports), 1) {
  102. assert.False(t, db.Ports.External())
  103. assert.False(t, db.Ports[0].External())
  104. assert.Equal(t, db.Ports[0].Balancer, 5432)
  105. assert.Equal(t, db.Ports[0].Container, 5432)
  106. }
  107. }
  108. }
  109. }
  110. func TestLoadFullVersion2(t *testing.T) {
  111. m, err := manifestFixture("full-v2")
  112. if assert.Nil(t, err) {
  113. if web := m.Services["web"]; assert.NotNil(t, web) {
  114. assert.Equal(t, web.Build.Context, ".")
  115. assert.Equal(t, web.Command.String, manifest.Command{String: "bin/web"}.String)
  116. assert.Equal(t, web.Dockerfile, "Dockerfile.dev")
  117. assert.Equal(t, web.Entrypoint, "/sbin/init")
  118. assert.Equal(t, len(web.Environment), 2)
  119. assert.Equal(t, web.Environment["FOO"], "bar")
  120. assert.Equal(t, web.Environment["BAZ"], "")
  121. assert.Equal(t, len(web.Labels), 2)
  122. assert.Equal(t, web.Labels["convox.foo"], "bar")
  123. assert.Equal(t, web.Labels["convox.baz"], "4")
  124. assert.Equal(t, web.Privileged, true)
  125. if assert.Equal(t, len(web.Links), 1) {
  126. assert.Equal(t, web.Links[0], "database")
  127. }
  128. if assert.Equal(t, len(web.Ports), 2) {
  129. assert.True(t, web.Ports.External())
  130. assert.True(t, web.Ports[0].External())
  131. assert.Equal(t, web.Ports[0].Balancer, 80)
  132. assert.Equal(t, web.Ports[0].Container, 5000)
  133. assert.True(t, web.Ports[0].External())
  134. assert.Equal(t, web.Ports[1].Balancer, 443)
  135. assert.Equal(t, web.Ports[1].Container, 5001)
  136. }
  137. if assert.Equal(t, len(web.ExtraHosts), 2) {
  138. assert.Equal(t, web.ExtraHosts[0], "foo:10.10.10.10")
  139. assert.Equal(t, web.ExtraHosts[1], "bar:20.20.20.20")
  140. }
  141. if assert.Equal(t, len(web.Volumes), 1) {
  142. assert.Equal(t, web.Volumes[0], "/var/db")
  143. }
  144. }
  145. if db := m.Services["database"]; assert.NotNil(t, db) {
  146. assert.Equal(t, len(db.Environment), 2)
  147. assert.Equal(t, db.Environment["FOO"], "bar")
  148. assert.Equal(t, db.Environment["BAZ"], "qux")
  149. assert.Equal(t, db.Image, "convox/postgres")
  150. assert.Equal(t, len(db.Labels), 2)
  151. assert.Equal(t, db.Labels["convox.aaa"], "4")
  152. assert.Equal(t, db.Labels["convox.ccc"], "ddd")
  153. if assert.Equal(t, len(db.Ports), 1) {
  154. assert.False(t, db.Ports.External())
  155. assert.False(t, db.Ports[0].External())
  156. assert.Equal(t, db.Ports[0].Balancer, 5432)
  157. assert.Equal(t, db.Ports[0].Container, 5432)
  158. }
  159. }
  160. }
  161. }
  162. func TestLoadGarbage(t *testing.T) {
  163. m, err := manifest.Load([]byte("\t\003//783bfkl1f"))
  164. if assert.Nil(t, m) && assert.NotNil(t, err) {
  165. assert.Equal(t, err.Error(), "could not parse manifest: yaml: control characters are not allowed")
  166. }
  167. }
  168. func TestLoadEnvVar(t *testing.T) {
  169. rando1 := randomString(30)
  170. rando2 := randomString(30)
  171. rando3 := randomString(30)
  172. err := os.Setenv("KNOWN_VAR1", rando1)
  173. if err != nil {
  174. t.Error(err)
  175. return
  176. }
  177. err = os.Setenv("KNOWN_VAR2", rando2)
  178. if err != nil {
  179. t.Error(err)
  180. return
  181. }
  182. err = os.Setenv("KNOWN_VAR3", rando3)
  183. if err != nil {
  184. t.Error(err)
  185. return
  186. }
  187. m, err := manifestFixture("interpolate-env-var")
  188. if assert.Nil(t, err) {
  189. assert.Equal(t, m.Services["web"].Image, rando1)
  190. assert.Equal(t, m.Services["web"].Entrypoint, fmt.Sprintf("%s/%s/%s", rando2, rando2, rando3))
  191. assert.Equal(t, m.Services["web"].Dockerfile, "$REMAIN")
  192. assert.Equal(t, m.Services["web"].Volumes[0], "${broken")
  193. }
  194. }
  195. func TestLoadIdleTimeoutUnset(t *testing.T) {
  196. m, err := manifestFixture("idle-timeout-unset")
  197. if assert.Nil(t, err) {
  198. if assert.Equal(t, 1, len(m.Balancers())) {
  199. b := m.Balancers()[0]
  200. if val, err := b.IdleTimeout(); assert.Nil(t, err) {
  201. assert.Equal(t, val, "3600")
  202. }
  203. }
  204. }
  205. }
  206. func TestLoadIdleTimeoutSet(t *testing.T) {
  207. m, err := manifestFixture("idle-timeout-set")
  208. if assert.Nil(t, err) {
  209. if assert.Equal(t, 1, len(m.Balancers())) {
  210. b := m.Balancers()[0]
  211. if val, err := b.IdleTimeout(); assert.Nil(t, err) {
  212. assert.Equal(t, val, "99")
  213. }
  214. }
  215. }
  216. }
  217. func TestLoadBadVersion1(t *testing.T) {
  218. m, err := manifestFixture("bad-v1")
  219. if assert.Nil(t, m) && assert.NotNil(t, err) {
  220. assert.Equal(t, err.Error(), "error loading manifest: yaml: unmarshal errors:\n line 3: cannot unmarshal !!map into []string")
  221. }
  222. }
  223. func TestLoadBadVersion2(t *testing.T) {
  224. m, err := manifestFixture("bad-v2")
  225. if assert.Nil(t, m) && assert.NotNil(t, err) {
  226. assert.Equal(t, err.Error(), "error loading manifest: yaml: unmarshal errors:\n line 5: cannot unmarshal !!map into []string")
  227. }
  228. }
  229. func TestLoadNonexistentFile(t *testing.T) {
  230. m, err := manifest.LoadFile("/foo/bar/hope/this/doesnt/exist")
  231. if assert.Nil(t, m) && assert.NotNil(t, err) {
  232. assert.Equal(t, err.Error(), "open /foo/bar/hope/this/doesnt/exist: no such file or directory")
  233. }
  234. }
  235. func TestLoadUnknownVersion(t *testing.T) {
  236. m, err := manifestFixture("unknown-version")
  237. if assert.Nil(t, m) && assert.NotNil(t, err) {
  238. assert.Equal(t, err.Error(), "unknown manifest version: 3")
  239. }
  240. }
  241. func TestExternalPorts(t *testing.T) {
  242. m, err := manifestFixture("full-v1")
  243. if assert.Nil(t, err) {
  244. ports := m.ExternalPorts()
  245. if assert.Equal(t, len(ports), 2) {
  246. assert.Equal(t, ports[0], 80)
  247. assert.Equal(t, ports[1], 443)
  248. }
  249. }
  250. }
  251. func TestPortConflictsWithoutConflict(t *testing.T) {
  252. m, err := manifestFixture("port-conflicts")
  253. if assert.Nil(t, err) {
  254. pc, err := m.PortConflicts()
  255. if assert.Nil(t, err) {
  256. assert.Equal(t, len(pc), 0)
  257. }
  258. }
  259. }
  260. func TestPortConflictsWithConflict(t *testing.T) {
  261. m, err := manifestFixture("port-conflicts")
  262. if assert.Nil(t, err) {
  263. l, err := net.Listen("tcp", "127.0.0.1:30544")
  264. defer l.Close()
  265. ch := make(chan error)
  266. go func() {
  267. for {
  268. _, err := l.Accept()
  269. ch <- err
  270. }
  271. }()
  272. if assert.Nil(t, err) {
  273. pc, err := m.PortConflicts()
  274. if assert.Nil(t, err) && assert.Equal(t, len(pc), 1) {
  275. assert.Equal(t, pc[0], 30544)
  276. }
  277. }
  278. select {
  279. case <-time.After(200 * time.Millisecond):
  280. assert.Fail(t, "nothing connected to the server")
  281. case <-ch:
  282. }
  283. }
  284. }
  285. func TestManifestNetworks(t *testing.T) {
  286. m, err := manifestFixture("networks")
  287. if assert.Nil(t, err) {
  288. for _, s := range m.Services {
  289. assert.Equal(t, s.Networks, manifest.Networks{
  290. "foo": manifest.InternalNetwork{
  291. "external": manifest.ExternalNetwork{
  292. Name: "foo",
  293. },
  294. },
  295. })
  296. assert.Equal(t, s.NetworkName(), "foo")
  297. }
  298. }
  299. }
  300. func TestShift(t *testing.T) {
  301. m, err := manifestFixture("shift")
  302. if assert.Nil(t, err) {
  303. m.Shift(5000)
  304. web := m.Services["web"]
  305. if assert.NotNil(t, web) && assert.Equal(t, len(web.Ports), 2) {
  306. assert.Equal(t, web.Ports[0].Balancer, 5000)
  307. assert.Equal(t, web.Ports[0].Container, 5000)
  308. assert.Equal(t, web.Ports[1].Balancer, 11000)
  309. assert.Equal(t, web.Ports[1].Container, 7000)
  310. }
  311. other := m.Services["other"]
  312. if assert.NotNil(t, other) && assert.Equal(t, len(other.Ports), 2) {
  313. assert.Equal(t, other.Ports[0].Balancer, 8000)
  314. assert.Equal(t, other.Ports[0].Container, 8000)
  315. assert.Equal(t, other.Ports[1].Balancer, 15000)
  316. assert.Equal(t, other.Ports[1].Container, 9001)
  317. }
  318. }
  319. }
  320. func TestManifestMarshalYaml(t *testing.T) {
  321. strCmd := manifest.Command{
  322. String: "bin/web",
  323. }
  324. arrayCmd := manifest.Command{
  325. Array: []string{"sh", "-c", "bin/web"},
  326. }
  327. m := manifest.Manifest{
  328. Version: "1",
  329. Services: map[string]manifest.Service{
  330. "food": manifest.Service{
  331. Name: "food",
  332. Build: manifest.Build{
  333. Context: ".",
  334. Dockerfile: "Dockerfile",
  335. },
  336. Command: strCmd,
  337. Ports: manifest.Ports{
  338. manifest.Port{
  339. Public: true,
  340. Balancer: 10,
  341. Container: 10,
  342. },
  343. },
  344. },
  345. },
  346. }
  347. byts, err := yaml.Marshal(m)
  348. if err != nil {
  349. t.Error(err.Error())
  350. }
  351. m2, err := manifest.Load(byts)
  352. if err != nil {
  353. t.Error(err.Error())
  354. }
  355. assert.Equal(t, m2.Version, "2")
  356. assert.Equal(t, m2.Services["food"].Name, "food")
  357. assert.Equal(t, m2.Services["food"].Command.String, strCmd.String)
  358. // Test an array Command
  359. food := m.Services["food"]
  360. food.Command = arrayCmd
  361. m.Services["food"] = food
  362. byts, err = yaml.Marshal(m)
  363. if err != nil {
  364. t.Error(err.Error())
  365. }
  366. m2, err = manifest.Load(byts)
  367. if err != nil {
  368. t.Error(err.Error())
  369. }
  370. assert.Equal(t, m2.Version, "2")
  371. assert.Equal(t, m2.Services["food"].Name, "food")
  372. assert.Equal(t, m2.Services["food"].Command.Array, arrayCmd.Array)
  373. }
  374. func TestManifestValidate(t *testing.T) {
  375. _, cerr := manifestFixture("invalid-cron")
  376. if assert.NotNil(t, cerr) {
  377. assert.Equal(t, cerr.Error(), "Cron task my_job is not valid (cron names can contain only alphanumeric characters and dashes and must be between 4 and 30 characters)")
  378. }
  379. _, lerr := manifestFixture("invalid-link")
  380. if assert.NotNil(t, lerr) {
  381. assert.Equal(t, lerr.Error(), "web links to service: database2 which does not exist")
  382. }
  383. _, lperr := manifestFixture("invalid-link-no-ports")
  384. if assert.NotNil(t, lperr) {
  385. assert.Equal(t, lperr.Error(), "web links to service: database which does not expose any ports")
  386. }
  387. _, herr := manifestFixture("invalid-health-timeout")
  388. if assert.NotNil(t, herr) {
  389. assert.Equal(t, herr.Error(), "convox.health.timeout is invalid for web, must be a number between 0 and 60")
  390. }
  391. }
  392. func manifestFixture(name string) (*manifest.Manifest, error) {
  393. return manifest.LoadFile(fmt.Sprintf("fixtures/%s.yml", name))
  394. }
  395. var randomAlphabet = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
  396. func randomString(size int) string {
  397. b := make([]rune, size)
  398. for i := range b {
  399. b[i] = randomAlphabet[rand.Intn(len(randomAlphabet))]
  400. }
  401. return string(b)
  402. }