mirror of
https://github.com/imjasonh/terraform-playground
synced 2026-07-07 23:35:16 +00:00
lock: accept name[extras]==version; error on dependency-bearing options (-e/-r/-c)
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
This commit is contained in:
parent
5830434434
commit
9a7dc02643
2 changed files with 45 additions and 5 deletions
|
|
@ -28,11 +28,17 @@ type Requirement struct {
|
|||
}
|
||||
|
||||
var (
|
||||
// pinRE matches "name==version" optionally followed by extras/markers we
|
||||
// ignore for matching purposes.
|
||||
pinRE = regexp.MustCompile(`^\s*([A-Za-z0-9][A-Za-z0-9._-]*)\s*==\s*([^\s;]+)`)
|
||||
// pinRE matches "name[extras]==version". Extras are accepted (and ignored
|
||||
// for matching, since a resolved lock pins their dependencies separately);
|
||||
// environment markers after ";" are also ignored.
|
||||
pinRE = regexp.MustCompile(`^\s*([A-Za-z0-9][A-Za-z0-9._-]*)\s*(?:\[[^\]]*\])?\s*==\s*([^\s;]+)`)
|
||||
hashRE = regexp.MustCompile(`--hash=sha256:([0-9a-fA-F]{64})`)
|
||||
normRE = regexp.MustCompile(`[-_.]+`)
|
||||
|
||||
// dependencyOptions reference further requirements rather than configuring
|
||||
// the resolver. Silently skipping them could omit dependencies, so we
|
||||
// reject them: a lock fed to pymage must be fully inlined and pinned.
|
||||
dependencyOptions = []string{"-e", "--editable", "-r", "--requirement", "-c", "--constraint"}
|
||||
)
|
||||
|
||||
// NormalizeName implements PEP 503 name normalization.
|
||||
|
|
@ -66,9 +72,11 @@ func Parse(r io.Reader) ([]Requirement, error) {
|
|||
if strings.TrimSpace(stripped) == "" {
|
||||
return nil
|
||||
}
|
||||
// Skip pip options like -r/-c/--index-url that aren't pins.
|
||||
// Configuration options (e.g. --index-url) aren't pins and are skipped,
|
||||
// but options that pull in other requirements must not be silently
|
||||
// ignored.
|
||||
if isOption(stripped) {
|
||||
return nil
|
||||
return optionError(stripped)
|
||||
}
|
||||
m := pinRE.FindStringSubmatch(stripped)
|
||||
if m == nil {
|
||||
|
|
@ -127,6 +135,18 @@ func isOption(s string) bool {
|
|||
return strings.HasPrefix(t, "-")
|
||||
}
|
||||
|
||||
// optionError returns an error for dependency-bearing options (which we cannot
|
||||
// honor from a local wheelhouse), and nil for benign configuration options.
|
||||
func optionError(s string) error {
|
||||
t := strings.TrimSpace(s)
|
||||
for _, opt := range dependencyOptions {
|
||||
if t == opt || strings.HasPrefix(t, opt+" ") || strings.HasPrefix(t, opt+"=") {
|
||||
return fmt.Errorf("lock: unsupported option %q; referenced requirements must be inlined and pinned", t)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate ensures every requirement is pinned and (optionally) hashed.
|
||||
func Validate(reqs []Requirement, requireHashes bool) error {
|
||||
for _, r := range reqs {
|
||||
|
|
|
|||
|
|
@ -65,6 +65,10 @@ func TestParseRejectsUnsupportedLines(t *testing.T) {
|
|||
"requests>=2.0", // not pinned with ==
|
||||
"mypkg @ https://example.com/x.whl", // direct URL reference
|
||||
"justaname", // bare name
|
||||
"-e ./local", // editable reference
|
||||
"--editable=./local", // editable reference
|
||||
"-r other.txt", // nested requirements
|
||||
"-c constraints.txt", // nested constraints
|
||||
} {
|
||||
if _, err := Parse(strings.NewReader(in)); err == nil {
|
||||
t.Errorf("expected error for unsupported line %q", in)
|
||||
|
|
@ -72,6 +76,22 @@ func TestParseRejectsUnsupportedLines(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestParseAllowsExtrasAndBenignOptions(t *testing.T) {
|
||||
in := `--index-url https://pypi.org/simple
|
||||
requests[socks,security]==2.31.0 --hash=sha256:` + strings.Repeat("a", 64) + `
|
||||
`
|
||||
reqs, err := Parse(strings.NewReader(in))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(reqs) != 1 || reqs[0].Name != "requests" || reqs[0].Version != "2.31.0" {
|
||||
t.Fatalf("got %+v", reqs)
|
||||
}
|
||||
if len(reqs[0].Hashes) != 1 {
|
||||
t.Errorf("expected the hash to be parsed, got %v", reqs[0].Hashes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeName(t *testing.T) {
|
||||
for _, tc := range []struct{ in, want string }{
|
||||
{"Flask", "flask"},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue