Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#142: Added validation for configuration when creating a new controller #143

Merged
merged 10 commits into from
Sep 13, 2023
25 changes: 19 additions & 6 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,33 @@ func main() {
if openAPIOutputPath != nil && *openAPIOutputPath != "" {
err := generateOpenAPISpec(*openAPIOutputPath)
if err != nil {
panic(fmt.Sprintf("failed to generate OpenAPI to %q: %v", *openAPIOutputPath, err))
fmt.Printf("failed to generate OpenAPI to %q: %v\n", *openAPIOutputPath, err)
os.Exit(1)
}
} else {
startServer(*extensionRegistryURL, *serverAddress, *addCauseToInternalServerError)
err := startServer(*extensionRegistryURL, *serverAddress, *addCauseToInternalServerError)
if err != nil {
fmt.Printf("failed to start server: %v\n", err)
os.Exit(1)
}
}
}

func startServer(pathToExtensionFolder string, serverAddress string, addCauseToInternalServerError bool) {
func startServer(pathToExtensionFolder string, serverAddress string, addCauseToInternalServerError bool) error {
if pathToExtensionFolder == "" {
return fmt.Errorf("please specify extension registry with parameter '-extensionRegistryURL'")
}
log.Printf("Starting extension manager with extension folder %q", pathToExtensionFolder)
controller := extensionController.CreateWithConfig(extensionController.ExtensionManagerConfig{
controller, err := extensionController.CreateWithValidatedConfig(extensionController.ExtensionManagerConfig{
ExtensionRegistryURL: pathToExtensionFolder,
ExtensionSchema: restAPI.EXTENSION_SCHEMA_NAME,
BucketFSBasePath: "/buckets/bfsdefault/default/"})
if err != nil {
return err
}
restApi := restAPI.Create(controller, serverAddress, addCauseToInternalServerError)
restApi.Serve()
return nil
}

func generateOpenAPISpec(filename string) error {
Expand All @@ -61,7 +73,7 @@ func writeFile(filename string, content []byte) error {
if err != nil {
return err
}
fmt.Printf("Wrote OpenAPI spec to %s", filename)
fmt.Printf("Wrote OpenAPI spec to %s\n", filename)
return nil
}

Expand All @@ -70,7 +82,8 @@ func generateOpenAPIJson() ([]byte, error) {
if err != nil {
return nil, err
}
err = restAPI.AddPublicEndpoints(api, extensionController.ExtensionManagerConfig{})
dummyConfiguration := extensionController.ExtensionManagerConfig{ExtensionRegistryURL: "dummy", BucketFSBasePath: "dummy", ExtensionSchema: "dummy"}
err = restAPI.AddPublicEndpoints(api, dummyConfiguration)
if err != nil {
return nil, err
}
Expand Down
3 changes: 3 additions & 0 deletions doc/changes/changes_0.5.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ Code name:

This release updates the upload process for the extension registry to verify that the extension URLs are valid. It also adds design, requirements and user guide for the integration testing framework.

The release deprecates function `extensionController.CreateWithConfig()` in favor of `extensionController.CreateWithValidatedConfig()`. This new function validates the given configuration and returns an error in case it finds an issue.

## Features

* #129: Added verification for extension URLs before uploading to registry
* #142: Added validation of configuration when creating a new controller
* #130: Added verification that no instance exists before uninstalling an extension

## Documentation
Expand Down
37 changes: 27 additions & 10 deletions doc/developer_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ To use a local, non-published version of the extension interface for testing EM

1. Build `extension-manager-interface` by running `npm run build`. This is required after each change.
2. Edit [pkg/integrationTesting/extensionForTesting/package.json](./../pkg/integrationTesting/extensionForTesting/package.json) and replace the version of `"@exasol/extension-manager-interface"` with the path to your local clone of [extension-manager-interface](https://github.com/exasol/extension-manager-interface).
3. Edit [pkg/integrationTesting/extensionForTesting/extensionForTestingTemplate.ts](./../pkg/integrationTesting/extensionForTesting/extensionForTestingTemplate.ts) and adapt it to the new API if necessary.
3. Edit [pkg/integrationTesting/extensionForTesting/extensionForTestingTemplate.js](./../pkg/integrationTesting/extensionForTesting/extensionForTestingTemplate.js) and adapt it to the new API if necessary.
kaklakariada marked this conversation as resolved.
Show resolved Hide resolved

**Note:** The file contains placeholders that will be replaced during tests. It is not valid TypeScript, so it's normal that the editor complains about the invalid syntax.
**Note:** The file contains placeholders that will be replaced during tests. It is not valid JavaScript, so it's normal that the editor complains about the invalid syntax.

Make sure to not commit the modified `package.json`.

Expand Down Expand Up @@ -172,24 +172,41 @@ You can embed the Extension Manager's REST API in other programs that use the [N
```go
// Create an instance of `openapi.API`
api := openapi.NewOpenAPI()
// Create a new configuration object
config := ExtensionManagerConfig{ExtensionRegistryURL: "https://<extension-registry>"}
// Create a new configuration
config := extensionController.ExtensionManagerConfig{
kaklakariada marked this conversation as resolved.
Show resolved Hide resolved
ExtensionRegistryURL: "https://example.com/registry.json",
BucketFSBasePath: "/buckets/bfsdefault/default/",
ExtensionSchema: "EXA_EXTENSIONS",
}
// Add endpoints
err := restAPI.AddPublicEndpoints(api, config)
if err != nil {
return err
}
// Start the server
```

### Embedding the Controller
### Embedding the Extension Controller

If you want to directly use the controller:
If you want to directly use the extension controller in your application you can use the following code as an example:

```go
controller:=extensionController.CreateWithConfig(extensionController.ExtensionManagerConfig{
// Create a new configuration
config := extensionController.ExtensionManagerConfig{
ExtensionRegistryURL: "https://example.com/registry.json",
BucketFSBasePath: "/buckets/bfsdefault/default/",
ExtensionSchema: "EXA_EXTENSIONS",
})
var db *sql.DB // create database connection
extensions, err := controller.GetAllExtensions(context.Background(), db)
}
// Create controller and handle configuration validation error
ctrl, err := extensionController.CreateWithValidatedConfig(config)
if err != nil {
return err
}

// Create database connection (required as an argument for all controller methods)
var db *sql.DB = createDBConnection()
kaklakariada marked this conversation as resolved.
Show resolved Hide resolved

// Call controller method and process result. Use a custom context if available.
extensions, err := ctrl.GetAllExtensions(context.Background(), db)
// ...
```
27 changes: 26 additions & 1 deletion pkg/extensionController/transactionController.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,36 @@ func Create(extensionRegistryURL string, schema string) TransactionController {
}

// CreateWithConfig creates a new instance of [TransactionController] with more configuration options.
//
// Deprecated: Use function [CreateWithValidatedConfig] which additionally validates the given configuration.
func CreateWithConfig(config ExtensionManagerConfig) TransactionController {
controller, _ := CreateWithValidatedConfig(config)
return controller
}

// CreateWithValidatedConfig validates the configuration and creates a new instance of [TransactionController].
func CreateWithValidatedConfig(config ExtensionManagerConfig) (TransactionController, error) {
if err := validateConfig(config); err != nil {
return nil, fmt.Errorf("invalid configuration: %w", err)
}
controller := createImpl(config)
return &transactionControllerImpl{
transactionController := &transactionControllerImpl{
controller: controller,
bucketFs: bfs.CreateBucketFsAPI(config.BucketFSBasePath)}
return transactionController, nil
}

func validateConfig(config ExtensionManagerConfig) error {
if config.BucketFSBasePath == "" {
return fmt.Errorf("missing BucketFSBasePath")
}
if config.ExtensionRegistryURL == "" {
return fmt.Errorf("missing ExtensionRegistryURL")
}
if config.ExtensionSchema == "" {
return fmt.Errorf("missing ExtensionSchema")
}
return nil
}

type transactionControllerImpl struct {
Expand Down
31 changes: 31 additions & 0 deletions pkg/extensionController/transactionController_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,37 @@ func (suite *extCtrlUnitTestSuite) AfterTest(suiteName, testName string) {
}
}

// CreateWithValidatedConfig

func (suite *extCtrlUnitTestSuite) TestCreateWithValidatedConfigSuccess() {
ctrl, err := CreateWithValidatedConfig(ExtensionManagerConfig{ExtensionRegistryURL: "url", BucketFSBasePath: "bfspath", ExtensionSchema: "schema"})
suite.NoError(err)
suite.NotNil(ctrl)
}

func (suite *extCtrlUnitTestSuite) TestCreateWithValidatedConfigFailure() {
var tests = []struct {
name string
config ExtensionManagerConfig
expectedError string
}{
{name: "missing registry url", config: ExtensionManagerConfig{BucketFSBasePath: "bfspath", ExtensionSchema: "schema"}, expectedError: "invalid configuration: missing ExtensionRegistryURL"},
{name: "empty registry url", config: ExtensionManagerConfig{ExtensionRegistryURL: "", BucketFSBasePath: "bfspath", ExtensionSchema: "schema"}, expectedError: "invalid configuration: missing ExtensionRegistryURL"},
{name: "missing bucketfs base path", config: ExtensionManagerConfig{ExtensionRegistryURL: "url", ExtensionSchema: "schema"}, expectedError: "invalid configuration: missing BucketFSBasePath"},
{name: "empty bucketfs base path", config: ExtensionManagerConfig{ExtensionRegistryURL: "url", BucketFSBasePath: "", ExtensionSchema: "schema"}, expectedError: "invalid configuration: missing BucketFSBasePath"},
{name: "missing schema", config: ExtensionManagerConfig{ExtensionRegistryURL: "url", BucketFSBasePath: "bfspath"}, expectedError: "invalid configuration: missing ExtensionSchema"},
{name: "empty schema", config: ExtensionManagerConfig{ExtensionRegistryURL: "url", BucketFSBasePath: "bfspath", ExtensionSchema: ""}, expectedError: "invalid configuration: missing ExtensionSchema"},
{name: "all missing", config: ExtensionManagerConfig{}, expectedError: "invalid configuration: missing BucketFSBasePath"},
}
for _, test := range tests {
suite.Run(test.name, func() {
ctrl, err := CreateWithValidatedConfig(test.config)
suite.EqualError(err, test.expectedError)
suite.Nil(ctrl)
})
}
}

// GetAllExtensions

func (suite *extCtrlUnitTestSuite) TestGetAllExtensionsSuccess() {
Expand Down
5 changes: 4 additions & 1 deletion pkg/restAPI/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ const (
// The config struct contains configuration options for the extension manager.
/* [impl -> dsn~go-library~1]. */
func AddPublicEndpoints(api *openapi.API, config extensionController.ExtensionManagerConfig) error {
controller := extensionController.CreateWithConfig(config)
controller, err := extensionController.CreateWithValidatedConfig(config)
if err != nil {
return err
}
return addPublicEndpointsWithController(api, false, controller)
}

Expand Down
Loading