Skip to content

Commit

Permalink
[2 of 6] introduce CtxDetector ("contextual detector")
Browse files Browse the repository at this point in the history
The existing Detector interface cannot be extended in a backward
compatible way to support new features desired for the GitDetector
implementation[0]. The new CtxDetector interface is introduced to allow
such extension without breaking backward compatibility with existing
users.

The new interface also avoids adding new methods to the existing
Detector interface that would then need to be supported by all Detector
implementations, both in-library and in the wild.

A CtxDetector is slightly more cumbersome to use than Detector. Callers
can (and should) continue to use Detector unless the enhanced
capabilities of one or more of the CtxDetector implementation modules is
needed. At the time of writing (2020-08), the only CtxDetector with such
extra mojo is the forthcoming GitCtxDetector.

Existing Detector implementations can easily be wrapped by CtxDetector
implementations. The information available to a CtxDetector impl. is a
strict superset of the information provided to a Detector. Where there
is no need for the additional context info provided by the CtxDetect
dispatch function, impls. can simply pass through the common subset to
the Detect method of the analogous Detector impl.

CAVEAT: In this changeset the list of ContextualDetectors is commented-
        out. This is intended to make a clear introduction of the
        interface type prior to introducing any implementations of it.

A forthcoming change will provide such wrapping for all in-tree Detector
impls, followed by the introduction of specialization for the
GitCtxDetector impl.

[0] C.f., hashicorp#268
  • Loading branch information
salewski committed Aug 31, 2020
1 parent 2f6c376 commit 59849b0
Showing 1 changed file with 168 additions and 0 deletions.
168 changes: 168 additions & 0 deletions detect_ctx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package getter

import (
"fmt"

"github.com/hashicorp/go-getter/helper/url"
)

// CtxDetector (read: "Contextual Detector"), the evil twin of Detector.
//
// Like its Detector sibling, CtxDetector defines an interface that an invalid
// URL or a URL with a blank scheme can be passed through in order to
// determine if its shorthand for something else well-known.
//
// CtxDetector expands on the capabilities of Detector in the following ways:
//
// * A CtxDetector allows the caller to provide more information about its
// invocation context to the CtxDetect dispatch function. This allows for
// some types of useful detections and transformations that were not
// previously possible.
//
// * The CtxDetect dispatch function provides the CtxDetector
// implementations with all of the context information it has available
// to it, including the force token flag (e.g., "git::"). This allows the
// implementations to safely take (or avoid taking) actions that would be
// unsafe otherwise.
//
// A CtxDetector is slightly more cumbersome to use than Detector. Callers can
// (and should) continue to use Detector unless the enhanced capabilities of
// one or more of the CtxDetector implementation is needed. At the time of
// writing (2020-08), the only CtxDetector with such extra mojo is
// GitCtxDetector (q.v.).
//
type CtxDetector interface {

// CtxDetect will detect whether the string matches a known pattern to
// turn it into a proper URL
//
// 'src' (required) is the input string to be interpretted. In the common
// case this value will have been preparsed by the CtxDetect dispatch
// function; its forcing token (if any) will have been removed; same for
// any 'go-getter' subdir portion('//some/subdir'). Some examples:
//
// "s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234"
//
// "github.com/hashicorp/foo.git"
//
// "[email protected]:hashicorp/foo.git?foo=bar"
//
// "../../git-submods/tf-mods/some-tf-module?ref=v1.2.3"
//
// 'pwd' (optional, sometimes) is the filepath that should be taken as the
// current working directory (mainly for the purpose of resolving
// filesystem paths; may be overridden for that purpose by
// 'srcResolveFrom'). Some CtxDetector implementation may require this
// path to be an abosolute filepath.
//
// 'forceToken' (optional) is the forcing token, if any, extracted from
// the input string submitted to the CtxDetect dispatch function. It is
// provided as a param to the CtxDetect method so that CtxDetector
// implementations may recognize 'src' strings intended for them. This
// removes ambiguity when a given 'src' value could be legitimately
// processed by more than one CtxDetector implementation.
//
// 'ctxSubDir' (optional) is the 'go-getter' subdir portion (if any)
// pre-extracted from the source string (as noted above). It is provided
// to the CtxDetector implementation only for contextual awareness, which
// conceivably could inform its decision-making process. It should not be
// incorporated into the result returned by the CtxDetector impl.
//
// 'srcResolveFrom' (optional, sometimes) A caller-provided filepath to be
// used as the directory from which any relative filepath in 'src' should
// be resolved, instead of relative to 'pwd'. An individual CtxDetector
// implementation may require that this value be absolute.
//
// Protocol: Where they need to be resolved, relative filepath values in
// 'src' will be resolved relative to 'pwd', unless
// 'srcResolveFrom' is non-empty; then they will be resolved
// relative to 'srcResolveFrom'.
//
// Note that some CtxDetector impls. (FileCtxDetector,
// GitCtxDetector) can only produce meaningful results in some
// circumstances if they have an absolute directory to resolve
// to. For best results, when 'srcResolveFrom' is non-empty,
// provide an absolute filepath.
//
// The CtxDetect interface itself does not require that either
// 'pwd' or 'srcResolveFrom' be absolute filepaths, but that
// might be required by a particular CtxDetector implementation.
// Know that RFC-compliant use of 'file://' URIs (which some
// CtxDetector impls. emit) permit only absolute filepaths, and
// tools (such as Git) expect this. Providing relative filepaths
// for 'pwd' and/or 'srcResolveFrom' may result in the
// generation of non-legit 'file://' URIs with relative paths in
// them, and a CtxDetector implementation is permitted to reject
// them with an error if it requires an absolute path.
//
CtxDetect(src, pwd, forceToken, ctxSubDir, srcResolveFrom string) (string, bool, error)
}

// ContextualDetectors is the list of detectors that are tried on an invalid URL.
// This is also the order they're tried (index 0 is first).
var ContextualDetectors []CtxDetector

func init() {
ContextualDetectors = []CtxDetector{
// new(GitHubCtxDetector),
// new(GitCtxDetector),
// new(BitBucketCtxDetector),
// new(S3CtxDetector),
// new(GCSCtxDetector),
// new(FileCtxDetector),
}
}

// CtxDetect turns a source string into another source string if it is
// detected to be of a known pattern.
//
// An empty-string value provided for 'pwd' is interpretted as "not
// provided". Likewise for 'srcResolveFrom'.
//
// The (optional) 'srcResolveFrom' parameter allows the caller to provide a
// directory from which any relative filepath in 'src' should be resolved,
// instead of relative to 'pwd'. This supports those use cases (e.g.,
// Terraform modules with relative 'source' filepaths) where the caller
// context for path resolution may be different than the pwd. For best result,
// the provided value should be an absolute filepath. If unneeded, use specify
// the empty string.
//
// The 'cds' []CtxDetector parameter should be the list of detectors to use in
// the order to try them. If you don't want to configure this, just use the
// global ContextualDetectors variable.
//
// This is safe to be called with an already valid source string: CtxDetect
// will just return it.
//
func CtxDetect(src, pwd, srcResolveFrom string, cds []CtxDetector) (string, error) {

getForce, getSrc := getForcedGetter(src)

// Separate out the subdir if there is one, we don't pass that to detect
getSrc, subDir := SourceDirSubdir(getSrc)

u, err := url.Parse(getSrc)
if err == nil && u.Scheme != "" {
// Valid URL
return src, nil
}

for _, d := range cds {
result, ok, err := d.CtxDetect(getSrc, pwd, getForce, subDir, srcResolveFrom)
if err != nil {
return "", err
}
if !ok {
continue
}

result, err = handleDetected(result, getForce, subDir)
if err != nil {
return "", err
}

return result, nil
}

return "", fmt.Errorf("invalid source string: %s", src)
}

0 comments on commit 59849b0

Please sign in to comment.