-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add compatibility handshake between router and execution config (…
- Loading branch information
Showing
15 changed files
with
1,585 additions
and
1,337 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import fs from 'fs'; | ||
import { fileURLToPath } from 'node:url'; | ||
import path from 'node:path'; | ||
|
||
const __filename = fileURLToPath(import.meta.url); | ||
const __dirname = path.dirname(__filename); | ||
const compositionVersion = '{{$COMPOSITION__VERSION}}'; | ||
|
||
// From pnpm v10+, modules will explicitly need to set whether a hook is allowed to run. | ||
if (process.argv[1] === __filename) { | ||
const json = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json')).toString()); | ||
const version = json.version; | ||
const varFilePath = path.join(__dirname, '../dist/utils/composition-version.js'); | ||
let content = fs.readFileSync(varFilePath).toString(); | ||
if (content.indexOf(compositionVersion) < 0) { | ||
throw new Error(`"${compositionVersion}" string not found in dist/utils/composition-version.js.`); | ||
} | ||
content = content.replace(compositionVersion, version); | ||
fs.writeFileSync(varFilePath, content); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const COMPOSITION_VERSION = '{{$COMPOSITION__VERSION}}'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package execution_config | ||
|
||
import ( | ||
"fmt" | ||
"go.uber.org/zap" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
const ( | ||
// ExecutionConfigVersionThreshold should ONLY be updated if there is a breaking change in the router execution config. | ||
ExecutionConfigVersionThreshold = 1 | ||
compatibilityVersionParseErrorMessage = "Failed to parse compatibility version." | ||
executionConfigVersionParseErrorMessage = "Failed to parse router execution config version of compatibility version." | ||
) | ||
|
||
func IsRouterCompatibleWithExecutionConfig(logger *zap.Logger, compatibilityVersion string) bool { | ||
if compatibilityVersion == "" { | ||
return true | ||
} | ||
/* A compatibility version is composed thus: <router execution configuration version>:<composition package version> | ||
* A router version supports a maximum router execution configuration version (ExecutionConfigVersionThreshold). | ||
* In the event the execution config version exceeds ExecutionConfigVersionThreshold, an error will request for | ||
* the router version be upgraded. | ||
* If the router version requires a newer router execution configuration version, a warning will explain that some | ||
* new features may be unavailable or functionality/behaviour may have changed. | ||
*/ | ||
segments := strings.Split(compatibilityVersion, ":") | ||
if len(segments) != 2 { | ||
logger.Error(compatibilityVersionParseErrorMessage, zap.String("compatibility_version", compatibilityVersion)) | ||
return false | ||
} | ||
routerExecutionVersion, err := strconv.ParseInt(segments[0], 10, 32) | ||
if err != nil { | ||
logger.Error(executionConfigVersionParseErrorMessage, zap.String("compatibility_version", compatibilityVersion)) | ||
return false | ||
} | ||
switch { | ||
case routerExecutionVersion == ExecutionConfigVersionThreshold: | ||
return true | ||
case routerExecutionVersion > ExecutionConfigVersionThreshold: | ||
logger.Error( | ||
executionConfigVersionThresholdExceededError(routerExecutionVersion), | ||
zap.Int64("execution_config_version", routerExecutionVersion), | ||
zap.String("composition_package_version", segments[1]), | ||
) | ||
return false | ||
default: | ||
logger.Warn( | ||
executionConfigVersionInsufficientWarning(routerExecutionVersion), | ||
zap.Int64("execution_config_version", routerExecutionVersion), | ||
zap.String("composition_package_version", segments[1]), | ||
) | ||
return true | ||
} | ||
} | ||
|
||
func executionConfigVersionThresholdExceededError(executionConfigVersion int64) string { | ||
return fmt.Sprintf( | ||
"This router version supports a router execution config version up to %d. The router execution config version supplied is %d. Please upgrade your router version.", | ||
ExecutionConfigVersionThreshold, | ||
executionConfigVersion, | ||
) | ||
} | ||
|
||
func executionConfigVersionInsufficientWarning(executionConfigVersion int64) string { | ||
return fmt.Sprintf( | ||
"This router version requires a minimum router execution config version of %d to support all functionality. The router execution config version supplied is %d. Please create a new execution configuration.", | ||
ExecutionConfigVersionThreshold, | ||
executionConfigVersion, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package execution_config | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"github.com/stretchr/testify/assert" | ||
"go.uber.org/zap" | ||
"go.uber.org/zap/zapcore" | ||
"go.uber.org/zap/zaptest/observer" | ||
"testing" | ||
) | ||
|
||
func TestExecutionConfiguration(t *testing.T) { | ||
t.Run("no compatibility version is supported", func(t *testing.T) { | ||
observed, logs := observer.New(zapcore.DebugLevel) | ||
logger := newLogger(observed) | ||
assert.True(t, IsRouterCompatibleWithExecutionConfig(logger, "")) | ||
assert.Equal(t, 0, len(logs.All())) | ||
}) | ||
|
||
t.Run("same compatibility version is supported", func(t *testing.T) { | ||
observed, logs := observer.New(zapcore.DebugLevel) | ||
logger := newLogger(observed) | ||
assert.True(t, IsRouterCompatibleWithExecutionConfig(logger, fmt.Sprintf("%d:0.1.0", ExecutionConfigVersionThreshold))) | ||
assert.Equal(t, 0, len(logs.All())) | ||
}) | ||
|
||
t.Run("return an error if compatibility version is unparsable #1", func(t *testing.T) { | ||
observed, logs := observer.New(zapcore.DebugLevel) | ||
logger := newLogger(observed) | ||
compatibilityVersion := "nonsense" | ||
assert.False(t, IsRouterCompatibleWithExecutionConfig(logger, compatibilityVersion)) | ||
logsSlice := logs.All() | ||
assert.Equal(t, 1, len(logsSlice)) | ||
assert.Equal(t, compatibilityVersionParseErrorMessage, logsSlice[0].Message) | ||
assert.Equal(t, zapcore.ErrorLevel, logsSlice[0].Level) | ||
assert.Equal(t, 1, len(logsSlice[0].Context)) | ||
assert.Equal(t, zap.String("compatibility_version", compatibilityVersion), logsSlice[0].Context[0]) | ||
}) | ||
|
||
t.Run("return an error if compatibility version is unparsable #2", func(t *testing.T) { | ||
observed, logs := observer.New(zapcore.DebugLevel) | ||
logger := newLogger(observed) | ||
compatibilityVersion := "1:2:3" | ||
assert.False(t, IsRouterCompatibleWithExecutionConfig(logger, compatibilityVersion)) | ||
logsSlice := logs.All() | ||
assert.Equal(t, 1, len(logsSlice)) | ||
assert.Equal(t, compatibilityVersionParseErrorMessage, logsSlice[0].Message) | ||
assert.Equal(t, zapcore.ErrorLevel, logsSlice[0].Level) | ||
assert.Equal(t, 1, len(logsSlice[0].Context)) | ||
assert.Equal(t, zap.String("compatibility_version", compatibilityVersion), logsSlice[0].Context[0]) | ||
}) | ||
|
||
t.Run("return an error if execution config version is unparsable", func(t *testing.T) { | ||
observed, logs := observer.New(zapcore.DebugLevel) | ||
logger := newLogger(observed) | ||
compatibilityVersion := "a:0.1.0" | ||
assert.False(t, IsRouterCompatibleWithExecutionConfig(logger, compatibilityVersion)) | ||
logsSlice := logs.All() | ||
assert.Equal(t, 1, len(logsSlice)) | ||
assert.Equal(t, executionConfigVersionParseErrorMessage, logsSlice[0].Message) | ||
assert.Equal(t, zapcore.ErrorLevel, logsSlice[0].Level) | ||
assert.Equal(t, 1, len(logsSlice[0].Context)) | ||
assert.Equal(t, zap.String("compatibility_version", compatibilityVersion), logsSlice[0].Context[0]) | ||
}) | ||
|
||
t.Run("return an error if the maximum execution config version threshold of the router is exceeded", func(t *testing.T) { | ||
observed, logs := observer.New(zapcore.DebugLevel) | ||
logger := newLogger(observed) | ||
nextVersion := int64(ExecutionConfigVersionThreshold + 1) | ||
compVersion := "0.1.0" | ||
compatibilityVersion := fmt.Sprintf("%d:%s", nextVersion, compVersion) | ||
assert.False(t, IsRouterCompatibleWithExecutionConfig(logger, compatibilityVersion)) | ||
logsSlice := logs.All() | ||
assert.Equal(t, 1, len(logsSlice)) | ||
assert.Equal(t, executionConfigVersionThresholdExceededError(nextVersion), logsSlice[0].Message) | ||
assert.Equal(t, zapcore.ErrorLevel, logsSlice[0].Level) | ||
assert.Equal(t, 2, len(logsSlice[0].Context)) | ||
assert.Equal(t, zap.Int64("execution_config_version", nextVersion), logsSlice[0].Context[0]) | ||
assert.Equal(t, zap.String("composition_package_version", compVersion), logsSlice[0].Context[1]) | ||
}) | ||
|
||
t.Run("return a warning if the execution config version is insufficient", func(t *testing.T) { | ||
observed, logs := observer.New(zapcore.DebugLevel) | ||
logger := newLogger(observed) | ||
previousVersion := int64(ExecutionConfigVersionThreshold - 1) | ||
compVersion := "0.1.0" | ||
compatibilityVersion := fmt.Sprintf("%d:%s", previousVersion, compVersion) | ||
assert.True(t, IsRouterCompatibleWithExecutionConfig(logger, compatibilityVersion)) | ||
logsSlice := logs.All() | ||
assert.Equal(t, 1, len(logsSlice)) | ||
assert.Equal(t, executionConfigVersionInsufficientWarning(previousVersion), logsSlice[0].Message) | ||
assert.Equal(t, zapcore.WarnLevel, logsSlice[0].Level) | ||
assert.Equal(t, 2, len(logsSlice[0].Context)) | ||
assert.Equal(t, zap.Int64("execution_config_version", previousVersion), logsSlice[0].Context[0]) | ||
assert.Equal(t, zap.String("composition_package_version", compVersion), logsSlice[0].Context[1]) | ||
}) | ||
} | ||
|
||
func newLogger(observed zapcore.Core) *zap.Logger { | ||
var buffer bytes.Buffer | ||
return zap.New( | ||
zapcore.NewTee( | ||
zapcore.NewCore(zapcore.NewJSONEncoder(zapcore.EncoderConfig{}), zapcore.AddSync(&buffer), zapcore.DebugLevel), | ||
observed, | ||
), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters