Skip to content

Commit

Permalink
Add more logic crate update messaging to cover edge cases.
Browse files Browse the repository at this point in the history
  • Loading branch information
phamann committed May 12, 2020
1 parent 58ed8bd commit 2f4dd6c
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 72 deletions.
12 changes: 8 additions & 4 deletions pkg/compute/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type BuildCommand struct {
client api.HTTPClient
name string
lang string
force bool
}

// NewBuildCommand returns a usable command registered under the parent.
Expand All @@ -51,6 +52,7 @@ func NewBuildCommand(parent common.Registerer, client api.HTTPClient, globals *c
c.CmdClause = parent.Command("build", "Build a Compute@Edge package locally")
c.CmdClause.Flag("name", "Package name").StringVar(&c.name)
c.CmdClause.Flag("language", "Language type").StringVar(&c.lang)
c.CmdClause.Flag("force", "Force build and skip verification steps").BoolVar(&c.force)
return &c
}

Expand Down Expand Up @@ -109,11 +111,13 @@ func (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) {
return fmt.Errorf("unsupported language %s", lang)
}

progress.Step(fmt.Sprintf("Verifying local %s toolchain...", lang))
if !c.force {
progress.Step(fmt.Sprintf("Verifying local %s toolchain...", lang))

err = toolchain.Verify(progress)
if err != nil {
return err
err = toolchain.Verify(progress)
if err != nil {
return err
}
}

progress.Step(fmt.Sprintf("Building package using %s toolchain...", lang))
Expand Down
115 changes: 47 additions & 68 deletions pkg/compute/rust.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (m *CargoManifest) Read(filename string) error {
return err
}

// CargoLock models the package configuration properties of a Rust Cargo
// CargoLock models the package confuiguration properties of a Rust Cargo
// lock file which we are interested in and are read from the Cargo.lock file
// within the $PWD of the package.
type CargoLock struct {
Expand Down Expand Up @@ -187,31 +187,69 @@ func (r Rust) Verify(out io.Writer) error {
return fmt.Errorf("error reading Cargo.lock file: %w", err)
}

// Fetch the latest crate version from cargo.io API.
l, err := GetLatestCrateVersion(r.client, "fastly")
if err != nil {
return err
}
latest, err := semver.Parse(l)
if err != nil {
return err
}

// Find the crate version declared in Cargo.lock lockfile.
var crate CargoPackage
for _, p := range lock.Package {
if p.Name == "fastly" {
crate = p
break
}
}

// If crate not found in lockfile, error with dual remediation steps.
if crate.Name == "" {
return errors.RemediationError{
Inner: fmt.Errorf("fastly crate not found"),
Remediation: fmt.Sprintf(
"To fix this error, edit %s with:\n\n\t %s\n\nAnd then run the following command:\n\n\t$ %s\n",
text.Bold("Cargo.toml"),
text.Bold(fmt.Sprintf(`fastly = "^%s"`, latest)),
text.Bold("cargo update -p fastly"),
),
}
}

// Parse lockfile version to semver.
version, err := semver.Parse(crate.Version)
if err != nil {
return fmt.Errorf("error parsing Cargo.lock file: %w", err)
}

l, err := GetLatestCrateVersion(r.client, "fastly")
// Parse the lowest minor semver for the latest release.
// I.e. v0.3.2 becomes v0.3.0.
latestMinor, err := semver.Parse(fmt.Sprintf("%d.%d.0", latest.Major, latest.Minor))
if err != nil {
return err
return fmt.Errorf("error parsing Cargo.lock file: %w", err)
}
latest, err := semver.Parse(l)
if err != nil {
return err

// If the lockfile version is within the minor range but lower than the
// latest, error with remediation to run `cargo update`.
if version.GTE(latestMinor) && version.LT(latest) {
return errors.RemediationError{
Inner: fmt.Errorf("fastly crate not up-to-date"),
Remediation: fmt.Sprintf("To fix this error, run the following command:\n\n\t$ %s\n", text.Bold("cargo update -p fastly")),
}
}

// If version is on a lower minor or major, error with a dual remediation.
if version.LT(latest) {
return errors.RemediationError{
Inner: fmt.Errorf("fastly crate not up-to-date"),
Remediation: fmt.Sprintf("To fix this error, edit %s with:\n\n\t %s\n", text.Bold("Cargo.toml"), text.Bold(fmt.Sprintf(`fastly = "^%s"`, latest))),
Inner: fmt.Errorf("fastly crate not up-to-date"),
Remediation: fmt.Sprintf(
"To fix this error, edit %s with:\n\n\t %s\n\nAnd then run the following command:\n\n\t$ %s\n",
text.Bold("Cargo.toml"),
text.Bold(fmt.Sprintf(`fastly = "^%s"`, latest)),
text.Bold("cargo update -p fastly"),
),
}
}

Expand Down Expand Up @@ -331,62 +369,3 @@ func (r Rust) Build(out io.Writer, verbose bool) error {

return nil
}

// CargoCrateVersion models a Cargo crate version returned by the crates.io API.
type CargoCrateVersion struct {
Version string `json:"num"`
}

// CargoCrateVersions models a Cargo crate version returned by the crates.io API.
type CargoCrateVersions struct {
Versions []CargoCrateVersion `json:"versions"`
}

// GetLatestCrateVersion fetches all versions of a given Rust crate from the
// crates.io HTTP API and returns the latest valid semver version.
func GetLatestCrateVersion(client api.HTTPClient, name string) (string, error) {
url := fmt.Sprintf("https://crates.io/api/v1/crates/%s/versions", name)

req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
resp, err := client.Do(req)
if err != nil {
return "", err
}

if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("error fetching latest crate version %s", resp.Status)
}

defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}

crate := CargoCrateVersions{}
err = json.Unmarshal(body, &crate)
if err != nil {
return "", err
}

var versions []semver.Version
for _, v := range crate.Versions {
if version, err := semver.Parse(v.Version); err == nil {
versions = append(versions, version)
}
}

if len(versions) < 1 {
return "", fmt.Errorf("no valid crate versions found")
}

semver.Sort(versions)

latest := versions[len(versions)-1]

return latest.String(), nil
}

0 comments on commit 2f4dd6c

Please sign in to comment.