github.com/coreos/mantle@v0.13.0/sdk/repo/verify.go (about) 1 // Copyright 2016 CoreOS, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package repo 16 17 import ( 18 "bytes" 19 "crypto/sha1" 20 "encoding/hex" 21 "encoding/xml" 22 "errors" 23 "fmt" 24 "os" 25 "path/filepath" 26 27 "github.com/coreos/pkg/capnslog" 28 29 "github.com/coreos/mantle/sdk" 30 "github.com/coreos/mantle/system/exec" 31 ) 32 33 var ( 34 plog = capnslog.NewPackageLogger("github.com/coreos/mantle", "sdk/repo") 35 36 Unimplemented = errors.New("repo: unimplemented feature in manifest") 37 MissingField = errors.New("repo: missing required field in manifest") 38 VerifyError = errors.New("repo: failed verification") 39 ) 40 41 type repo struct { 42 Manifest 43 root string 44 name string 45 } 46 47 func (r *repo) load(name string) (err error) { 48 r.root = sdk.RepoRoot() 49 path := filepath.Join(r.root, ".repo") 50 if len(name) != 0 { 51 path = filepath.Join(path, "manifests", name) 52 r.name = name 53 } else { 54 path = filepath.Join(path, "manifest.xml") 55 r.name = "manifest" // just need something for errs 56 } 57 58 file, err := os.Open(path) 59 if err != nil { 60 return err 61 } 62 defer file.Close() 63 64 if err = xml.NewDecoder(file).Decode(&r.Manifest); err != nil { 65 return err 66 } 67 68 // Check for currently unsupported features. 69 assertEmpty := func(l int, f string) { 70 if l == 0 { 71 return 72 } 73 plog.Errorf("Unsupported feature %s in %s", f, r.name) 74 err = Unimplemented 75 } 76 assertEmpty(len(r.Includes), "include") 77 assertEmpty(len(r.ExtendProjects), "extend-project") 78 assertEmpty(len(r.RemoveProjects), "remove-project") 79 for _, project := range r.Projects { 80 if len(project.SubProjects) != 0 { 81 plog.Errorf("Unsupported sub-project in %s", r.name) 82 err = Unimplemented 83 break 84 } 85 } 86 87 return err 88 } 89 90 func (r *repo) fillDefaults() (err error) { 91 for _, project := range r.Projects { 92 if project.Name == "" { 93 plog.Errorf("Project missing name in %s", r.name) 94 err = MissingField 95 } 96 97 if project.Path == "" { 98 project.Path = project.Name 99 } 100 101 if project.Remote == "" { 102 project.Remote = r.Default.Remote 103 } 104 if project.Remote == "" { 105 plog.Errorf("Project %s missing remote in %s", 106 project.Name, r.name) 107 err = MissingField 108 } 109 110 if project.Revision == "" { 111 project.Revision = r.Default.Revision 112 } 113 if project.Revision == "" { 114 plog.Errorf("Project %s missing revision in %s", 115 project.Name, r.name) 116 err = MissingField 117 } 118 119 if project.DestBranch == "" { 120 project.DestBranch = r.Default.DestBranch 121 } 122 if project.SyncBranch == "" { 123 project.SyncBranch = r.Default.SyncBranch 124 } 125 if project.SyncSubProjects == "" { 126 project.SyncSubProjects = r.Default.SyncSubProjects 127 } 128 } 129 return err 130 } 131 132 func isSHA1(s string) bool { 133 b, err := hex.DecodeString(s) 134 return err == nil && len(b) == sha1.Size 135 } 136 137 func (r *repo) projectHEAD(p Project) (string, error) { 138 git := exec.Command("git", "rev-list", "--max-count=1", "HEAD") 139 git.Dir = filepath.Join(r.root, p.Path) 140 git.Stderr = os.Stderr 141 out, err := git.Output() 142 if err != nil { 143 return "", err 144 } 145 146 rev := string(bytes.TrimSpace(out)) 147 if !isSHA1(rev) { 148 return "", fmt.Errorf("%s: bad revision %s", p.Path, rev) 149 } 150 151 return rev, nil 152 } 153 154 func (r *repo) projectIsClean(p Project) error { 155 git := exec.Command("git", "diff", "--quiet") 156 git.Dir = filepath.Join(r.root, p.Path) 157 git.Stdout = os.Stdout 158 git.Stderr = os.Stderr 159 return git.Run() 160 } 161 162 // VerifySync takes a manifest name and asserts the current repo checkout 163 // matches it exactly. Currently only supports manifests flattened by the 164 // "repo manifest -r" command. A blank name means to use the default. 165 // TODO: check symbolic refs too? e.g. HEAD == refs/remotes/origin/master 166 func VerifySync(name string) error { 167 var manifest repo 168 if err := manifest.load(name); err != nil { 169 return err 170 } 171 172 if err := manifest.fillDefaults(); err != nil { 173 return err 174 } 175 176 var result error 177 for _, project := range manifest.Projects { 178 if !isSHA1(project.Revision) { 179 plog.Errorf("Cannot verify %s revision %s in %s", 180 project.Name, project.Revision, manifest.name) 181 return Unimplemented 182 } 183 184 rev, err := manifest.projectHEAD(project) 185 if err != nil { 186 return err 187 } 188 189 if rev != project.Revision { 190 plog.Errorf("Project dir %s at %s, expected %s", 191 project.Path, rev, project.Revision) 192 result = VerifyError 193 } 194 195 if err := manifest.projectIsClean(project); err != nil { 196 plog.Errorf("Project dir %s is not clean, git diff %v", 197 project.Path, err) 198 result = VerifyError 199 } 200 } 201 return result 202 }