Added a few unit test for Arvados class
[arvados.git] / services / crunch-run / git_mount.go
1 package main
2
3 import (
4         "fmt"
5         "net/url"
6         "os"
7         "path/filepath"
8         "regexp"
9
10         "git.curoverse.com/arvados.git/sdk/go/arvados"
11         "gopkg.in/src-d/go-billy.v3/osfs"
12         git "gopkg.in/src-d/go-git.v4"
13         git_config "gopkg.in/src-d/go-git.v4/config"
14         git_plumbing "gopkg.in/src-d/go-git.v4/plumbing"
15         git_http "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
16         "gopkg.in/src-d/go-git.v4/storage/memory"
17 )
18
19 type gitMount arvados.Mount
20
21 var (
22         sha1re     = regexp.MustCompile(`^[0-9a-f]{40}$`)
23         repoUUIDre = regexp.MustCompile(`^[0-9a-z]{5}-s0uqq-[0-9a-z]{15}$`)
24 )
25
26 func (gm gitMount) validate() error {
27         if gm.Path != "" && gm.Path != "/" {
28                 return fmt.Errorf("cannot mount git_tree with path %q -- only \"/\" is supported", gm.Path)
29         }
30         if !sha1re.MatchString(gm.Commit) {
31                 return fmt.Errorf("cannot mount git_tree with commit %q -- must be a 40-char SHA1", gm.Commit)
32         }
33         if gm.RepositoryName != "" || gm.GitURL != "" {
34                 return fmt.Errorf("cannot mount git_tree -- repository_name and git_url must be empty")
35         }
36         if !repoUUIDre.MatchString(gm.UUID) {
37                 return fmt.Errorf("cannot mount git_tree with uuid %q -- must be a repository UUID", gm.UUID)
38         }
39         if gm.Writable {
40                 return fmt.Errorf("writable git_tree mount is not supported")
41         }
42         return nil
43 }
44
45 // ExtractTree extracts the specified tree into dir, which is an
46 // existing empty local directory.
47 func (gm gitMount) extractTree(ac IArvadosClient, dir string, token string) error {
48         err := gm.validate()
49         if err != nil {
50                 return err
51         }
52         baseURL, err := ac.Discovery("gitUrl")
53         if err != nil {
54                 return fmt.Errorf("discover gitUrl from API: %s", err)
55         } else if _, ok := baseURL.(string); !ok {
56                 return fmt.Errorf("discover gitUrl from API: expected string, found %T", baseURL)
57         }
58
59         u, err := url.Parse(baseURL.(string))
60         if err != nil {
61                 return fmt.Errorf("parse gitUrl %q: %s", baseURL, err)
62         }
63         u, err = u.Parse("/" + gm.UUID + ".git")
64         if err != nil {
65                 return fmt.Errorf("build git url from %q, %q: %s", baseURL, gm.UUID, err)
66         }
67         store := memory.NewStorage()
68         repo, err := git.Init(store, osfs.New(dir))
69         if err != nil {
70                 return fmt.Errorf("init repo: %s", err)
71         }
72         _, err = repo.CreateRemote(&git_config.RemoteConfig{
73                 Name: "origin",
74                 URLs: []string{u.String()},
75         })
76         if err != nil {
77                 return fmt.Errorf("create remote %q: %s", u.String(), err)
78         }
79         err = repo.Fetch(&git.FetchOptions{
80                 RemoteName: "origin",
81                 Auth:       git_http.NewBasicAuth("none", token),
82         })
83         if err != nil {
84                 return fmt.Errorf("git fetch %q: %s", u.String(), err)
85         }
86         wt, err := repo.Worktree()
87         if err != nil {
88                 return fmt.Errorf("worktree failed: %s", err)
89         }
90         err = wt.Checkout(&git.CheckoutOptions{
91                 Hash: git_plumbing.NewHash(gm.Commit),
92         })
93         if err != nil {
94                 return fmt.Errorf("checkout failed: %s", err)
95         }
96         err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
97                 if err != nil {
98                         return err
99                 }
100                 // copy user rx bits to group and other, in case
101                 // prevailing umask is more restrictive than 022
102                 mode := info.Mode()
103                 mode = mode | ((mode >> 3) & 050) | ((mode >> 6) & 5)
104                 return os.Chmod(path, mode)
105         })
106         if err != nil {
107                 return fmt.Errorf("chmod -R %q: %s", dir, err)
108         }
109         return nil
110 }