This new implementation of composer-cli is broken up into 2 major pieces. The
./weldr/
package contains the library functions for communicating with the
API server. These functions are publicly accessible and should be documented so
that 3rd party users can use them to communicate with the osbuild-composer
server.
The ./cmd/composer-cli/
code is the user interface code, separated out into
commands and subcommands. They are for interactive use, and by scripts via the
--json
argument that switches the output to JSON formated objects.
The weldr library should contain functions, separated into descriptively named
files, like blueprints.go. The functions should handle communication with the
API server and return values to the caller using, if possible, regular Go data
types. eg. ListBlueprints
returns a slice of strings.
Errors from lower functions should be returned to the called as errors, and API
error responses should be returned as APIResponse
or slices of APIErrorMsg
.
The type returned will depend on the response from the server.
The blueprints.go has examples of most of the types of responses you will encounter, use it as a template for adding new functions.
Not every response can use a Go type. For those that need to return more
details use structs that match the API structs used in the server. We do not
want to import the actual server modules (and cannot because they are all
implemented as internal modules) so the structs should be placed into
weldr/apischema.go
Try to avoid using interface{}
as much as possible. I have only used it for
returning the raw blueprint to the caller in the GetBlueprints...
functions so
that it isn't tightly coupled to the actual blueprint format. The composer-cli
isn't manipulating the blueprint data, only passing it through, so it doesn't
need to know what's actually there. Blueprints are the thing most likely to
change, and they are not versioned like the API is so remaining flexible is
important.
Functions, or parts of functions, that can be tested without an actual API
server should be written in the weldr/unit_test.go
file, or if there are no
integration tests into a foo_test.go file where foo is the name of the file you
put the function in.
Integration tests require an API server to be running, and should be placed
into a foo_test.go file alongside the code they are testing. At the top of the
file there should be a // +build integration
before the package line, ans
separated from it with a blank line. See blueprints_test.go for an example.
These tests should be independent from each other. If there is server setup that needs to be done it should be done in a way that works with a clean or a dirty server. Usually it will be run against a clean server, as part of the github action tests that are run when a PR is submitted. But don't depend on that.
If something needs to be setup you can do it in the test function itself, or in the integration_test.go file if necessary. But try to keep changes there to a minimum.
Mention Cobra, point to docs. Point to simple example like 'status show', tell how to hook it up the rootCmd
and how to write tests for it.
composer-cli uses the Cobra library to handle command parsing, options, and subcommands.
The ./cmd/composer-cli/
directory is structured so that top level commands go
into their own directory and package. They import the root Cobra object from
cmd/composer-cli/root
and attach themselves to that command using
root.AddRootCommand()
. Then the subcommands attach themselves to this
command. See cmd/composer-cli/blueprints/blueprints.go
for an example of a
subcommand.
Developer documentation should go into a doc.go file.
Commands are hooked into the root command parser by adding them to the include
list in cmd/composer-cli/main.go
with a preceeding _
so that the compiler
won't complain about unused symbols.
Subcommands should go into their own file, and be members of the command's
package. The file should only contain the functions needed to implement the
user interface for this command. Any helper functions or API related functions
that could be useful to other developers should go into the weldr/
library.
Commands should use the Cobra RunE method which returns an error to the root command handler.
If the command exits immediately on error it should return a
root.ExecutionError
with a string describing the error, and embedding the err
if there was one.
See cmd/composer-cli/blueprints/list.go
for an example of this type of handling.
If there could be more than one error, eg. when processing a list of
blueprints, the command function should print them to os.Stderr
as ERROR: ...\n
and return a blank root.ExecutionError(cmd, "")
to the root handler. See
cmd/composer-cli/blueprints/push.go
for an example.
The command line code under ./cmd/
is unit tested against the ./weldr/
library
code. They use a mock http client that is setup, by each test, to return the
expected JSON or TOML responses. See the tests in
./cmd/composer-cli/blueprints/
for examples of how to set this up.
Basically, you setup a mock http client that stores the query it receives and returns a canned response. This should not be too elaborate, otherwise you end up mocking every detail of the server. The goal is to make sure the cmdline command calls the right library function and that the response from the function is parsed correctly.
The weldr.MockClient
function that is used to handle the requests adds a .Req
element that can be examined in the tests. eg:
assert.Equal(t, "/api/v1/blueprints/list", mc.Req.URL.Path)
If the test returns an error the MockClient
DoFunc
needs to return the
request so that the apiError
function can pass it to the callback function.
See common_test.TestRequestMethods404
for an example.
Unit tests can be run by running make test
.
Integration tests are run by a test binary. You can build the binary by running
make integration
and then running it on a system with osbuild-composer running. Pass it -test.v to
output more verbose details about the tests being run.