diff --git a/plugin/server.go b/plugin/server.go index b651c6a69..bed02921e 100644 --- a/plugin/server.go +++ b/plugin/server.go @@ -30,9 +30,11 @@ const MagicCookieValue = "d602bf8f470bc67ca7faa0386276bbdd4330efaf76d1a219cb4d69 func Serve(svc interface{}) error { // First check the cookie if os.Getenv(MagicCookieKey) != MagicCookieValue { - return errors.New( - "Please do not execute plugins directly. " + - "Terraform will execute these for you.") + fmt.Fprintf(os.Stderr, + "This binary is a Terraform plugin. These are not meant to be\n" + + "executed directly. Please execute `terraform`, which will load\n" + + "any plugins automatically.\n") + os.Exit(1) } // Create the server to serve our interface diff --git a/website/source/docs/plugins/basics.html.md b/website/source/docs/plugins/basics.html.md new file mode 100644 index 000000000..e7b80ebd2 --- /dev/null +++ b/website/source/docs/plugins/basics.html.md @@ -0,0 +1,110 @@ +--- +layout: "docs" +page_title: "Plugin Basics" +sidebar_current: "docs-plugins-basics" +--- + +# Plugin Basics + +This page documents the basics of how the plugin system in Terraform +works, and how to setup a basic development environment for plugin development +if you're writing a Terraform plugin. + +
+Advanced topic! Plugin development is a highly advanced +topic in Terraform, and is not required knowledge for day-to-day usage. +If you don't plan on writing any plugins, we recommend not reading +this section of the documentation. +
+ +## How it Works + +The plugin system for Terraform is based on multi-process RPC. Every +provider, provisioner, etc. in Terraform is actually a separate compiled +binary. You can see this when you download Terraform: the Terraform package +contains multiple binaries. + +Terraform executes these binaries in a certain way and uses Unix domain +sockets or network sockets to perform RPC with the plugins. + +If you try to execute a plugin directly, an error will be shown: + +``` +$ terraform-provider-aws +This binary is a Terraform plugin. These are not meant to be +executed directly. Please execute `terraform`, which will load +any plugins automatically. +``` + +The code within the binaries must adhere to certain interfaces. +The network communication and RPC is handled automatically by higher-level +Terraform libraries. The exact interface to implement is documented +in its respective documentation section. + +## Installing a Plugin + +To install a plugin, put the binary somewhere on your filesystem, then +configure Terraform to be able to find it. The configuration where plugins +are defined is `~/.terraformrc` for Unix-like systems and +`%APPDATA%/terraform.rc` for Windows. + +An example that configures a new provider is shown below: + +``` +providers { + privatecloud = "/path/to/privatecloud" +} +``` + +The key `privatecloud` is the _prefix_ of the resources for that provider. +For example, if there is `privatecloud_instance` resource, then the above +configuration would work. The value is the name of the executable. This +can be a full path. If it isn't a full path, the executable will be looked +up on the `PATH`. + +## Developing a Plugin + +Developing a plugin is simple. The only knowledge necessary to write +a plugin is basic command-line skills and basic knowledge of the +[Go programming language](http://golang.org). + +
+Note: A common pitfall is not properly setting up a +$GOPATH. This can lead to strange errors. You can read more about +this here to familiarize +yourself. +
+ +Create a new Go project somewhere in your `$GOPATH`. If you're a +GitHub user, we recommend creating the project in the directory +`$GOPATH/src/github.com/USERNAME/terraform-NAME`, where `USERNAME` +is your GitHub username and `NAME` is the name of the plugin you're +developing. This structure is what Go expects and simplifies things down +the road. + +With the directory made, create a `main.go` file. This project will +be a binary so the package is "main": + +``` +package main + +import ( + "github.com/hashicorp/terraform/plugin" +) + +func main() { + plugin.Serve(new(MyPlugin)) +} +``` + +And that's basically it! You'll have to change the argument given to +`plugin.Serve` to be your actual plugin, but that is the only change +you'll have to make. The argument should be a structure implementing +one of the plugin interfaces (depending on what sort of plugin +you're creating). + +While its not strictly necessary, Terraform plugins follow specific +naming conventions. The format of the plugin binaries are +`terraform-TYPE-NAME`. For example, `terraform-provider-aws`. +We recommend you follow this convention to help make it clear what +your plugin does to users. diff --git a/website/source/docs/plugins/provider.html.md b/website/source/docs/plugins/provider.html.md index f1389bfa3..2ccbc12cb 100644 --- a/website/source/docs/plugins/provider.html.md +++ b/website/source/docs/plugins/provider.html.md @@ -27,15 +27,218 @@ If you don't plan on writing any plugins, we recommend not reading this section of the documentation. -## Coming Soon! +If you're interested in provider development, then read on. The remainder +of this page will assume you're familiar with +[plugin basics](/docs/plugins/basics.html) and that you already have +a basic development environment setup. -The documentation for writing custom providers is coming soon. In the -mean time, you can look at how our -[built-in providers are written](https://github.com/hashicorp/terraform/tree/master/builtin). -We recommend copying as much as possible from our providers when working -on yours. +## Low-Level Interface -We're also rapidly working on improving the high-level helpers for -writing providers. We expect that writing providers will become much -easier very shortly, and acknowledge that writing them now is not the -easiest thing to do. +The interface you must implement for providers is +[ResourceProvider](https://github.com/hashicorp/terraform/blob/master/terraform/resource_provider.go). + +This interface is extremely low level, however, and we don't recommend +you implement it directly. Implementing the interface directly is error +prone, complicated, and difficult. + +Instead, we've developed some higher level libraries to help you out +with developing providers. These are the same libraries we use in our +own core providers. + +## helper/schema + +The `helper/schema` library is a framework we've built to make creating +providers extremely easy. This is the same library we use to build most +of the core providers. + +To give you an idea of how productive you can become with this framework: +we implemented the Google Cloud provider in about 6 hours of coding work. +This isn't a simple provider, and we did have knowledge of +the framework beforehand, but it goes to show how expressive the framework +can be. + +The GoDoc for `helper/schema` can be +[found here](http://godoc.org/github.com/hashicorp/terraform/helper/schema). +This is API-level documentation but will be extremely important +for you going forward. + +## Provider + +The first thing to do in your plugin is to create the +[schema.Provider](http://godoc.org/github.com/hashicorp/terraform/helper/schema#Provider) structure. +This structure implements the `ResourceProvider` interface. We +recommend creating this structure in a function to make testing easier +later. Example: + +``` +func Provider() *schema.Provider { + return &schema.Provider{ + ... + } +} +``` + +Within the `schema.Provider`, you should initialize all the fields. They +are documented within the godoc, but a brief overview is here as well: + + * `Schema` - This is the configuration schema for the provider itself. + You should define any API keys, etc. here. Schemas are covered below. + + * `ResourcesMap` - The map of resources that this provider supports. + All keys are resource names and the values are the + [schema.Resource](http://godoc.org/github.com/hashicorp/terraform/helper/schema#Resource) structures implementing this resource. + + * `ConfigureFunc` - This function callback is used to configure the + provider. This function should do things such as initialize any API + clients, validate API keys, etc. The `interface{}` return value of + this function is the `meta` parameter that will be passed into all + resource [CRUD](http://en.wikipedia.org/wiki/Create,_read,_update_and_delete) + functions. In general, the returned value is a configuration structure + or a client. + +As part of the unit tests, you should call `InternalValidate`. This is used +to verify the structure of the provider and all of the resources, and reports +an error if it is invalid. An example test is shown below: + +``` +func TestProvider(t *testing.T) { + if err := Provider().InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} +``` + +Having this unit test will catch a lot of beginner mistakes as you build +your provider. + +## Resources + +Next, you'll want to create the resources that the provider can manage. +These resources are put into the `ResourcesMap` field of the provider +structure. Again, we recommend creating functions to instantiate these. +An example is shown below. + +``` +func resourceComputeAddress() *schema.Resource { + return &schema.Resource { + ... + } +} +``` + +Resources are described using the +[schema.Resource](http://godoc.org/github.com/hashicorp/terraform/helper/schema#Resource) +structure. This structure has the following fields: + + * `Schema` - The configuration schema for this resource. Schemas are + covered in more detail below. + + * `Create`, `Read`, `Update`, and `Delete` - These are the callback + functions that implement CRUD operations for the resource. The only + optional field is `Update`. If your resource doesn't support update, then + you may keep that field nil. + +The CRUD operations in more detail, along with their contracts: + + * `Create` - This is called to create a new instance of the resource. + Terraform guarantees that an existing ID is not set on the resource + data. That is, you're working with a new resource. + + * `Read` - This is called to resync the local state with the remote state. + Terraform guarantees that an existing ID will be set. This ID should be + used to look up the resource. Any remote data should be updated into + the local data. **No changes to the remote resource are to be made.** + + * `Update` - This is called to update properties of an existing resource. + Terraform guarantees that an existing ID will be set. Additionally, + the only changed attributes are guaranteed to be those that support + update, as specified by the schema. Be careful to read about partial + states below. + + * `Delete` - This is called to delete the resource. Terraform guarantees + an existing ID will be set. + +## Schemas + +Both providers and resources require a schema to be specified. The schema +is used to define the structure of the configuration, the types, etc. It is +very important to get correct. + +In both provider and resource, the schema is a `map[string]*schema.Schema`. +The key of this map is the configuration key, and the value is a schema for +the value of that key. + +Schemas are incredibly powerful, so this documentation page won't attempt +to cover the full power of them. Instead, the API docs should be referenced +which cover all available settings. + +We recommend viewing schemas of existing or similar providers to learn +best practices. A good starting place is the +[core Terraform providers](https://github.com/hashicorp/terraform/tree/master/builtin/providers). + +## Resource Data + +The parameter to provider configuration as well as all the CRUD operations +on a resource is a +[schema.ResourceData](http://godoc.org/github.com/hashicorp/terraform/helper/schema#ResourceData). +This structure is used to query configurations as well as to set information +about the resource such as it's ID, connection information, and computed +attributes. + +The API documentation covers ResourceData well, as well as the core providers +in Terraform. + +**Partial state** deserves a special mention. Occasionally in Terraform, create or +update operations are not atomic; they can fail halfway through. As an example, +when creating an AWS security group, creating the group may succeed, +but creating all the initial rules may fail. In this case, it is incredibly +important that Terraform record the correct _partial state_ so that a +subsequent `terraform apply` fixes this resource. + +Most of the time, partial state is not required. When it is, it must be +specifically enabled. An example is shown below: + +
+func resourceUpdate(d *schema.ResourceData, meta interface{}) error {
+	// Enable partial state mode
+	d.Partial(true)
+
+	if d.HasChange("tags") {
+		// If an error occurs, return with an error,
+		// we didn't finish updating
+		if err := updateTags(d, meta); err != nil {
+			return err
+		}
+
+		d.SetPartial("tags")
+	}
+
+	if d.HashChange("name") {
+		if err := updateName(d, meta); err != nil {
+			return err
+		}
+
+		d.SetPartial("name")
+	}
+
+	// We succeeded, disable partial mode
+	d.Partial(false)
+
+	return nil
+}
+
+ +In the example above, it is possible that setting the `tags` succeeds, +but setting the `name` fails. In this scenario, we want to make sure +that only the state of the `tags` is updated. To do this the +`Partial` and `SetPartial` functions are used. + +`Partial` toggles partial-state mode. When disabled, all changes are merged +into the state upon result of the operation. When enabled, only changes +enabled with `SetPartial` are merged in. + +`SetPartial` tells Terraform what state changes to adopt upon completion +of an operation. You should call `SetPartial` with every key that is safe +to merge into the state. The parameter to `SetPartial` is a prefix, so +if you have a nested structure and want to accept the whole thing, +you can just specify the prefix. diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index dd14e213d..5880ea849 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -134,6 +134,10 @@ > Plugins