github.com/azure/draft-classic@v0.16.0/pkg/draft/pack/repo/installer/vcs_installer.go (about)

     1  package installer
     2  
     3  import (
     4  	"net/url"
     5  	"os"
     6  	"path/filepath"
     7  	"sort"
     8  
     9  	"github.com/Masterminds/semver"
    10  	"github.com/Masterminds/vcs"
    11  	"k8s.io/helm/pkg/plugin/cache"
    12  
    13  	"github.com/Azure/draft/pkg/draft/draftpath"
    14  	"github.com/Azure/draft/pkg/draft/pack/repo"
    15  	"github.com/Azure/draft/pkg/osutil"
    16  	"github.com/Azure/draft/pkg/plugin/installer"
    17  )
    18  
    19  //VCSInstaller installs packs from a remote repository
    20  type VCSInstaller struct {
    21  	Repo    vcs.Repo
    22  	Version string
    23  	base
    24  }
    25  
    26  // NewVCSInstaller creates a new VCSInstaller.
    27  func NewVCSInstaller(source, version string, home draftpath.Home) (*VCSInstaller, error) {
    28  	// create a system safe cache key
    29  	key, err := cache.Key(source)
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  	cachedpath := home.Path("cache", "packs", key)
    34  	repo, err := vcs.NewRepo(source, cachedpath)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	i := &VCSInstaller{
    40  		Repo:    repo,
    41  		Version: version,
    42  		base:    newBase(source, home),
    43  	}
    44  	return i, err
    45  }
    46  
    47  // Path is where the pack repo will be symlinked to.
    48  func (i *VCSInstaller) Path() string {
    49  	u, err := url.Parse(i.Source)
    50  	if err == nil {
    51  		return filepath.Join(i.DraftHome.Packs(), u.Host, u.Path)
    52  	}
    53  	return filepath.Join(i.DraftHome.Packs(), filepath.Base(i.Source))
    54  }
    55  
    56  // Install clones a remote repository and creates a symlink to the pack repo directory in DRAFT_HOME
    57  //
    58  // Implements Installer
    59  func (i *VCSInstaller) Install() error {
    60  	if err := i.sync(i.Repo); err != nil {
    61  		return err
    62  	}
    63  
    64  	ref, err := i.solveVersion(i.Repo)
    65  	if err != nil {
    66  		return err
    67  	}
    68  
    69  	if ref != "" {
    70  		if err := i.setVersion(i.Repo, ref); err != nil {
    71  			return err
    72  		}
    73  	}
    74  
    75  	if !isPackRepo(i.Repo.LocalPath()) {
    76  		return repo.ErrHomeMissing
    77  	}
    78  
    79  	if err := os.MkdirAll(filepath.Dir(i.Path()), 0755); err != nil {
    80  		return err
    81  	}
    82  
    83  	return osutil.SymlinkWithFallback(i.Repo.LocalPath(), i.Path())
    84  }
    85  
    86  // Update updates a remote repository
    87  func (i *VCSInstaller) Update() error {
    88  	if i.Repo.IsDirty() {
    89  		return repo.ErrRepoDirty
    90  	}
    91  	if err := i.Repo.Update(); err != nil {
    92  		return err
    93  	}
    94  	if !isPackRepo(i.Repo.LocalPath()) {
    95  		return repo.ErrHomeMissing
    96  	}
    97  	return nil
    98  }
    99  
   100  func existingVCSRepo(location string, home draftpath.Home) (installer.Installer, error) {
   101  	repo, err := vcs.NewRepo("", location)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	i := &VCSInstaller{
   106  		Repo: repo,
   107  		base: newBase(repo.Remote(), home),
   108  	}
   109  
   110  	return i, err
   111  }
   112  
   113  // Filter a list of versions to only included semantic versions. The response
   114  // is a mapping of the original version to the semantic version.
   115  func getSemVers(refs []string) []*semver.Version {
   116  	var sv []*semver.Version
   117  	for _, r := range refs {
   118  		if v, err := semver.NewVersion(r); err == nil {
   119  			sv = append(sv, v)
   120  		}
   121  	}
   122  	return sv
   123  }
   124  
   125  // setVersion attempts to checkout the version
   126  func (i *VCSInstaller) setVersion(repo vcs.Repo, ref string) error {
   127  	return repo.UpdateVersion(ref)
   128  }
   129  
   130  func (i *VCSInstaller) solveVersion(rp vcs.Repo) (string, error) {
   131  	if i.Version == "" {
   132  		return "", nil
   133  	}
   134  
   135  	if rp.IsReference(i.Version) {
   136  		return i.Version, nil
   137  	}
   138  
   139  	// Create the constraint first to make sure it's valid before
   140  	// working on the repo.
   141  	constraint, err := semver.NewConstraint(i.Version)
   142  	if err != nil {
   143  		return "", err
   144  	}
   145  
   146  	// Get the tags
   147  	refs, err := rp.Tags()
   148  	if err != nil {
   149  		return "", err
   150  	}
   151  
   152  	// Convert and filter the list to semver.Version instances
   153  	semvers := getSemVers(refs)
   154  
   155  	// Sort semver list
   156  	sort.Sort(sort.Reverse(semver.Collection(semvers)))
   157  	for _, v := range semvers {
   158  		if constraint.Check(v) {
   159  			// If the constraint passes get the original reference
   160  			ver := v.Original()
   161  			return ver, nil
   162  		}
   163  	}
   164  
   165  	return "", repo.ErrVersionDoesNotExist
   166  }
   167  
   168  // sync will clone or update a remote repo.
   169  func (i *VCSInstaller) sync(repo vcs.Repo) error {
   170  
   171  	if _, err := os.Stat(repo.LocalPath()); os.IsNotExist(err) {
   172  		return repo.Get()
   173  	}
   174  	return repo.Update()
   175  }