diff --git a/website/content/docs/plugins/go-plugins/guests.mdx b/website/content/docs/plugins/go-plugins/guests.mdx new file mode 100644 index 000000000..da3e804b0 --- /dev/null +++ b/website/content/docs/plugins/go-plugins/guests.mdx @@ -0,0 +1,222 @@ +--- +layout: docs +page_title: Custom Guests - Go Plugin Development +description: |- + This page documents how to add new guest OS detection to Vagrant, allowing + Vagrant to properly configure new operating systems. Prior to reading this, + you should be familiar with the plugin development basics. +--- + +# Go Plugin Development: Guests + +Outside of these components, the caller must provide all other arguments +that are required. + +The most basic guest plugin is composed of: +1. A detection function that determines if the plugin is usable on the system +(is the expected guest) +2. A set of capabilities that define actions that can be run against the guest +3. An entry point defining the plugin options + +**Note**: To quickly get started writing Go guest plugins, clone the [vagrant-guest-plugin-skeleton](https://github.com/soapy1/vagrant-guest-plugin-skeleton) +template and follow the Readme. + +The file structure of a guest plugin looks like: + +``` +- myplugin + |- main.go + \- guest + |- myguest.go + \- cap + |- mycapability.go +``` + +Where `main.go` defines the plugin options. `guest/myguest.go` defines the core plugin +functionality including the detection of the guest. `cap/*` has the definitions of +all the guest plugin capabilities. These capabilities are the same as those for [Ruby +plugins](../guest-capabilities). + +## Writing a guest plugin + +A guest must satisfy the interface defined for a guest component + +``` + GuestDetectFunc() interface{} + ParentFunc() interface{} + HasCapabilityFunc() interface{} + CapabilityFunc(capName string) interface{} +``` +Src: https://github.com/hashicorp/vagrant-plugin-sdk/blob/main/component/component.go + +`GuestDetectFunc`: returns a function that defines the code that determines if the guest is + detected. The returned function must return a `bool`. + +`ParentFunc`: returns a function that defines the code that determines the most immediate parent + plugin. A child plugin will inherit all the capabilities defined in the parent. The + returned function must return a `string`. + +`HasCapabilityFunc`: returns a function that defines a lookup for a capability. The returned + function must return an `bool`. + +`CapabilityFunc`: returns a capability function that is defined by the plugin registered by a given name. + +An example guest plugin + +``` +// file: myplugin/guest/myguest.go +package guest + +import ( + "github.com/hashicorp/vagrant-plugin-sdk/component" + sdkcore "github.com/hashicorp/vagrant-plugin-sdk/core" +) + +// AlwaysTrueGuest is a Guest implementation for myplugin. +type AlwaysTrueGuest struct { +} + +// DetectFunc implements component.Guest +func (h *AlwaysTrueGuest) GuestDetectFunc() interface{} { + return h.Detect +} + +func (h *AlwaysTrueGuest) Detect(t sdkcore.Target) (bool, error) { + return true, nil +} + +// ParentsFunc implements component.Guest +func (h *AlwaysTrueGuest) ParentFunc() interface{} { + return h.Parent +} + +func (h *AlwaysTrueGuest) Parent() string { + // This plugin has no parents + return "" +} + +// HasCapabilityFunc implements component.Guest +func (h *AlwaysTrueGuest) HasCapabilityFunc() interface{} { + return h.CheckCapability +} + +func (h *AlwaysTrueGuest) CheckCapability(n *component.NamedCapability) bool { + // This plugin has no capabilities + return false +} + +// CapabilityFunc implements component.Guest +func (h *AlwaysTrueGuest) CapabilityFunc(name string) interface{} { + return fmt.Errorf("requested capability %s not found", name) +} + +var ( + _ component.Guest = (*AlwaysTrueGuest)(nil) +) +``` + +``` +// file: myplugin/main.go +package myplugin + +import ( + sdk "github.com/hashicorp/vagrant-plugin-sdk" + "github.com/hashicorp/vagrant/builtin/myplugin/guest" +) + +// Options are the SDK options to use for instantiation. +var ComponentOptions = []sdk.Option{ + sdk.WithComponents( + // Include the defined guest as a component defined in this plugin + &guest.AlwaysTrueGuest{}, + ), +} + +func main() { + sdk.Main(ComponentOptions...) + os.Exit(0) +} +``` + +In this example, the guest plugin will always be detected. It does not define any +capabilities, or have any parent plugins. + +### Defining and registering guest capabilities + +A guest plugin may have capabilities two ways: +1. By defining and implementing the capability in the plugin +2. By inheriting the capability from a parent guest plugin + +Define a capability by writing out a function that returns the desired capability + +``` +// file: myplugin/guest/cap/mycapability.go +package cap + +import ( + "io/ioutil" + + "github.com/hashicorp/vagrant-plugin-sdk/terminal" +) + +func WriteHelloFunc() interface{} { + return WriteHello +} + +func WriteHello(trm terminal.UI) error { + trm.Output("Hello world") + return nil +} +``` + +Make the capability available to the plugin by filling in the capability functions + +``` +myplugin/guest/myguest.go +// HasCapabilityFunc implements component.Guest +func (h *AlwaysTrueGuest) HasCapabilityFunc() interface{} { + return h.CheckCapability +} + +func (h *AlwaysTrueGuest) CheckCapability(n *component.NamedCapability) bool { + if n.Capability == "write_hello" { + return true + } + return false +} + +// CapabilityFunc implements component.Guest +func (h *AlwaysTrueGuest) CapabilityFunc(name string) interface{} { + if name == "write_hello" { + return h.WriteHelloCap + } + return errors.New("Invalid capability requested") +} + +func (h *AlwaysTrueGuest) WriteHelloCap(ui terminal.UI) error { + return cap.WriteHello(ui) +} +``` + +A guest plugin may inherit the capabilities of a parent function by defining +a parent in the plugin implementation. This is done by setting the return +value of the `Parent` function to the name of the desired parent plugin. Go +based guest plugins may use Ruby based plugins as their parent. + +``` +// file: myplugin/guest/myguest.go + +... + +// ParentsFunc implements component.Guest +func (h *AlwaysTrueGuest) ParentFunc() interface{} { + return h.Parent +} + +func (h *AlwaysTrueGuest) Parent() string { + // This plugin sets the parent to the "debian" plugin which is provided + // as a Ruby plugin. This AlwaysTrueGuest now inherits all the capabilities + // of the debian Ruby guest plugin. + return "debian" +} +``` diff --git a/website/content/docs/plugins/go-plugins/index.mdx b/website/content/docs/plugins/go-plugins/index.mdx new file mode 100644 index 000000000..e6de441f0 --- /dev/null +++ b/website/content/docs/plugins/go-plugins/index.mdx @@ -0,0 +1,74 @@ +--- +layout: docs +page_title: Go Based Plugins +description: |- + Vagrant comes with many great features out of the box to get your environments + up and running. Sometimes, however, you want to change the way Vagrant does + something or add additional functionality to Vagrant. This can be done via + Vagrant plugins. +--- + +# Go Vagrant Plugins + +With the introduction of Vagrant-go, Vagrant now supports running plugins +implemented in Go. Note that Vagrant-go can run Go and Ruby based plugins +while Vagrant-ruby only runs Ruby based plugins. + +## Anatomy of a Go plugin + +When a plugin is started, it runs in its own process and is able to communicate +with the other plugins over GRPC. + +A Vagrant-go plugin must implement an interface for a [plugin component](https://github.com/hashicorp/vagrant-plugin-sdk/blob/main/component/component.go). +A plugin may satisfy more than one component interface. The plugin interfaces +define the `Func` functions for the plugin. These functions are meant to +return the actual function that represents the named action. For example the +`Host` component defines a `DetectFunc`. So, a plugin must have a `DetectFunc` +implementation that returns a function that can detect if Vagrant is running +on the given host. For example + +``` +// DetectFunc implements component.Host +func (h *AlwaysTrueHost) DetectFunc() interface{} { + return h.Detect +} + +func (h *AlwaysTrueHost) Detect() bool { + // This plugin always detects that it is running on the expected host + return true +} +``` + +Using this pattern of `Func` functions, Vagrant is able to allow flexibility +over the receivers of the functions that actually define the behavior of the plugin. +This injection of dependencies is done using go-argmapper. So, in the same example, +the `Detect` function may be made to accept some arguments. + +``` +// DetectFunc implements component.Host +func (h *AlwaysTrueHost) DetectFunc() interface{} { + return h.Detect +} + +func (h *AlwaysTrueHost) Detect(string msg, trm terminal.UI) bool { + trm.Output(msg) + return true +} +``` + +Now, in order to run the `Detect` function, the caller must provide a `string` and +`terminal.UI` argument. By default, Vagrant will always inject the following arguments +into a call to a plugin: + +- `terminal.UI` component +- basis +- project (when available) +- context +- logger + +For each component type, Vagrant may also inject some additional arguments. + +Outside of these components, the caller must provide all other arguments that are required. +So, in the case above, in order to successfully call this `Detect` function, the caller must +also provide a `string`. It is recommended that plugin authors do not rely on arguments being +injected into their implementations outside of these sets of arguments. diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index a58cce316..0c942db9b 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -699,6 +699,19 @@ { "title": "Packaging & Distribution", "path": "plugins/packaging" + }, + { + "title": "Go Plugins", + "routes": [ + { + "title": "Overview", + "path": "plugins/go-plugins" + }, + { + "title": "Guests", + "path": "plugins/go-plugins/guests" + } + ] } ] },