GitOps for k8s
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

releaser_test.go 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. package release
  2. import (
  3. "encoding/json"
  4. "reflect"
  5. "testing"
  6. "time"
  7. "github.com/go-kit/kit/log"
  8. "github.com/weaveworks/flux"
  9. "github.com/weaveworks/flux/cluster"
  10. "github.com/weaveworks/flux/cluster/kubernetes"
  11. "github.com/weaveworks/flux/git"
  12. "github.com/weaveworks/flux/git/gittest"
  13. "github.com/weaveworks/flux/image"
  14. registryMock "github.com/weaveworks/flux/registry/mock"
  15. "github.com/weaveworks/flux/resource"
  16. "github.com/weaveworks/flux/update"
  17. )
  18. var (
  19. // This must match the value in cluster/kubernetes/testfiles/data.go
  20. helloContainer = "greeter"
  21. sidecarContainer = "sidecar"
  22. oldImage = "quay.io/weaveworks/helloworld:master-a000001"
  23. oldRef, _ = image.ParseRef(oldImage)
  24. sidecarImage = "weaveworks/sidecar:master-a000001"
  25. sidecarRef, _ = image.ParseRef(sidecarImage)
  26. hwSvcID, _ = flux.ParseResourceID("default:deployment/helloworld")
  27. hwSvcSpec, _ = update.ParseResourceSpec(hwSvcID.String())
  28. hwSvc = cluster.Controller{
  29. ID: hwSvcID,
  30. Containers: cluster.ContainersOrExcuse{
  31. Containers: []resource.Container{
  32. {
  33. Name: helloContainer,
  34. Image: oldRef,
  35. },
  36. {
  37. Name: sidecarContainer,
  38. Image: sidecarRef,
  39. },
  40. },
  41. },
  42. }
  43. testServiceRef, _ = image.ParseRef("quay.io/weaveworks/test-service:1")
  44. oldLockedImg = "quay.io/weaveworks/locked-service:1"
  45. oldLockedRef, _ = image.ParseRef(oldLockedImg)
  46. newLockedImg = "quay.io/weaveworks/locked-service:2"
  47. newLockedID, _ = image.ParseRef(newLockedImg)
  48. lockedSvcID, _ = flux.ParseResourceID("default:deployment/locked-service")
  49. lockedSvcSpec, _ = update.ParseResourceSpec(lockedSvcID.String())
  50. lockedSvc = cluster.Controller{
  51. ID: lockedSvcID,
  52. Containers: cluster.ContainersOrExcuse{
  53. Containers: []resource.Container{
  54. {
  55. Name: "locked-service",
  56. Image: oldLockedRef,
  57. },
  58. },
  59. },
  60. }
  61. testSvc = cluster.Controller{
  62. ID: flux.MustParseResourceID("default:deployment/test-service"),
  63. Containers: cluster.ContainersOrExcuse{
  64. Containers: []resource.Container{
  65. {
  66. Name: "test-service",
  67. Image: testServiceRef,
  68. },
  69. },
  70. },
  71. }
  72. testSvcSpec, _ = update.ParseResourceSpec(testSvc.ID.String())
  73. allSvcs = []cluster.Controller{
  74. hwSvc,
  75. lockedSvc,
  76. testSvc,
  77. }
  78. newHwRef, _ = image.ParseRef("quay.io/weaveworks/helloworld:master-a000002")
  79. // this is what we expect things to be updated to
  80. newSidecarRef, _ = image.ParseRef("weaveworks/sidecar:master-a000002")
  81. // this is what we store in the registry cache
  82. canonSidecarRef, _ = image.ParseRef("index.docker.io/weaveworks/sidecar:master-a000002")
  83. timeNow = time.Now()
  84. mockRegistry = &registryMock.Registry{
  85. Images: []image.Info{
  86. {
  87. ID: newHwRef,
  88. CreatedAt: timeNow,
  89. },
  90. {
  91. ID: newSidecarRef,
  92. CreatedAt: timeNow,
  93. },
  94. {
  95. ID: newLockedID,
  96. CreatedAt: timeNow,
  97. },
  98. },
  99. }
  100. mockManifests = &kubernetes.Manifests{}
  101. )
  102. func mockCluster(running ...cluster.Controller) *cluster.Mock {
  103. return &cluster.Mock{
  104. AllServicesFunc: func(string) ([]cluster.Controller, error) {
  105. return running, nil
  106. },
  107. SomeServicesFunc: func(ids []flux.ResourceID) ([]cluster.Controller, error) {
  108. var res []cluster.Controller
  109. for _, id := range ids {
  110. for _, svc := range running {
  111. if id == svc.ID {
  112. res = append(res, svc)
  113. }
  114. }
  115. }
  116. return res, nil
  117. },
  118. }
  119. }
  120. func setup(t *testing.T) (*git.Checkout, func()) {
  121. return gittest.Checkout(t)
  122. }
  123. func Test_FilterLogic(t *testing.T) {
  124. cluster := mockCluster(hwSvc, lockedSvc) // no testsvc in cluster, but it _is_ in repo
  125. notInRepoService := "default:deployment/notInRepo"
  126. notInRepoSpec, _ := update.ParseResourceSpec(notInRepoService)
  127. for _, tst := range []struct {
  128. Name string
  129. Spec update.ReleaseSpec
  130. Expected update.Result
  131. }{
  132. // ignored if: excluded OR not included OR not correct image.
  133. {
  134. Name: "include specific service",
  135. Spec: update.ReleaseSpec{
  136. ServiceSpecs: []update.ResourceSpec{hwSvcSpec},
  137. ImageSpec: update.ImageSpecLatest,
  138. Kind: update.ReleaseKindExecute,
  139. Excludes: []flux.ResourceID{},
  140. },
  141. Expected: update.Result{
  142. flux.MustParseResourceID("default:deployment/helloworld"): update.ControllerResult{
  143. Status: update.ReleaseStatusSuccess,
  144. PerContainer: []update.ContainerUpdate{
  145. update.ContainerUpdate{
  146. Container: helloContainer,
  147. Current: oldRef,
  148. Target: newHwRef,
  149. },
  150. update.ContainerUpdate{
  151. Container: sidecarContainer,
  152. Current: sidecarRef,
  153. Target: newSidecarRef,
  154. },
  155. },
  156. },
  157. flux.MustParseResourceID("default:deployment/locked-service"): update.ControllerResult{
  158. Status: update.ReleaseStatusIgnored,
  159. Error: update.NotIncluded,
  160. },
  161. flux.MustParseResourceID("default:deployment/test-service"): update.ControllerResult{
  162. Status: update.ReleaseStatusIgnored,
  163. Error: update.NotIncluded,
  164. },
  165. },
  166. }, {
  167. Name: "exclude specific service",
  168. Spec: update.ReleaseSpec{
  169. ServiceSpecs: []update.ResourceSpec{update.ResourceSpecAll},
  170. ImageSpec: update.ImageSpecLatest,
  171. Kind: update.ReleaseKindExecute,
  172. Excludes: []flux.ResourceID{lockedSvcID},
  173. },
  174. Expected: update.Result{
  175. flux.MustParseResourceID("default:deployment/helloworld"): update.ControllerResult{
  176. Status: update.ReleaseStatusSuccess,
  177. PerContainer: []update.ContainerUpdate{
  178. update.ContainerUpdate{
  179. Container: helloContainer,
  180. Current: oldRef,
  181. Target: newHwRef,
  182. },
  183. update.ContainerUpdate{
  184. Container: sidecarContainer,
  185. Current: sidecarRef,
  186. Target: newSidecarRef,
  187. },
  188. },
  189. },
  190. flux.MustParseResourceID("default:deployment/locked-service"): update.ControllerResult{
  191. Status: update.ReleaseStatusIgnored,
  192. Error: update.Excluded,
  193. },
  194. flux.MustParseResourceID("default:deployment/test-service"): update.ControllerResult{
  195. Status: update.ReleaseStatusSkipped,
  196. Error: update.NotInCluster,
  197. },
  198. },
  199. }, {
  200. Name: "update specific image",
  201. Spec: update.ReleaseSpec{
  202. ServiceSpecs: []update.ResourceSpec{update.ResourceSpecAll},
  203. ImageSpec: update.ImageSpecFromRef(newHwRef),
  204. Kind: update.ReleaseKindExecute,
  205. Excludes: []flux.ResourceID{},
  206. },
  207. Expected: update.Result{
  208. flux.MustParseResourceID("default:deployment/helloworld"): update.ControllerResult{
  209. Status: update.ReleaseStatusSuccess,
  210. PerContainer: []update.ContainerUpdate{
  211. update.ContainerUpdate{
  212. Container: helloContainer,
  213. Current: oldRef,
  214. Target: newHwRef,
  215. },
  216. },
  217. },
  218. flux.MustParseResourceID("default:deployment/locked-service"): update.ControllerResult{
  219. Status: update.ReleaseStatusIgnored,
  220. Error: update.DifferentImage,
  221. },
  222. flux.MustParseResourceID("default:deployment/test-service"): update.ControllerResult{
  223. Status: update.ReleaseStatusSkipped,
  224. Error: update.NotInCluster,
  225. },
  226. },
  227. },
  228. // skipped if: not ignored AND (locked or not found in cluster)
  229. // else: service is pending.
  230. {
  231. Name: "skipped & service is pending",
  232. Spec: update.ReleaseSpec{
  233. ServiceSpecs: []update.ResourceSpec{update.ResourceSpecAll},
  234. ImageSpec: update.ImageSpecLatest,
  235. Kind: update.ReleaseKindExecute,
  236. Excludes: []flux.ResourceID{},
  237. },
  238. Expected: update.Result{
  239. flux.MustParseResourceID("default:deployment/helloworld"): update.ControllerResult{
  240. Status: update.ReleaseStatusSuccess,
  241. PerContainer: []update.ContainerUpdate{
  242. update.ContainerUpdate{
  243. Container: helloContainer,
  244. Current: oldRef,
  245. Target: newHwRef,
  246. },
  247. update.ContainerUpdate{
  248. Container: sidecarContainer,
  249. Current: sidecarRef,
  250. Target: newSidecarRef,
  251. },
  252. },
  253. },
  254. flux.MustParseResourceID("default:deployment/locked-service"): update.ControllerResult{
  255. Status: update.ReleaseStatusSkipped,
  256. Error: update.Locked,
  257. },
  258. flux.MustParseResourceID("default:deployment/test-service"): update.ControllerResult{
  259. Status: update.ReleaseStatusSkipped,
  260. Error: update.NotInCluster,
  261. },
  262. },
  263. },
  264. {
  265. Name: "all overrides spec",
  266. Spec: update.ReleaseSpec{
  267. ServiceSpecs: []update.ResourceSpec{hwSvcSpec, update.ResourceSpecAll},
  268. ImageSpec: update.ImageSpecLatest,
  269. Kind: update.ReleaseKindExecute,
  270. Excludes: []flux.ResourceID{},
  271. },
  272. Expected: update.Result{
  273. flux.MustParseResourceID("default:deployment/helloworld"): update.ControllerResult{
  274. Status: update.ReleaseStatusSuccess,
  275. PerContainer: []update.ContainerUpdate{
  276. update.ContainerUpdate{
  277. Container: helloContainer,
  278. Current: oldRef,
  279. Target: newHwRef,
  280. },
  281. update.ContainerUpdate{
  282. Container: sidecarContainer,
  283. Current: sidecarRef,
  284. Target: newSidecarRef,
  285. },
  286. },
  287. },
  288. flux.MustParseResourceID("default:deployment/locked-service"): update.ControllerResult{
  289. Status: update.ReleaseStatusSkipped,
  290. Error: update.Locked,
  291. },
  292. flux.MustParseResourceID("default:deployment/test-service"): update.ControllerResult{
  293. Status: update.ReleaseStatusSkipped,
  294. Error: update.NotInCluster,
  295. },
  296. },
  297. },
  298. {
  299. Name: "service not in repo",
  300. Spec: update.ReleaseSpec{
  301. ServiceSpecs: []update.ResourceSpec{notInRepoSpec},
  302. ImageSpec: update.ImageSpecLatest,
  303. Kind: update.ReleaseKindExecute,
  304. Excludes: []flux.ResourceID{},
  305. },
  306. Expected: update.Result{
  307. flux.MustParseResourceID("default:deployment/helloworld"): update.ControllerResult{
  308. Status: update.ReleaseStatusIgnored,
  309. Error: update.NotIncluded,
  310. },
  311. flux.MustParseResourceID("default:deployment/locked-service"): update.ControllerResult{
  312. Status: update.ReleaseStatusIgnored,
  313. Error: update.NotIncluded,
  314. },
  315. flux.MustParseResourceID("default:deployment/test-service"): update.ControllerResult{
  316. Status: update.ReleaseStatusIgnored,
  317. Error: update.NotIncluded,
  318. },
  319. flux.MustParseResourceID(notInRepoService): update.ControllerResult{
  320. Status: update.ReleaseStatusSkipped,
  321. Error: update.NotInRepo,
  322. },
  323. },
  324. },
  325. } {
  326. checkout, cleanup := setup(t)
  327. defer cleanup()
  328. testRelease(t, tst.Name, &ReleaseContext{
  329. cluster: cluster,
  330. manifests: mockManifests,
  331. registry: mockRegistry,
  332. repo: checkout,
  333. }, tst.Spec, tst.Expected)
  334. }
  335. }
  336. func Test_ImageStatus(t *testing.T) {
  337. cluster := mockCluster(hwSvc, lockedSvc, testSvc)
  338. upToDateRegistry := &registryMock.Registry{
  339. Images: []image.Info{
  340. {
  341. ID: oldRef,
  342. CreatedAt: timeNow,
  343. },
  344. {
  345. ID: sidecarRef,
  346. CreatedAt: timeNow,
  347. },
  348. },
  349. }
  350. testSvcSpec, _ := update.ParseResourceSpec(testSvc.ID.String())
  351. for _, tst := range []struct {
  352. Name string
  353. Spec update.ReleaseSpec
  354. Expected update.Result
  355. }{
  356. {
  357. Name: "image not found",
  358. Spec: update.ReleaseSpec{
  359. ServiceSpecs: []update.ResourceSpec{testSvcSpec},
  360. ImageSpec: update.ImageSpecLatest,
  361. Kind: update.ReleaseKindExecute,
  362. Excludes: []flux.ResourceID{},
  363. },
  364. Expected: update.Result{
  365. flux.MustParseResourceID("default:deployment/helloworld"): update.ControllerResult{
  366. Status: update.ReleaseStatusIgnored,
  367. Error: update.NotIncluded,
  368. },
  369. flux.MustParseResourceID("default:deployment/locked-service"): update.ControllerResult{
  370. Status: update.ReleaseStatusIgnored,
  371. Error: update.NotIncluded,
  372. },
  373. flux.MustParseResourceID("default:deployment/test-service"): update.ControllerResult{
  374. Status: update.ReleaseStatusIgnored,
  375. Error: update.DoesNotUseImage,
  376. },
  377. },
  378. }, {
  379. Name: "image up to date",
  380. Spec: update.ReleaseSpec{
  381. ServiceSpecs: []update.ResourceSpec{hwSvcSpec},
  382. ImageSpec: update.ImageSpecLatest,
  383. Kind: update.ReleaseKindExecute,
  384. Excludes: []flux.ResourceID{},
  385. },
  386. Expected: update.Result{
  387. flux.MustParseResourceID("default:deployment/helloworld"): update.ControllerResult{
  388. Status: update.ReleaseStatusSkipped,
  389. Error: update.ImageUpToDate,
  390. },
  391. flux.MustParseResourceID("default:deployment/locked-service"): update.ControllerResult{
  392. Status: update.ReleaseStatusIgnored,
  393. Error: update.NotIncluded,
  394. },
  395. flux.MustParseResourceID("default:deployment/test-service"): update.ControllerResult{
  396. Status: update.ReleaseStatusIgnored,
  397. Error: update.NotIncluded,
  398. },
  399. },
  400. },
  401. } {
  402. checkout, cleanup := setup(t)
  403. defer cleanup()
  404. ctx := &ReleaseContext{
  405. cluster: cluster,
  406. manifests: mockManifests,
  407. repo: checkout,
  408. registry: upToDateRegistry,
  409. }
  410. testRelease(t, tst.Name, ctx, tst.Spec, tst.Expected)
  411. }
  412. }
  413. func testRelease(t *testing.T, name string, ctx *ReleaseContext, spec update.ReleaseSpec, expected update.Result) {
  414. results, err := Release(ctx, spec, log.NewNopLogger())
  415. if err != nil {
  416. t.Fatal(err)
  417. }
  418. if !reflect.DeepEqual(expected, results) {
  419. exp, _ := json.Marshal(expected)
  420. got, _ := json.Marshal(results)
  421. t.Errorf("%s\n--- expected ---\n%s\n--- got ---\n%s\n", name, string(exp), string(got))
  422. }
  423. }