REST APIs ========= This section covers the process of generating `REST APIs `_ for a new resource. Let's continue with the example of the ``localaccount`` resource. OAPI Definitions ------------------- The first step is to define the new resource in the OpenAPI definitions. To define the new resource, do the following: 1. Go to the OAPI files that are located at ``api/openapi`` folder. The resources are defined in the ``schemas`` folder. 2. Create a new file called ``localaccount.yaml`` and ensure that the definition looks as follows and contains all the necessary abstractions: .. code-block:: yaml LocalAccount: description: "A local account resource." type: object required: - username - sshKey properties: resourceId: type: string pattern: "^localaccount-[0-9a-f]{8}$" description: "resource ID, generated by the inventory on Create." readOnly: true username: description: "The local account's username." type: string maxLength: 32 pattern: "^[a-z][a-z0-9-]{0,31}$" sshkey: description: "The local account's sshkey." type: string maxLength: 800 pattern: "^(ssh-(rsa|dss|ed25519)|ecdsa-sha2-nistp(256|384|521)) ([A-Za-z0-9+/=]+) ?(.*)?$" timestamps: "$ref": "./common.yaml#/Timestamps" readOnly: true title: LocalAccount The ``LocalAccount`` object is defined with the necessary properties and constraints. They reflect the protobuf definition as closely as possible. Also, remember to register the new resources in ``_index.yaml`` file. The second step is to write the API paths which basically will define the allowed operation on the resource and the attributes/properties that can be modified through the request. For example, the paths allow to specify the ``readOnly`` property considering a specific operation. The paths are defined in the ``paths`` folder. Create a new file called ``localaccount.yaml``: .. code-block:: yaml localaccounts: get: description: "Gets all local account objects." parameters: ... post: description: "Create a Local account instance" tags: - LocalAccount requestBody: required: true content: application/json: schema: "$ref": "../schemas/_index.yaml#/LocalAccount" ... localaccountId: parameters: - name: localaccountID ... get: description: "Get a local account object by ID." ... delete: description: "Delete a local account object by ID." ... Before generating the Golang\* code, ensure to register the new paths in the ``edge-infrastructure-manager-openapi.yaml`` file. Code and Documentation Generation -------------------------------------- Assuming that the protobuf definition is ready, the next step is to generate the code and the documentation. **API Server** exposes several make targets that assist the developer journey. Use the following command to generate the code and the documentation: .. code-block:: bash make generate After running the command, the generated code and documentation are available in the respective directories. Make sure the following files are updated: .. code-block:: bash `api/api/openapi/edge-infrastructure-manager-openapi-all.yaml` `api/pkg/api/v0/edge-infrastructure-manager-openapi-client.gen.go` `api/pkg/api/v0/edge-infrastructure-manager-openapi-server.gen.go` `api/pkg/api/v0/edge-infrastructure-manager-openapi-types.gen.go` API Server Implementation ------------------------------ Next step is to implement the logic handling the HTTP requests coming for the external clients. First step is to implement a new handler that will receive the HTTP requests and will submit the request to the worker go routines that will deal with the translation of requests in protobuf messages and vice versa. The handler will be implemented as new file ``internal/server/handlerlocalaccount.go``. Make sure to implement only the necessary hooks. For example, in the following example there will not be an hook to handle PATCH/PUT accordingly to what was defined in the OpenAPI specification. .. code-block:: go // (GET /localaccounts). func (r *restHandlers) GetLocalAccounts(ctx echo.Context, query api.GetLocalAccountParams) error { ... } // (POST /localaccounts). func (r *restHandlers) PostLocalAccounts(ctx echo.Context) error { ... } // GET /localaccount/{localAccountId} - Get a local account by ID func (r *restHandlers) GetLocalAccountLocalAccountID(ctx echo.Context, localAccountID string) error { ... } // (DELETE /localaccount/{localAccountId}). func (r *restHandlers) DeleteLocalAccountLocalAccountID(ctx echo.Context, localAccountID string) error { ... } The requests submitted by the REST handlers are handled by the corresponding Inventory handler. Each handler is responsible to handle only one resource. The next step is to register the Inventory handlers and to properly extend the types respectively in the following files ``internal/worker/handlers/invhandlers/main.go``, ``internal/types/types.go`` and ``internal/worker/handlers/invhandlers/params.go``. Now it is the time for the core changes, see one of the files in ``api/internal/worker/handlers/invhandlers`` as an example. In general, it is required to implement for each API operation a new function that will handle the request and will deal with the translation of the request in protobuf messages and vice versa. .. code-block:: go func (t telemetryLogsGroupHandler) Create(job *types.Job) (*types.Payload, error) { ... } func (t telemetryLogsGroupHandler) Get(job *types.Job) (*types.Payload, error) { ... } func (t telemetryLogsGroupHandler) Update(job *types.Job) (*types.Payload, error) { err := errors.Errorfc(codes.Unimplemented, "%s operation not supported", job.Operation) return nil, err } func (t telemetryLogsGroupHandler) Delete(job *types.Job) error { ... } func (t telemetryLogsGroupHandler) List(job *types.Job) (*types.Payload, error) { ... } Create unit tests using the testing framework exposed by Inventory. This will allow to test using a real Inventory instance. To further tests corner case make sure to extend the logic of the Inventory mocks. The mocks implementation are located in ``api/test/utils``. Extending Integration Tests --------------------------- End-to-end tests are required to ensure that the new resource is working as expected. The tests are in the folder ``api/test/client`` and use auto-generated Golang clients which make HTTP requests to the API server. The tests written in Go are executed using a deployment that contains Edge Infrastructure Manager components and allows to test the new resource in a real environment and exercise the reconciliation of the resource managers. See the ``test`` directory for more details.