Cthulhu

This is just a proof of concept
Cloud based hypervisor system for juju controlled environments.
Development
Coding conventions
Style conventions
- Private stuff is written in
camelCase
- Public stuff is written in
PascalCase
- Constants & macros are written in
SCREAMING_SNAKE_CASE
- Namespaces are written in
snake_case
- Filenames are written in
snake_case
Cthulhu should contain as few comments as possible. If you are unsure about adding a comment, ask yourself if the code is self-explanatory. If not, consider refactoring to make it clearer.
The following situations explicitly require comments:
- Exported interfaces: All exported types, interfaces, classes, functions, etc., used by other parts of the code (if it has a public BUILD rule) require explicit comments describing HOW to use them.
- Complex implementation: If a piece of code operates in a complex manner, it should generally be refactored. If refactoring is not possible or would make the code more complex, you must add a comment explaining WHY it functions that way.
Comments are written in godoc
for go
and doxygen
for C++
.
Every shared component must contain a README
file that concisely describes WHY this component exists.
Following the rules above, when you maintain code, it is essential to check and update the following:
- Update functionality of an exported interface: Check and update the interface's comment.
- Breaking change in the functionality of a component: Check and update the
README
.
Exception system (C++)
C++ components employ exception matching at the level where the exception is logged. Expected exceptions should be in ComponentException
format, if they are not, the logged component is set to "UNDEFINED".
-
Exceptions thrown directly by cthulhu components (exclude independent "util" folder) must be in util::error::ComponentException
format.
-
Internal or external C++ std::exception
derivates should be converted to ComponentException
at the level of a definable component (e.g. a wave library should NOT return a ComponentException
; however, the calling component should immediately convert it to a ComponentException
by rethrowing it and adding the component information to it).
Exception system (Go)
Go components employ error matching at the level where the error is logged. Expected errors should be in ComponentError
format, if they are not, the logged component is set to "UNDEFINED".
-
Errors returned directly by cthulhu components (exclude independent "util" folder) must be in error.ComponentError
format.
-
Regular Go error
s should be converted to ComponentError
at the level of a definable component (e.g. a rune library should NOT return a ComponentError
; however, the calling component should immediately convert it to a ComponentError
with the provided error.Errorf()
function).
Exception messages
Error and exception messages should not be overloaded. However, when low-level errors or errors from external libraries occur, prefix them with a short message describing the error from the perspective of the current component.
Example: The output of getKeyType
is very general, so it is enhanced with a short message:
keyType, err := getKeyType(
metaconfig.GetString("ACME_CERTIFICATE_KEY_TYPE", defOpts.ACME_CERTIFICATE_KEY_TYPE))
if err!=nil {
return nil, cterror.Errorf("CERT", "Failed to process ACME_CERTIFICATE_KEY_TYPE: ", err.Error())
}
That way the reader gets a hint that the ACME_CERTIFICATE_KEY_TYPE variable is involved here (instead of just getting "Invalid key type" crap).
The goal is to provide enough information in the error message so that the reader can determine the source and direction of the error, and possibly how to resolve it, without needing to enable debug logs etc.
Database naming
All database key-segments are strictly written in UPPERCASE and words are spaced with '_'.
Database keys are named based on those three segments: major prefix
, minor prefix
and suffix
.
major prefix
: Determines key access. Shared keys start with SHARED
followed by the component name. This segment is relevant for RBAC
roles. (example: SHARED_RUNE_
|| RUNE_
).
minor prefix
: Holds key description / use. (example: CERT_REQUEST_DOMAIN_
|| CERT_PROVIDER_EMAIL_
).
suffix
: Holds dynamic identifiers for the key. It usually contains attributes like the hostname
to identify the node or the service
to identify the cthulhu service. The suffix is very variable and is based on the context like the range queries performed on it. (example: SRVCTH01_RUNE
|| WAVE_SRVCTH02
).
Full examples:
SHARED_RUNE_CERT_REQUEST_EXP_SRVCTH01_WAVE # Certificate request expiration date for wave on srvcth01
RUNE_CERT_ACME_EMAIL_SRVCTH01 # Certificate acme email used from rune on srvcth01
RUNE_CERT_ACME_KEY_SRVCTH01 # Certificate acme accesskey used from rune on srvcth01
(This concept may seem confusing at first, but it enables efficient use of etcd features).
Dependency management
All build steps are managed by the bazel
build tool. Other tools shall only be used as bazel plugin.
Go dependencies are managed in the go.mod
file at the root of the project, this file is processed by gazelle
to generate the required rules under the hood.
C++ dependencies are managed in the MODULE.bazel
file at the root of the project.
Every component does have its own Bazel BUILD file.
C++ rules must be manually configured, for Go and Proto rules, bazel run //:gazelle
can be used to generate the code automatically.
Completions / IntelliSense
GO
For Go development I suggest using the gopls lang-server and import the repository root. Then you should have completion over the whole project.
C++
For C++ development I recommend using clangd for intellisense / documentation.
To generate the compile_commands.json file there are various options, I recommend to use this tool:
bazel-compile-commands
bazel-compile-commands releases
(In case std is by default not ++23, you can use this option to replace the std: bazel-compile-commands -R c++14=c++23 -R -fno-canonical-system-headers=""
)