Skip to content

Commit

Permalink
Improved deployer version mismatch error.
Browse files Browse the repository at this point in the history
Recall that when a deployer deploys a binary, it first extracts the
deployer API version from the binary and checks that its deployer API
version is compatible. Before this PR, the resulting error message
looked like this:

```
version mismatch: deployer's deployer API version v0.17.0 is
incompatible with app' deployer API version v0.14.0
```

Now, it looks like this:

```
ERROR: The binary you're trying to deploy ("collatz") was built with
github.com/ServiceWeaver/weaver module version v0.17.0 (deployer API
version v0.14.0). However, the 'weaver multi' binary you're using was built
with weaver module version v0.17.0 (deployer API version v0.15.0). These
versions are incompatible.

We recommend updating both the weaver module your application is built with and
updating the 'weaver multi' command by running the following.

    go get github.com/ServiceWeaver/weaver@latest
    go install github.com/ServiceWeaver/weaver/cmd/weaver@latest

Then, re-build your code and re-run 'weaver multi deploy'. If the problem
persists, please file an issue at https://github.com/ServiceWeaver/weaver/issues.
```

This PR also makes the multi and ssh deployers check these versions
earlier, before trying to deploy anything.

See #431 for a similar PR.
  • Loading branch information
mwhittaker committed Jun 29, 2023
1 parent 1e58cb8 commit f82cb58
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 41 deletions.
3 changes: 3 additions & 0 deletions godeps.txt
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,7 @@ github.com/ServiceWeaver/weaver/internal/tool/multi
github.com/ServiceWeaver/weaver/runtime/protos
github.com/ServiceWeaver/weaver/runtime/retry
github.com/ServiceWeaver/weaver/runtime/tool
github.com/ServiceWeaver/weaver/runtime/version
github.com/google/uuid
go.opentelemetry.io/otel/sdk/trace
golang.org/x/exp/maps
Expand Down Expand Up @@ -661,11 +662,13 @@ github.com/ServiceWeaver/weaver/internal/tool/ssh
github.com/ServiceWeaver/weaver/internal/tool/config
github.com/ServiceWeaver/weaver/internal/tool/ssh/impl
github.com/ServiceWeaver/weaver/runtime
github.com/ServiceWeaver/weaver/runtime/bin
github.com/ServiceWeaver/weaver/runtime/codegen
github.com/ServiceWeaver/weaver/runtime/colors
github.com/ServiceWeaver/weaver/runtime/logging
github.com/ServiceWeaver/weaver/runtime/protos
github.com/ServiceWeaver/weaver/runtime/tool
github.com/ServiceWeaver/weaver/runtime/version
github.com/google/uuid
golang.org/x/exp/maps
io
Expand Down
34 changes: 34 additions & 0 deletions internal/tool/multi/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,19 @@ import (
"net/http"
"os"
"os/signal"
"path/filepath"
"syscall"

"github.com/ServiceWeaver/weaver/internal/status"
"github.com/ServiceWeaver/weaver/internal/tool/config"
"github.com/ServiceWeaver/weaver/runtime"
"github.com/ServiceWeaver/weaver/runtime/bin"
"github.com/ServiceWeaver/weaver/runtime/codegen"
"github.com/ServiceWeaver/weaver/runtime/colors"
"github.com/ServiceWeaver/weaver/runtime/logging"
"github.com/ServiceWeaver/weaver/runtime/retry"
"github.com/ServiceWeaver/weaver/runtime/tool"
"github.com/ServiceWeaver/weaver/runtime/version"
"github.com/google/uuid"
)

Expand Down Expand Up @@ -84,6 +87,37 @@ func deploy(ctx context.Context, args []string) error {
}
multiConfig.App = appConfig

// Check version compatibility.
versions, err := bin.ReadVersions(appConfig.Binary)
if err != nil {
return fmt.Errorf("read versions: %w", err)
}
if versions.DeployerVersion != version.DeployerVersion {
// Try to relativize the binary, defaulting to the absolute path if
// there are any errors..
binary := appConfig.Binary
if cwd, err := os.Getwd(); err == nil {
if rel, err := filepath.Rel(cwd, appConfig.Binary); err == nil {
binary = rel
}
}
return fmt.Errorf(`ERROR: The binary you're trying to deploy (%q) was built with
github.com/ServiceWeaver/weaver module version %s (deployer API
version %s). However, the 'weaver multi' binary you're using was built
with weaver module version %s (deployer API version %s). These
versions are incompatible.
We recommend updating both the weaver module your application is built with and
updating the 'weaver multi' command by running the following.
go get github.com/ServiceWeaver/weaver@latest
go install github.com/ServiceWeaver/weaver/cmd/weaver@latest
Then, re-build your code and re-run 'weaver multi deploy'. If the problem
persists, please file an issue at https://github.com/ServiceWeaver/weaver/issues.`,
binary, versions.ModuleVersion, versions.DeployerVersion, version.ModuleVersion, version.DeployerVersion)
}

// Create the deployer.
deploymentId := uuid.New().String()
d, err := newDeployer(ctx, deploymentId, multiConfig)
Expand Down
33 changes: 33 additions & 0 deletions internal/tool/ssh/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ import (
"github.com/ServiceWeaver/weaver/internal/tool/config"
"github.com/ServiceWeaver/weaver/internal/tool/ssh/impl"
"github.com/ServiceWeaver/weaver/runtime"
"github.com/ServiceWeaver/weaver/runtime/bin"
"github.com/ServiceWeaver/weaver/runtime/codegen"
"github.com/ServiceWeaver/weaver/runtime/colors"
"github.com/ServiceWeaver/weaver/runtime/logging"
"github.com/ServiceWeaver/weaver/runtime/protos"
"github.com/ServiceWeaver/weaver/runtime/tool"
"github.com/ServiceWeaver/weaver/runtime/version"
)

const (
Expand Down Expand Up @@ -92,6 +94,37 @@ func deploy(ctx context.Context, args []string) error {
App: app,
}

// Check version compatibility.
versions, err := bin.ReadVersions(app.Binary)
if err != nil {
return fmt.Errorf("read versions: %w", err)
}
if versions.DeployerVersion != version.DeployerVersion {
// Try to relativize the binary, defaulting to the absolute path if
// there are any errors..
binary := app.Binary
if cwd, err := os.Getwd(); err == nil {
if rel, err := filepath.Rel(cwd, app.Binary); err == nil {
binary = rel
}
}
return fmt.Errorf(`ERROR: The binary you're trying to deploy (%q) was built with
github.com/ServiceWeaver/weaver module version %s (deployer API
version %s). However, the 'weaver ssh' binary you're using was built
with weaver module version %s (deployer API version %s). These
versions are incompatible.
We recommend updating both the weaver module your application is built with and
updating the 'weaver ssh' command by running the following.
go get github.com/ServiceWeaver/weaver@latest
go install github.com/ServiceWeaver/weaver/cmd/weaver@latest
Then, re-build your code and re-run 'weaver ssh deploy'. If the problem
persists, please file an issue at https://github.com/ServiceWeaver/weaver/issues.`,
binary, versions.ModuleVersion, versions.DeployerVersion, version.ModuleVersion, version.DeployerVersion)
}

// Retrieve the list of locations to deploy.
locs, err := getLocations(config)
if err != nil {
Expand Down
72 changes: 42 additions & 30 deletions runtime/bin/bin.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@ import (
"github.com/ServiceWeaver/weaver/runtime/version"
)

// deployerVersion exists to embed the deployer API version into a Service
// Weaver binary. We split declaring and assigning version to prevent the
// compiler from erasing it.
// versionData exists to embed the weaver module version and deployer API
// version into a Service Weaver binary. We split declaring and assigning
// versionData to prevent the compiler from erasing it.
//
//nolint:unused
var deployerVersion string
var versionData string

func init() {
// NOTE that deployerVersion must be assigned a string constant that
// reflects the values of version.DeployerMajor and version.DeployerMinor.
// If the string is not a constant---if we try to use fmt.Sprintf, for
// NOTE that versionData must be assigned a string constant that reflects
// the values of version.ModuleVersion and version.DeployerVersion. If the
// string is not a constant---if we try to use fmt.Sprintf, for
// example---it will not be embedded in a Service Weaver binary.
deployerVersion = "⟦wEaVeRdEpLoYeRvErSiOn:v0.14.0⟧"
versionData = "⟦wEaVeRvErSiOn:module=v0.17.0;deployer=v0.14.0⟧"
}

// rodata returns the read-only data section of the provided binary.
Expand Down Expand Up @@ -112,37 +112,49 @@ func ReadListeners(file string) ([]codegen.ComponentListeners, error) {
return codegen.ExtractListeners(data), nil
}

// ReadDeployerVersion reads the deployer API version from the specified binary.
func ReadDeployerVersion(filename string) (version.SemVer, error) {
type Versions struct {
ModuleVersion version.SemVer // see version.ModuleVersion
DeployerVersion version.SemVer // see version.DeployerVersion
}

// ReadVersions reads the module version and deployer API version from the
// specified binary.
func ReadVersions(filename string) (Versions, error) {
data, err := rodata(filename)
if err != nil {
return version.SemVer{}, err
return Versions{}, err
}
return extractDeployerVersion(data)
return extractVersions(data)
}

// extractDeployerVersion returns the deployer API version embedded in data.
func extractDeployerVersion(data []byte) (version.SemVer, error) {
re := regexp.MustCompile(`⟦wEaVeRdEpLoYeRvErSiOn:v([0-9]*?)\.([0-9]*?)\.([0-9]*?)⟧`)
// extractVersions returns the module version and deployer API version embedded
// in data.
func extractVersions(data []byte) (Versions, error) {
re := regexp.MustCompile(`⟦wEaVeRvErSiOn:module=v([0-9]*?)\.([0-9]*?)\.([0-9]*?);deployer=v([0-9]*?)\.([0-9]*?)\.([0-9]*?)⟧`)
m := re.FindSubmatch(data)
if m == nil {
return version.SemVer{}, fmt.Errorf("embedded deployer API version not found")
return Versions{}, fmt.Errorf("embedded versions not found")
}
major, minor, patch := string(m[1]), string(m[2]), string(m[3])

v := version.SemVer{}
var err error
v.Major, err = strconv.Atoi(major)
if err != nil {
return version.SemVer{}, fmt.Errorf("invalid embedded deployer API major %q: %w", major, err)
}
v.Minor, err = strconv.Atoi(minor)
if err != nil {
return version.SemVer{}, fmt.Errorf("invalid embedded deployer API minor %q: %w", minor, err)
}
v.Patch, err = strconv.Atoi(patch)
if err != nil {
return version.SemVer{}, fmt.Errorf("invalid embedded deployer API patch %q: %w", patch, err)
v := Versions{}
for _, segment := range []struct {
name string
data []byte
dst *int
}{
{"module major version", m[1], &v.ModuleVersion.Major},
{"module minor version", m[2], &v.ModuleVersion.Minor},
{"module patch version", m[3], &v.ModuleVersion.Patch},
{"deployer major version", m[4], &v.DeployerVersion.Major},
{"deployer minor version", m[5], &v.DeployerVersion.Minor},
{"deployer patch version", m[6], &v.DeployerVersion.Patch},
} {
s := string(segment.data)
x, err := strconv.Atoi(s)
if err != nil {
return Versions{}, fmt.Errorf("invalid embedded %s %q: %w", segment.name, s, err)
}
*segment.dst = x
}
return v, nil
}
31 changes: 20 additions & 11 deletions runtime/bin/bin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,26 @@ func TestReadListeners(t *testing.T) {
}
}

func TestExtractDeployerVersion(t *testing.T) {
for _, want := range []version.SemVer{
{Major: 0, Minor: 0, Patch: 0},
{Major: 10, Minor: 10, Patch: 10},
{Major: 123, Minor: 4567, Patch: 891011},
func TestExtractVersion(t *testing.T) {
for _, want := range []Versions{
{
ModuleVersion: version.SemVer{Major: 1, Minor: 2, Patch: 3},
DeployerVersion: version.SemVer{Major: 4, Minor: 5, Patch: 6},
},
{
ModuleVersion: version.SemVer{Major: 100, Minor: 100, Patch: 234},
DeployerVersion: version.SemVer{Major: 0, Minor: 567, Patch: 8910},
},
} {
t.Run(want.String(), func(t *testing.T) {
name := fmt.Sprintf("%s-%s", want.ModuleVersion, want.DeployerVersion)
t.Run(name, func(t *testing.T) {
// Embed the version string inside a big array of bytes.
var bytes [10000]byte
embedded := fmt.Sprintf("⟦wEaVeRdEpLoYeRvErSiOn:%s⟧", want)
embedded := fmt.Sprintf("⟦wEaVeRvErSiOn:module=%s;deployer=%s⟧", want.ModuleVersion, want.DeployerVersion)
copy(bytes[1234:], []byte(embedded))

// Extract the version string.
got, err := extractDeployerVersion(bytes[:])
got, err := extractVersions(bytes[:])
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -162,12 +168,15 @@ func TestReadVersion(t *testing.T) {
}

// Read version.
got, err := ReadDeployerVersion(binary)
got, err := ReadVersions(binary)
if err != nil {
t.Fatal(err)
}
if got != version.DeployerVersion {
t.Fatalf("bad dipe version: got %s, want %s", got, version.DeployerVersion)
if got.ModuleVersion != version.ModuleVersion {
t.Fatalf("bad module version: got %s, want %s", got.ModuleVersion, version.ModuleVersion)
}
if got.DeployerVersion != version.DeployerVersion {
t.Fatalf("bad deployer version: got %s, want %s", got.DeployerVersion, version.DeployerVersion)
}
})
}
Expand Down

0 comments on commit f82cb58

Please sign in to comment.