8311: Remove support for writable git_tree mount.
[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         if gm.Writable {
39                 return fmt.Errorf("writable git_tree mount is not supported")
40         }
41         return nil
42 }
43
44 // ExtractTree extracts the specified tree into dir, which is an
45 // existing empty local directory.
46 func (gm gitMount) extractTree(ac IArvadosClient, dir string, token string) error {
47         err := gm.validate()
48         if err != nil {
49                 return err
50         }
51         baseURL, err := ac.Discovery("gitUrl")
52         if err != nil {
53                 return fmt.Errorf("discover gitUrl from API: %s", err)
54         } else if _, ok := baseURL.(string); !ok {
55                 return fmt.Errorf("discover gitUrl from API: expected string, found %T", baseURL)
56         }
57
58         u, err := url.Parse(baseURL.(string))
59         if err != nil {
60                 return fmt.Errorf("parse gitUrl %q: %s", baseURL, err)
61         }
62         u, err = u.Parse("/" + gm.UUID + ".git")
63         if err != nil {
64                 return fmt.Errorf("build git url from %q, %q: %s", baseURL, gm.UUID, err)
65         }
66         store := memory.NewStorage()
67         repo, err := git.Init(store, osfs.New(dir))
68         if err != nil {
69                 return fmt.Errorf("init repo: %s", err)
70         }
71         _, err = repo.CreateRemote(&git_config.RemoteConfig{
72                 Name: "origin",
73                 URLs: []string{u.String()},
74         })
75         if err != nil {
76                 return fmt.Errorf("create remote %q: %s", u.String(), err)
77         }
78         err = repo.Fetch(&git.FetchOptions{
79                 RemoteName: "origin",
80                 Auth:       git_http.NewBasicAuth("none", token),
81         })
82         if err != nil {
83                 return fmt.Errorf("git fetch %q: %s", u.String(), err)
84         }
85         wt, err := repo.Worktree()
86         if err != nil {
87                 return fmt.Errorf("worktree failed: %s", err)
88         }
89         err = wt.Checkout(&git.CheckoutOptions{
90                 Hash: git_plumbing.NewHash(gm.Commit),
91         })
92         if err != nil {
93                 return fmt.Errorf("checkout failed: %s", err)
94         }
95         err = os.Chmod(dir, 0755)
96         if err != nil {
97                 return fmt.Errorf("chmod %o %q: %s", 0755, dir, err)
98         }
99         return nil
100 }