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.

operations_test.go 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. package git
  2. import (
  3. "bytes"
  4. "context"
  5. "fmt"
  6. "io/ioutil"
  7. "os/exec"
  8. "path"
  9. "path/filepath"
  10. "testing"
  11. "time"
  12. "github.com/stretchr/testify/assert"
  13. "github.com/fluxcd/flux/cluster/kubernetes/testfiles"
  14. )
  15. const (
  16. testNoteRef = "flux-sync"
  17. )
  18. var (
  19. noteIdCounter = 1
  20. )
  21. type Note struct {
  22. ID string
  23. }
  24. func TestListNotes_2Notes(t *testing.T) {
  25. newDir, cleanup := testfiles.TempDir(t)
  26. defer cleanup()
  27. err := createRepo(newDir, []string{"another"})
  28. if err != nil {
  29. t.Fatal(err)
  30. }
  31. idHEAD_1, err := testNote(newDir, "HEAD~1")
  32. if err != nil {
  33. t.Fatal(err)
  34. }
  35. idHEAD, err := testNote(newDir, "HEAD")
  36. if err != nil {
  37. t.Fatal(err)
  38. }
  39. notes, err := noteRevList(context.Background(), newDir, testNoteRef)
  40. if err != nil {
  41. t.Fatal(err)
  42. }
  43. // Now check that these commits actually have a note
  44. if len(notes) != 2 {
  45. t.Fatal("expected two notes")
  46. }
  47. for rev := range notes {
  48. var note Note
  49. ok, err := getNote(context.Background(), newDir, testNoteRef, rev, &note)
  50. if err != nil {
  51. t.Error(err)
  52. }
  53. if !ok {
  54. t.Error("note not found for commit:", rev)
  55. }
  56. if note.ID != idHEAD_1 && note.ID != idHEAD {
  57. t.Error("Note contents not expected:", note.ID)
  58. }
  59. }
  60. }
  61. func TestListNotes_0Notes(t *testing.T) {
  62. newDir, cleanup := testfiles.TempDir(t)
  63. defer cleanup()
  64. err := createRepo(newDir, []string{"another"})
  65. if err != nil {
  66. t.Fatal(err)
  67. }
  68. notes, err := noteRevList(context.Background(), newDir, testNoteRef)
  69. if err != nil {
  70. t.Fatal(err)
  71. }
  72. if len(notes) != 0 {
  73. t.Fatal("expected two notes")
  74. }
  75. }
  76. func testNote(dir, rev string) (string, error) {
  77. id := fmt.Sprintf("%v", noteIdCounter)
  78. noteIdCounter += 1
  79. err := addNote(context.Background(), dir, rev, testNoteRef, &Note{ID: id})
  80. return id, err
  81. }
  82. func TestChangedFiles_SlashPath(t *testing.T) {
  83. newDir, cleanup := testfiles.TempDir(t)
  84. defer cleanup()
  85. nestedDir := "/test/dir"
  86. err := createRepo(newDir, []string{nestedDir})
  87. if err != nil {
  88. t.Fatal(err)
  89. }
  90. _, err = changed(context.Background(), newDir, "HEAD", []string{nestedDir})
  91. if err == nil {
  92. t.Fatal("Should have errored")
  93. }
  94. }
  95. func TestChangedFiles_UnslashPath(t *testing.T) {
  96. newDir, cleanup := testfiles.TempDir(t)
  97. defer cleanup()
  98. nestedDir := "test/dir"
  99. err := createRepo(newDir, []string{nestedDir})
  100. if err != nil {
  101. t.Fatal(err)
  102. }
  103. _, err = changed(context.Background(), newDir, "HEAD", []string{nestedDir})
  104. if err != nil {
  105. t.Fatal(err)
  106. }
  107. }
  108. func TestChangedFiles_NoPath(t *testing.T) {
  109. newDir, cleanup := testfiles.TempDir(t)
  110. defer cleanup()
  111. nestedDir := ""
  112. err := createRepo(newDir, []string{nestedDir})
  113. if err != nil {
  114. t.Fatal(err)
  115. }
  116. _, err = changed(context.Background(), newDir, "HEAD", nil)
  117. if err != nil {
  118. t.Fatal(err)
  119. }
  120. }
  121. func TestChangedFiles_LeadingSpace(t *testing.T) {
  122. newDir, cleanup := testfiles.TempDir(t)
  123. defer cleanup()
  124. err := createRepo(newDir, []string{})
  125. if err != nil {
  126. t.Fatal(err)
  127. }
  128. filename := " space.yaml"
  129. if err = updateDirAndCommit(newDir, "", map[string]string{filename: "foo"}); err != nil {
  130. t.Fatal(err)
  131. }
  132. files, err := changed(context.Background(), newDir, "HEAD~1", []string{})
  133. if err != nil {
  134. t.Fatal(err)
  135. }
  136. if len(files) != 1 {
  137. t.Fatal("expected 1 changed file")
  138. }
  139. if actualFilename := files[0]; actualFilename != filename {
  140. t.Fatalf("expected changed filename to equal: '%s', got '%s'", filename, actualFilename)
  141. }
  142. }
  143. func TestOnelinelog_NoGitpath(t *testing.T) {
  144. newDir, cleanup := testfiles.TempDir(t)
  145. defer cleanup()
  146. subdirs := []string{"dev", "prod"}
  147. err := createRepo(newDir, subdirs)
  148. if err != nil {
  149. t.Fatal(err)
  150. }
  151. if err = updateDirAndCommit(newDir, "dev", testfiles.FilesUpdated); err != nil {
  152. t.Fatal(err)
  153. }
  154. if err = updateDirAndCommit(newDir, "prod", testfiles.FilesUpdated); err != nil {
  155. t.Fatal(err)
  156. }
  157. commits, err := onelinelog(context.Background(), newDir, "HEAD~2..HEAD", nil)
  158. if err != nil {
  159. t.Fatal(err)
  160. }
  161. if len(commits) != 2 {
  162. t.Fatal(err)
  163. }
  164. }
  165. func TestOnelinelog_WithGitpath(t *testing.T) {
  166. newDir, cleanup := testfiles.TempDir(t)
  167. defer cleanup()
  168. subdirs := []string{"dev", "prod"}
  169. err := createRepo(newDir, subdirs)
  170. if err != nil {
  171. t.Fatal(err)
  172. }
  173. if err = updateDirAndCommit(newDir, "dev", testfiles.FilesUpdated); err != nil {
  174. t.Fatal(err)
  175. }
  176. if err = updateDirAndCommit(newDir, "prod", testfiles.FilesUpdated); err != nil {
  177. t.Fatal(err)
  178. }
  179. commits, err := onelinelog(context.Background(), newDir, "HEAD~2..HEAD", []string{"dev"})
  180. if err != nil {
  181. t.Fatal(err)
  182. }
  183. if len(commits) != 1 {
  184. t.Fatal(err)
  185. }
  186. }
  187. func TestCheckPush(t *testing.T) {
  188. upstreamDir, upstreamCleanup := testfiles.TempDir(t)
  189. defer upstreamCleanup()
  190. if err := createRepo(upstreamDir, []string{"config"}); err != nil {
  191. t.Fatal(err)
  192. }
  193. cloneDir, cloneCleanup := testfiles.TempDir(t)
  194. defer cloneCleanup()
  195. working, err := clone(context.Background(), cloneDir, upstreamDir, "master")
  196. if err != nil {
  197. t.Fatal(err)
  198. }
  199. err = checkPush(context.Background(), working, upstreamDir, "")
  200. if err != nil {
  201. t.Fatal(err)
  202. }
  203. }
  204. // ---
  205. func createRepo(dir string, subdirs []string) error {
  206. var (
  207. err error
  208. fullPath string
  209. )
  210. if err = execCommand("git", "-C", dir, "init"); err != nil {
  211. return err
  212. }
  213. if err := config(context.Background(), dir, "operations_test_user", "example@example.com"); err != nil {
  214. return err
  215. }
  216. for _, subdir := range subdirs {
  217. fullPath = path.Join(dir, subdir)
  218. if err = execCommand("mkdir", "-p", fullPath); err != nil {
  219. return err
  220. }
  221. if err = testfiles.WriteTestFiles(fullPath); err != nil {
  222. return err
  223. }
  224. if err = execCommand("git", "-C", dir, "add", "--all"); err != nil {
  225. return err
  226. }
  227. if err = execCommand("git", "-C", dir, "commit", "-m", "'Initial revision'"); err != nil {
  228. return err
  229. }
  230. }
  231. if err = execCommand("git", "-C", dir, "commit", "--allow-empty", "-m", "'Second revision'"); err != nil {
  232. return err
  233. }
  234. return nil
  235. }
  236. func execCommand(cmd string, args ...string) error {
  237. c := exec.Command(cmd, args...)
  238. c.Stderr = ioutil.Discard
  239. c.Stdout = ioutil.Discard
  240. return c.Run()
  241. }
  242. // Replaces/creates a file
  243. func updateFile(path string, files map[string]string) error {
  244. for file, content := range files {
  245. path := filepath.Join(path, file)
  246. if err := ioutil.WriteFile(path, []byte(content), 0666); err != nil {
  247. return err
  248. }
  249. }
  250. return nil
  251. }
  252. func updateDirAndCommit(dir, subdir string, filesUpdated map[string]string) error {
  253. path := filepath.Join(dir, subdir)
  254. if err := updateFile(path, filesUpdated); err != nil {
  255. return err
  256. }
  257. if err := execCommand("git", "-C", path, "add", "--all"); err != nil {
  258. return err
  259. }
  260. if err := execCommand("git", "-C", path, "commit", "-m", "'Update 1'"); err != nil {
  261. return err
  262. }
  263. return nil
  264. }
  265. func TestTraceGitCommand(t *testing.T) {
  266. type input struct {
  267. args []string
  268. config gitCmdConfig
  269. out string
  270. err string
  271. }
  272. examples := []struct {
  273. name string
  274. input input
  275. expected string
  276. actual string
  277. }{
  278. {
  279. name: "git clone",
  280. input: input{
  281. args: []string{
  282. "clone",
  283. "--branch",
  284. "master",
  285. "/tmp/flux-gitclone239583443",
  286. "/tmp/flux-working628880789",
  287. },
  288. config: gitCmdConfig{
  289. dir: "/tmp/flux-working628880789",
  290. },
  291. },
  292. expected: `TRACE: command="git clone --branch master /tmp/flux-gitclone239583443 /tmp/flux-working628880789" out="" dir="/tmp/flux-working628880789" env=""`,
  293. },
  294. {
  295. name: "git rev-list",
  296. input: input{
  297. args: []string{
  298. "rev-list",
  299. "--max-count",
  300. "1",
  301. "flux-sync",
  302. "--",
  303. },
  304. out: "b9d6a543acf8085ff6bed23fac17f8dc71bfcb66",
  305. config: gitCmdConfig{
  306. dir: "/tmp/flux-gitclone239583443",
  307. },
  308. },
  309. expected: `TRACE: command="git rev-list --max-count 1 flux-sync --" out="b9d6a543acf8085ff6bed23fac17f8dc71bfcb66" dir="/tmp/flux-gitclone239583443" env=""`,
  310. },
  311. {
  312. name: "git config email",
  313. input: input{
  314. args: []string{
  315. "config",
  316. "user.email",
  317. "support@weave.works",
  318. },
  319. config: gitCmdConfig{
  320. dir: "/tmp/flux-working056923691",
  321. },
  322. },
  323. expected: `TRACE: command="git config user.email support@weave.works" out="" dir="/tmp/flux-working056923691" env=""`,
  324. },
  325. {
  326. name: "git notes",
  327. input: input{
  328. args: []string{
  329. "notes",
  330. "--ref",
  331. "flux",
  332. "get-ref",
  333. },
  334. config: gitCmdConfig{
  335. dir: "/tmp/flux-working647148942",
  336. },
  337. out: "refs/notes/flux",
  338. },
  339. expected: `TRACE: command="git notes --ref flux get-ref" out="refs/notes/flux" dir="/tmp/flux-working647148942" env=""`,
  340. },
  341. }
  342. for _, example := range examples {
  343. actual := traceGitCommand(
  344. example.input.args,
  345. example.input.config,
  346. example.input.out,
  347. )
  348. assert.Equal(t, example.expected, actual)
  349. }
  350. }
  351. // TestMutexBuffer tests that the threadsafe buffer used to capture
  352. // stdout and stderr does not give rise to races or deadlocks. In
  353. // particular, this test guards against reverting to a situation in
  354. // which copying into the buffer from two goroutines can deadlock it,
  355. // if one of them uses `ReadFrom`.
  356. func TestMutexBuffer(t *testing.T) {
  357. ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
  358. defer cancel()
  359. out := &bytes.Buffer{}
  360. err := execGitCmd(ctx, []string{"log", "--oneline"}, gitCmdConfig{out: out})
  361. if err != nil {
  362. t.Fatal(err)
  363. }
  364. }