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  }