github.com/sdboyer/gps@v0.16.3/maybe_source.go (about)

     1  package gps
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"net/url"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/Masterminds/vcs"
    12  )
    13  
    14  // A maybeSource represents a set of information that, given some
    15  // typically-expensive network effort, could be transformed into a proper source.
    16  //
    17  // Wrapping these up as their own type achieves two goals:
    18  //
    19  // * Allows control over when deduction logic triggers network activity
    20  // * Makes it easy to attempt multiple URLs for a given import path
    21  type maybeSource interface {
    22  	try(ctx context.Context, cachedir string, c singleSourceCache, superv *supervisor) (source, sourceState, error)
    23  	getURL() string
    24  }
    25  
    26  type maybeSources []maybeSource
    27  
    28  func (mbs maybeSources) try(ctx context.Context, cachedir string, c singleSourceCache, superv *supervisor) (source, sourceState, error) {
    29  	var e sourceFailures
    30  	for _, mb := range mbs {
    31  		src, state, err := mb.try(ctx, cachedir, c, superv)
    32  		if err == nil {
    33  			return src, state, nil
    34  		}
    35  		e = append(e, sourceSetupFailure{
    36  			ident: mb.getURL(),
    37  			err:   err,
    38  		})
    39  	}
    40  	return nil, 0, e
    41  }
    42  
    43  // This really isn't generally intended to be used - the interface is for
    44  // maybeSources to be able to interrogate its members, not other things to
    45  // interrogate a maybeSources.
    46  func (mbs maybeSources) getURL() string {
    47  	strslice := make([]string, 0, len(mbs))
    48  	for _, mb := range mbs {
    49  		strslice = append(strslice, mb.getURL())
    50  	}
    51  	return strings.Join(strslice, "\n")
    52  }
    53  
    54  type sourceSetupFailure struct {
    55  	ident string
    56  	err   error
    57  }
    58  
    59  func (e sourceSetupFailure) Error() string {
    60  	return fmt.Sprintf("failed to set up %q, error %s", e.ident, e.err.Error())
    61  }
    62  
    63  type sourceFailures []sourceSetupFailure
    64  
    65  func (sf sourceFailures) Error() string {
    66  	var buf bytes.Buffer
    67  	fmt.Fprintf(&buf, "no valid source could be created:")
    68  	for _, e := range sf {
    69  		fmt.Fprintf(&buf, "\n\t%s", e.Error())
    70  	}
    71  
    72  	return buf.String()
    73  }
    74  
    75  type maybeGitSource struct {
    76  	url *url.URL
    77  }
    78  
    79  func (m maybeGitSource) try(ctx context.Context, cachedir string, c singleSourceCache, superv *supervisor) (source, sourceState, error) {
    80  	ustr := m.url.String()
    81  	path := filepath.Join(cachedir, "sources", sanitizer.Replace(ustr))
    82  
    83  	r, err := vcs.NewGitRepo(ustr, path)
    84  	if err != nil {
    85  		return nil, 0, unwrapVcsErr(err)
    86  	}
    87  
    88  	src := &gitSource{
    89  		baseVCSSource: baseVCSSource{
    90  			repo: &gitRepo{r},
    91  		},
    92  	}
    93  
    94  	// Pinging invokes the same action as calling listVersions, so just do that.
    95  	var vl []PairedVersion
    96  	err = superv.do(ctx, "git:lv:maybe", ctListVersions, func(ctx context.Context) (err error) {
    97  		if vl, err = src.listVersions(ctx); err != nil {
    98  			return fmt.Errorf("remote repository at %s does not exist, or is inaccessible", ustr)
    99  		}
   100  		return nil
   101  	})
   102  	if err != nil {
   103  		return nil, 0, err
   104  	}
   105  
   106  	c.storeVersionMap(vl, true)
   107  	state := sourceIsSetUp | sourceExistsUpstream | sourceHasLatestVersionList
   108  
   109  	if r.CheckLocal() {
   110  		state |= sourceExistsLocally
   111  	}
   112  
   113  	return src, state, nil
   114  }
   115  
   116  func (m maybeGitSource) getURL() string {
   117  	return m.url.String()
   118  }
   119  
   120  type maybeGopkginSource struct {
   121  	// the original gopkg.in import path. this is used to create the on-disk
   122  	// location to avoid duplicate resource management - e.g., if instances of
   123  	// a gopkg.in project are accessed via different schemes, or if the
   124  	// underlying github repository is accessed directly.
   125  	opath string
   126  	// the actual upstream URL - always github
   127  	url *url.URL
   128  	// the major version to apply for filtering
   129  	major uint64
   130  }
   131  
   132  func (m maybeGopkginSource) try(ctx context.Context, cachedir string, c singleSourceCache, superv *supervisor) (source, sourceState, error) {
   133  	// We don't actually need a fully consistent transform into the on-disk path
   134  	// - just something that's unique to the particular gopkg.in domain context.
   135  	// So, it's OK to just dumb-join the scheme with the path.
   136  	path := filepath.Join(cachedir, "sources", sanitizer.Replace(m.url.Scheme+"/"+m.opath))
   137  	ustr := m.url.String()
   138  
   139  	r, err := vcs.NewGitRepo(ustr, path)
   140  	if err != nil {
   141  		return nil, 0, unwrapVcsErr(err)
   142  	}
   143  
   144  	src := &gopkginSource{
   145  		gitSource: gitSource{
   146  			baseVCSSource: baseVCSSource{
   147  				repo: &gitRepo{r},
   148  			},
   149  		},
   150  		major: m.major,
   151  	}
   152  
   153  	var vl []PairedVersion
   154  	err = superv.do(ctx, "git:lv:maybe", ctListVersions, func(ctx context.Context) (err error) {
   155  		if vl, err = src.listVersions(ctx); err != nil {
   156  			return fmt.Errorf("remote repository at %s does not exist, or is inaccessible", ustr)
   157  		}
   158  		return nil
   159  	})
   160  	if err != nil {
   161  		return nil, 0, err
   162  	}
   163  
   164  	c.storeVersionMap(vl, true)
   165  	state := sourceIsSetUp | sourceExistsUpstream | sourceHasLatestVersionList
   166  
   167  	if r.CheckLocal() {
   168  		state |= sourceExistsLocally
   169  	}
   170  
   171  	return src, state, nil
   172  }
   173  
   174  func (m maybeGopkginSource) getURL() string {
   175  	return m.opath
   176  }
   177  
   178  type maybeBzrSource struct {
   179  	url *url.URL
   180  }
   181  
   182  func (m maybeBzrSource) try(ctx context.Context, cachedir string, c singleSourceCache, superv *supervisor) (source, sourceState, error) {
   183  	ustr := m.url.String()
   184  	path := filepath.Join(cachedir, "sources", sanitizer.Replace(ustr))
   185  
   186  	r, err := vcs.NewBzrRepo(ustr, path)
   187  	if err != nil {
   188  		return nil, 0, unwrapVcsErr(err)
   189  	}
   190  
   191  	err = superv.do(ctx, "bzr:ping", ctSourcePing, func(ctx context.Context) error {
   192  		if !r.Ping() {
   193  			return fmt.Errorf("remote repository at %s does not exist, or is inaccessible", ustr)
   194  		}
   195  		return nil
   196  	})
   197  	if err != nil {
   198  		return nil, 0, err
   199  	}
   200  
   201  	state := sourceIsSetUp | sourceExistsUpstream
   202  	if r.CheckLocal() {
   203  		state |= sourceExistsLocally
   204  	}
   205  
   206  	src := &bzrSource{
   207  		baseVCSSource: baseVCSSource{
   208  			repo: &bzrRepo{r},
   209  		},
   210  	}
   211  
   212  	return src, state, nil
   213  }
   214  
   215  func (m maybeBzrSource) getURL() string {
   216  	return m.url.String()
   217  }
   218  
   219  type maybeHgSource struct {
   220  	url *url.URL
   221  }
   222  
   223  func (m maybeHgSource) try(ctx context.Context, cachedir string, c singleSourceCache, superv *supervisor) (source, sourceState, error) {
   224  	ustr := m.url.String()
   225  	path := filepath.Join(cachedir, "sources", sanitizer.Replace(ustr))
   226  
   227  	r, err := vcs.NewHgRepo(ustr, path)
   228  	if err != nil {
   229  		return nil, 0, unwrapVcsErr(err)
   230  	}
   231  
   232  	err = superv.do(ctx, "hg:ping", ctSourcePing, func(ctx context.Context) error {
   233  		if !r.Ping() {
   234  			return fmt.Errorf("remote repository at %s does not exist, or is inaccessible", ustr)
   235  		}
   236  		return nil
   237  	})
   238  	if err != nil {
   239  		return nil, 0, err
   240  	}
   241  
   242  	state := sourceIsSetUp | sourceExistsUpstream
   243  	if r.CheckLocal() {
   244  		state |= sourceExistsLocally
   245  	}
   246  
   247  	src := &hgSource{
   248  		baseVCSSource: baseVCSSource{
   249  			repo: &hgRepo{r},
   250  		},
   251  	}
   252  
   253  	return src, state, nil
   254  }
   255  
   256  func (m maybeHgSource) getURL() string {
   257  	return m.url.String()
   258  }