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