feat: extract `/plugins` (#11464)

* feat: extract `/plugins`

Co-authored-by: Zachary Shilton <4624598+zchsh@users.noreply.github.com>
Co-authored-by: Adrien Delorme <azr@users.noreply.github.com>
pull/11488/head
Kevin Wang 4 years ago committed by GitHub
parent e062f69a89
commit b3341164cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,15 +5,11 @@ import {
getPathsFromNavData,
} from '@hashicorp/react-docs-page/server'
import renderPageMdx from '@hashicorp/react-docs-page/render-page-mdx'
import resolveNavData from './utils/resolve-nav-data'
import resolveNavDataWithRemotePlugins from './utils/resolve-nav-data'
import fetchLatestReleaseTag from './utils/fetch-latest-release-tag'
async function generateStaticPaths({
navDataFile,
localContentDir,
remotePluginsFile,
}) {
const navData = await resolveNavData(navDataFile, localContentDir, {
async function generateStaticPaths({ navDataFile, remotePluginsFile }) {
const navData = await resolveNavDataWithRemotePlugins(navDataFile, {
remotePluginsFile,
})
const paths = await getPathsFromNavData(navData)
@ -34,7 +30,7 @@ async function generateStaticProps({
// Resolve navData, including the possibility that this
// page is a remote plugin docs, in which case we'll provide
// the MDX fileString in the resolved navData
const navData = await resolveNavData(navDataFile, localContentDir, {
const navData = await resolveNavDataWithRemotePlugins(navDataFile, {
remotePluginsFile,
currentPath,
})
@ -102,5 +98,4 @@ async function generateStaticProps({
}
}
export default { generateStaticPaths, generateStaticProps }
export { generateStaticPaths, generateStaticProps }

@ -3,49 +3,32 @@ const path = require('path')
const grayMatter = require('gray-matter')
const fetchPluginDocs = require('./fetch-plugin-docs')
const fetchDevPluginDocs = require('./fetch-dev-plugin-docs')
const validateFilePaths = require('@hashicorp/react-docs-sidenav/utils/validate-file-paths')
const validateRouteStructure = require('@hashicorp/react-docs-sidenav/utils/validate-route-structure')
/**
* Resolves nav-data from file, including optional
* Resolves nav-data from file with
* resolution of remote plugin docs entries
*
* @param {string} navDataFile path to the nav-data.json file, relative to the cwd. Example: "data/docs-nav-data.json".
* @param {string} localContentDir path to the content root, relative to the cwd. Example: "content/docs".
* @param {object} options optional configuration object
* @param {string} options.remotePluginsFile path to a remote-plugins.json file, relative to the cwd. Example: "data/docs-remote-plugins.json".
* @returns {array} the resolved navData. This includes NavBranch nodes pulled from remote plugin repositories, as well as filePath properties on all local NavLeaf nodes, and remoteFile properties on all NavLeafRemote nodes.
* @returns {Promise<array>} the resolved navData. This includes NavBranch nodes pulled from remote plugin repositories, as well as filePath properties on all local NavLeaf nodes, and remoteFile properties on all NavLeafRemote nodes.
*/
async function resolveNavData(navDataFile, localContentDir, options = {}) {
async function resolveNavDataWithRemotePlugins(navDataFile, options = {}) {
const { remotePluginsFile, currentPath } = options
// Read in files
const navDataPath = path.join(process.cwd(), navDataFile)
const navData = JSON.parse(fs.readFileSync(navDataPath, 'utf8'))
// Fetch remote plugin docs, if applicable
let withPlugins = navData
if (remotePluginsFile) {
// Resolve plugins, this yields branches with NavLeafRemote nodes
withPlugins = await mergeRemotePlugins(
remotePluginsFile,
navData,
currentPath
)
}
// Resolve local filePaths for NavLeaf nodes
const withFilePaths = await validateFilePaths(withPlugins, localContentDir)
validateRouteStructure(withFilePaths)
// Return the nav data with:
// 1. Plugins merged, transformed into navData structures with NavLeafRemote nodes
// 2. filePaths added to all local NavLeaf nodes
return withFilePaths
let navData = JSON.parse(fs.readFileSync(navDataPath, 'utf8'))
return await appendRemotePluginsNavData(
remotePluginsFile,
navData,
currentPath
)
}
// Given a remote plugins config file, and the full tree of docs navData which
// contains top-level branch routes that match plugin component types,
// fetch and parse all remote plugin docs, merge them into the
// broader tree of docs navData, and return the docs navData
// with the merged plugin docs
async function mergeRemotePlugins(remotePluginsFile, navData, currentPath) {
async function appendRemotePluginsNavData(
remotePluginsFile,
navData,
currentPath
) {
// Read in and parse the plugin configuration JSON
const remotePluginsPath = path.join(process.cwd(), remotePluginsFile)
const pluginEntries = JSON.parse(fs.readFileSync(remotePluginsPath, 'utf-8'))
@ -56,72 +39,42 @@ async function mergeRemotePlugins(remotePluginsFile, navData, currentPath) {
async (entry) => await resolvePluginEntryDocs(entry, currentPath)
)
)
// group navData by component type, to prepare to merge plugin docs
// into the broader tree of navData.
const pluginDocsByComponent = pluginEntriesWithDocs.reduce(
(acc, pluginEntry) => {
const { components } = pluginEntry
Object.keys(components).forEach((type) => {
const navData = components[type]
if (!navData) return
if (!acc[type]) acc[type] = []
acc[type].push(navData[0])
})
return acc
},
{}
)
// merge plugin docs, by plugin component type,
// into the corresponding top-level component NavBranch
const navDataWithPlugins = navData.slice().map((n) => {
// we only care about top-level NavBranch nodes
if (!n.routes) return n
// for each component type, check if this NavBranch
// is the parent route for that type
const componentTypes = Object.keys(pluginDocsByComponent)
let typeMatch = false
for (var i = 0; i < componentTypes.length; i++) {
const componentType = componentTypes[i]
const routeMatches = n.routes.filter((r) => r.path === componentType)
if (routeMatches.length > 0) {
typeMatch = componentType
break
const titleMap = {
builders: 'Builders',
provisioners: 'Provisioners',
'post-processors': 'Post-Processors',
datasources: 'Data Sources',
}
return navData.concat(
pluginEntriesWithDocs.map((entry) => {
return {
title: entry.title,
routes: Object.entries(entry.components).map(
([type, componentList]) => {
return {
title: titleMap[type],
// Flat map to avoid ┐
// > Proxmox │
// > Builders │
// > Proxmox <---┘
// > Overview
// > Clone
// > ISO
routes: componentList.flatMap((c) => {
if ('path' in c) {
return c
} else if ('routes' in c) {
return c.routes
}
}),
}
}
),
}
}
// if this NavBranch does not match a component type slug,
// then return it unmodified
if (!typeMatch) return n
// if there are no matching remote plugin components,
// then return the navBranch unmodified
const pluginsOfType = pluginDocsByComponent[typeMatch]
if (!pluginsOfType || pluginsOfType.length == 0) return n
// if this NavBranch is the parent route for the type,
// then append all remote plugins of this type to the
// NavBranch's child routes
const routesWithPlugins = n.routes.slice().concat(pluginsOfType)
// console.log(JSON.stringify(routesWithPlugins, null, 2))
// Also, sort the child routes so the order is alphabetical
routesWithPlugins.sort((a, b) => {
// ensure casing does not affect ordering
const aTitle = a.title.toLowerCase()
const bTitle = b.title.toLowerCase()
// (exception: "Overview" comes first)
if (aTitle === 'overview') return -1
if (bTitle === 'overview') return 1
// (exception: "Community-Supported" comes last)
if (aTitle === 'community-supported') return 1
if (bTitle === 'community-supported') return -1
// (exception: "Custom" comes second-last)
if (aTitle === 'custom') return 1
if (bTitle === 'custom') return -1
return aTitle < bTitle ? -1 : aTitle > bTitle ? 1 : 0
})
// return n
return { ...n, routes: routesWithPlugins }
})
// return the merged navData, which now includes special NavLeaf nodes
// for plugin docs with remoteFile properties
return navDataWithPlugins
)
}
// Fetch remote plugin docs .mdx files, and
@ -271,4 +224,4 @@ function visitNavLeaves(navData, visitFn) {
})
}
module.exports = resolveNavData
module.exports = resolveNavDataWithRemotePlugins

@ -9,298 +9,21 @@ page_title: Plugins
# Packer Plugins
Packer Plugins allow new functionality to be added to Packer without modifying
the core source code. Packer plugins are able to add new components to Packer,
such as builders, provisioners, post-processors, and data sources.
the core source code. **Packer plugins** are able to add new components to
Packer, such as **builders**, **provisioners**, **post-processors**, and **data
sources**.
This page documents how to install plugins.
This page documents how to install plugins. You can find a list of available
plugins in the [Plugins section](/plugins).
If you're interested in developing plugins, see the [developing
plugins](/docs/plugins/creation#developing-plugins) page.
If you're a plugin maintainer interested in supporting HCP Packer, see the [](/docs/plugins/hcp-support) page.
If you're a plugin maintainer interested in supporting HCP Packer, see the [HCP
support](/docs/plugins/hcp-support) page.
The current official listing of community-written plugins can be found
[here](/community-tools#third-party-plugins); if you have written a Packer
plugin, please reach out to us so we can add your plugin to the website.
@include "plugins/how-plugins-work.mdx"
## How Plugins Work
@include "plugins/plugin-tiers-and-namespaces.mdx"
Packer plugins are completely separate, standalone applications that the core
of Packer starts and communicates with. Even the components that ship with the
Packer core (core builders, provisioners, and post-processors) are implemented
in a similar way and run as though they are standalone plugins.
These plugin applications aren't meant to be run manually. Instead, Packer core
launches and communicates with them. The next time you run a Packer build,
look at your process list and you should see a handful of `packer-` prefixed
applications running. One of those applications is the core; the rest are
plugins -- one plugin process is launched for each component used in a Packer
build.
## Tiers and Namespaces
Packer plugins are published and maintained by a variety of sources, including HashiCorp, and the Packer community. The Packer website uses tiers and badges to denote the source of a provider. Additionally, namespaces are used to help users identify the organization or publisher responsible for the integration, as shown in the table below.
| Tier | Description | Namespace |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| <PluginBadge type="official" /> | Official plugins are owned and maintained by HashiCorp. | hashicorp |
| <PluginBadge type="community" /> | Community providers are published by individual maintainers, groups of maintainers, or other members of the Packer community. | Third-party organization or maintainer's individual account |
## Installing Plugins
Currently, you do not need to install plugins for builder, provisioner, or
post-processor components documented on the Packer website; these components
ship with the Packer core and Packer automatically knows how to find and launch
them. These instructions are for installing custom components that are not
bundled with the Packer core.
The below tabs reference "multi-component" and "single-component" plugins. If
you are not sure what kind of plugin you are trying to install, the easiest way
to find out is to check the name. If the name starts with `packer-plugin-`, then
it is a multi-component plugin. If the name starts with a prefix that actually
says the component type (e.g. `packer-provisioner-` or `packer-builder`), then
it is a single-component plugin.
<Tabs>
<Tab heading="Packer init (from Packer v1.7.0)">
~> **Note**: Only _multi-component plugin binaries_ -- that is plugins named
packer-plugin-\*, like the `packer-plugin-amazon` -- are expected to work with
Packer init. The legacy `builder`, `post-processor` and `provisioner` plugin
types will keep on being detected but Packer cannot install them automatically.
If a plugin you use has not been upgraded to use the multi-component plugin
architecture, contact your maintainer to request an upgrade.
### Create a required_plugins block
1. Add a
[`required_plugins`](/docs/templates/hcl_templates/blocks/packer#specifying-plugin-requirements)
block to your [packer block](/docs/templates/hcl_templates/blocks/packer). Each block will tell Packer what version(s) of a
particular plugin can be installed. Make sure to set a valid [version
constraint string](/docs/templates/hcl_templates/blocks/packer#version-constraints).
Here is an example `required_plugins` block:
```hcl
packer {
required_plugins {
myawesomecloud = {
version = ">= 2.7.0"
source = "github.com/azr/myawesomecloud"
}
happycloud = {
version = ">= 1.1.3"
source = "github.com/azr/happycloud"
}
}
}
```
2. Run [`packer init`](/docs/commands/init) from your project directory (the
directory containing your Packer templates) to install all missing plugin
binaries. Given the above example, Packer will try to look for a GitHub
repository owned by user or organization `azr` named
`packer-plugin-myawesomecloud` and `packer-plugin-happycloud`.
## Names and Addresses
Each plugin has two identifiers:
- A `source` address, which is only necessary when requiring a plugin outside the HashiCorp domain.
- A unique **local name**, which is used everywhere else in a Packer
configuration.
### Local Names
Local names allow you to access the components of a plugin and must be unique
per configuration.
This is best explained using an example. In the above `required_plugins` block,
we declared the local name "myawesomecloud" for the plugin `azr/myawesomecloud`.
If the "myawesomecloud" plugin contains both an "ebs" builder and an "import"
post-processor, then the builder will be accessed in a source block by using:
```hcl
source "myawesomecloud-ebs" "example" {
// builder configuration...
}
```
similarly, the import post-processor would be accessed by declaring the
post-processor block:
```hcl
post-processor "myawesomecloud-import" {
// post-processor configuration...
}
```
If we change the required_plugins block to use a different local name "foo":
```hcl
required_plugins {
foo = {
version = ">= 2.7.0"
source = "github.com/azr/myawesomecloud"
}
}
```
Then we'd instead access that builder using the source:
```hcl
source "foo-ebs" "example" {
// builder configuration...
}
```
## Source Addresses
A plugin's source address is its global identifier. It also tells Packer where
to download it.
Source addresses consist of three parts delimited by slashes (`/`), as
follows:
`<HOSTNAME>/<NAMESPACE>/<TYPE>`
- **Hostname:** The hostname of the location/service that
distributes the plugin. Currently, the only valid "hostname" is github.com,
but we plan to eventually support plugins downloaded from other domains.
- **Namespace:** An organizational namespace within the specified host.
This often is the organization that publishes the plugin.
- **Type:** A short name for the platform or system the plugin manages. The
type is usually the plugin's preferred local name.
For example, the fictional `myawesomecloud` plugin could belong to the
`hashicorp` namespace on `github.com`, so its `source` could be
`github.com/hashicorp/myawesomecloud`,
Note: the actual _repository_ that myawesomecloud comes from must always have
the name format `github.com/hashicorp/packer-plugin-myawesomecloud`, but the
`required_plugins` block omits the redundant `packer-plugin-` repository prefix
for brevity.
The source address with all three components given explicitly is called the
plugin's _fully-qualified address_. You will see fully-qualified address in
various outputs, like error messages.
## Plugin location
@include "plugins/plugin-location.mdx"
## Implicit Github urls
Using the following example :
```hcl
required_plugins {
happycloud = {
version = ">= 2.7.0"
source = "github.com/azr/happycloud"
}
}
```
The plugin getter will look for plugins located at:
- github.com/azr/packer-plugin-happycloud
Packer will error if you set the `packer-plugin-` prefix in a `source`. This
will avoid conflicting with other plugins for other tools, like Terraform.
</Tab>
<Tab heading="manually (multi-component plugin)">
The easiest way to manually install a plugin is to name it correctly, then place
it in the proper directory. To name a plugin correctly, make sure the binary is
named `packer-plugin-NAME`. For example, `packer-plugin-amazon` for a "plugin"
binary named "amazon". This binary will make one or more components available to
use. Valid types for plugins are down this page.
Once the plugin is named properly, Packer automatically discovers plugins in
the following directories in the given order. If a conflicting plugin is found
later, it will take precedence over one found earlier.
1. The directory where `packer` is, or the executable directory.
2. The `$HOME/.packer.d/plugins` directory, if `$HOME` is defined (unix)
3. The `%APPDATA%/packer.d/plugins` if `%APPDATA%` is defined (windows)
4. The `%USERPROFILE%/packer.d/plugins` if `%USERPROFILE%` is defined
(windows)
5. The current working directory.
6. The directory defined in the env var `PACKER_PLUGIN_PATH`. There can be more
than one directory defined; for example, `~/custom-dir-1:~/custom-dir-2`.
Separate directories in the PATH string using a colon (`:`) on posix systems and
a semicolon (`;`) on windows systems. The above example path would be able to
find a provisioner named `packer-provisioner-foo` in either
`~/custom-dir-1/packer-provisioner-foo` or
`~/custom-dir-2/packer-provisioner-foo`.
The valid types for plugins are:
- `plugin` - A plugin binary that can contain one or more of each Packer component
type.
- `builder` - Plugins responsible for building images for a specific
platform.
- `post-processor` - A post-processor responsible for taking an artifact from
a builder and turning it into something else.
- `provisioner` - A provisioner to install software on images created by a
builder.
</Tab>
<Tab heading="manually (single-component plugin)">
The easiest way to manually install a plugin is to name it correctly, then place
it in the proper directory. To name a plugin correctly, make sure the binary is
named `packer-COMPONENT-NAME`. For example, `packer-provisioner-comment` for a "plugin"
binary named "comment". This binary will make a single provisioner named `comment` available to
use. Valid types for plugins are down this page.
Once the plugin is named properly, Packer automatically discovers plugins in
the following directories in the given order. If a conflicting plugin is found
later, it will take precedence over one found earlier.
1. The directory where `packer` is, or the executable directory.
2. The `$HOME/.packer.d/plugins` directory, if `$HOME` is defined (unix)
3. The `%APPDATA%/packer.d/plugins` if `%APPDATA%` is defined (windows)
4. The `%USERPROFILE%/packer.d/plugins` if `%USERPROFILE%` is defined
(windows)
5. The current working directory.
6. The directory defined in the env var `PACKER_PLUGIN_PATH`. There can be more
than one directory defined; for example, `~/custom-dir-1:~/custom-dir-2`.
Separate directories in the PATH string using a colon (`:`) on posix systems and
a semicolon (`;`) on windows systems. The above example path would be able to
find a provisioner named `packer-provisioner-foo` in either
`~/custom-dir-1/packer-provisioner-foo` or
`~/custom-dir-2/packer-provisioner-foo`.
The valid types for plugins are:
- `plugin` - A plugin binary that can contain one or more of each Packer component
type.
- `builder` - Plugins responsible for building images for a specific
platform.
- `post-processor` - A post-processor responsible for taking an artifact from
a builder and turning it into something else.
- `provisioner` - A provisioner to install software on images created by a
builder.
</Tab>
</Tabs>
@include "plugins/installing-plugins.mdx"

@ -0,0 +1,13 @@
## How Plugins Work
Packer plugins are completely separate, standalone applications that the core
of Packer starts and communicates with. Even the components that ship with the
Packer core (core builders, provisioners, and post-processors) are implemented
in a similar way and run as though they are standalone plugins.
These plugin applications aren't meant to be run manually. Instead, Packer core
launches and communicates with them. The next time you run a Packer build,
look at your process list and you should see a handful of `packer-` prefixed
applications running. One of those applications is the core; the rest are
plugins -- one plugin process is launched for each component used in a Packer
build.

@ -0,0 +1,258 @@
## Installing Plugins
Currently, you do not need to install plugins for builder, provisioner, or
post-processor components documented on the Packer website; these components
ship with the Packer core and Packer automatically knows how to find and launch
them. These instructions are for installing custom components that are not
bundled with the Packer core.
The below tabs reference "multi-component" and "single-component" plugins. If
you are not sure what kind of plugin you are trying to install, the easiest way
to find out is to check the name. If the name starts with `packer-plugin-`, then
it is a multi-component plugin. If the name starts with a prefix that actually
says the component type (e.g. `packer-provisioner-` or `packer-builder`), then
it is a single-component plugin.
<Tabs>
<Tab heading="Packer init (recomended from Packer v1.7.0)">
~> **Note**: Only _multi-component plugin binaries_ -- that is plugins named
packer-plugin-\*, like the `packer-plugin-amazon` -- are expected to work with
Packer init. The legacy `builder`, `post-processor` and `provisioner` plugin
types will keep on being detected but Packer cannot install them automatically.
If a plugin you use has not been upgraded to use the multi-component plugin
architecture, contact your maintainer to request an upgrade.
### Create a required_plugins block
1. Add a
[`required_plugins`](/docs/templates/hcl_templates/blocks/packer#specifying-plugin-requirements)
block to your [packer block](/docs/templates/hcl_templates/blocks/packer). Each block will tell Packer what version(s) of a
particular plugin can be installed. Make sure to set a valid [version
constraint string](/docs/templates/hcl_templates/blocks/packer#version-constraints).
Here is an example `required_plugins` block:
```hcl
packer {
required_plugins {
myawesomecloud = {
version = ">= 2.7.0"
source = "github.com/azr/myawesomecloud"
}
happycloud = {
version = ">= 1.1.3"
source = "github.com/azr/happycloud"
}
}
}
```
2. Run [`packer init`](/docs/commands/init) from your project directory (the
directory containing your Packer templates) to install all missing plugin
binaries. Given the above example, Packer will try to look for a GitHub
repository owned by user or organization `azr` named
`packer-plugin-myawesomecloud` and `packer-plugin-happycloud`.
## Names and Addresses
Each plugin has two identifiers:
- A `source` address, which is only necessary when requiring a plugin outside the HashiCorp domain.
- A unique **local name**, which is used everywhere else in a Packer
configuration.
### Local Names
Local names allow you to access the components of a plugin and must be unique
per configuration.
This is best explained using an example. In the above `required_plugins` block,
we declared the local name "myawesomecloud" for the plugin `azr/myawesomecloud`.
If the "myawesomecloud" plugin contains both an "ebs" builder and an "import"
post-processor, then the builder will be accessed in a source block by using:
```hcl
source "myawesomecloud-ebs" "example" {
// builder configuration...
}
```
similarly, the import post-processor would be accessed by declaring the
post-processor block:
```hcl
post-processor "myawesomecloud-import" {
// post-processor configuration...
}
```
If we change the required_plugins block to use a different local name "foo":
```hcl
required_plugins {
foo = {
version = ">= 2.7.0"
source = "github.com/azr/myawesomecloud"
}
}
```
Then we'd instead access that builder using the source:
```hcl
source "foo-ebs" "example" {
// builder configuration...
}
```
## Source Addresses
A plugin's source address is its global identifier. It also tells Packer where
to download it.
Source addresses consist of three parts delimited by slashes (`/`), as
follows:
`<HOSTNAME>/<NAMESPACE>/<TYPE>`
- **Hostname:** The hostname of the location/service that
distributes the plugin. Currently, the only valid "hostname" is github.com,
but we plan to eventually support plugins downloaded from other domains.
- **Namespace:** An organizational namespace within the specified host.
This often is the organization that publishes the plugin.
- **Type:** A short name for the platform or system the plugin manages. The
type is usually the plugin's preferred local name.
For example, the fictional `myawesomecloud` plugin could belong to the
`hashicorp` namespace on `github.com`, so its `source` could be
`github.com/hashicorp/myawesomecloud`,
Note: the actual _repository_ that myawesomecloud comes from must always have
the name format `github.com/hashicorp/packer-plugin-myawesomecloud`, but the
`required_plugins` block omits the redundant `packer-plugin-` repository prefix
for brevity.
The source address with all three components given explicitly is called the
plugin's _fully-qualified address_. You will see fully-qualified address in
various outputs, like error messages.
## Plugin location
@include "plugins/plugin-location.mdx"
## Implicit Github urls
Using the following example :
```hcl
required_plugins {
happycloud = {
version = ">= 2.7.0"
source = "github.com/azr/happycloud"
}
}
```
The plugin getter will look for plugins located at:
- github.com/azr/packer-plugin-happycloud
Packer will error if you set the `packer-plugin-` prefix in a `source`. This
will avoid conflicting with other plugins for other tools, like Terraform.
</Tab>
<Tab heading="manually (multi-component plugin)">
The easiest way to manually install a plugin is to name it correctly, then place
it in the proper directory. To name a plugin correctly, make sure the binary is
named `packer-plugin-NAME`. For example, `packer-plugin-amazon` for a "plugin"
binary named "amazon". This binary will make one or more components available to
use. Valid types for plugins are down this page.
Once the plugin is named properly, Packer automatically discovers plugins in
the following directories in the given order. If a conflicting plugin is found
later, it will take precedence over one found earlier.
1. The directory where `packer` is, or the executable directory.
2. The `$HOME/.packer.d/plugins` directory, if `$HOME` is defined (unix)
3. The `%APPDATA%/packer.d/plugins` if `%APPDATA%` is defined (windows)
4. The `%USERPROFILE%/packer.d/plugins` if `%USERPROFILE%` is defined
(windows)
5. The current working directory.
6. The directory defined in the env var `PACKER_PLUGIN_PATH`. There can be more
than one directory defined; for example, `~/custom-dir-1:~/custom-dir-2`.
Separate directories in the PATH string using a colon (`:`) on posix systems and
a semicolon (`;`) on windows systems. The above example path would be able to
find a provisioner named `packer-provisioner-foo` in either
`~/custom-dir-1/packer-provisioner-foo` or
`~/custom-dir-2/packer-provisioner-foo`.
The valid types for plugins are:
- `plugin` - A plugin binary that can contain one or more of each Packer component
type.
- `builder` - Plugins responsible for building images for a specific
platform.
- `post-processor` - A post-processor responsible for taking an artifact from
a builder and turning it into something else.
- `provisioner` - A provisioner to install software on images created by a
builder.
</Tab>
<Tab heading="manually (single-component plugin)">
The easiest way to manually install a plugin is to name it correctly, then place
it in the proper directory. To name a plugin correctly, make sure the binary is
named `packer-COMPONENT-NAME`. For example, `packer-provisioner-comment` for a "plugin"
binary named "comment". This binary will make a single provisioner named `comment` available to
use. Valid types for plugins are down this page.
Once the plugin is named properly, Packer automatically discovers plugins in
the following directories in the given order. If a conflicting plugin is found
later, it will take precedence over one found earlier.
1. The directory where `packer` is, or the executable directory.
2. The `$HOME/.packer.d/plugins` directory, if `$HOME` is defined (unix)
3. The `%APPDATA%/packer.d/plugins` if `%APPDATA%` is defined (windows)
4. The `%USERPROFILE%/packer.d/plugins` if `%USERPROFILE%` is defined
(windows)
5. The current working directory.
6. The directory defined in the env var `PACKER_PLUGIN_PATH`. There can be more
than one directory defined; for example, `~/custom-dir-1:~/custom-dir-2`.
Separate directories in the PATH string using a colon (`:`) on posix systems and
a semicolon (`;`) on windows systems. The above example path would be able to
find a provisioner named `packer-provisioner-foo` in either
`~/custom-dir-1/packer-provisioner-foo` or
`~/custom-dir-2/packer-provisioner-foo`.
The valid types for plugins are:
- `plugin` - A plugin binary that can contain one or more of each Packer component
type.
- `builder` - Plugins responsible for building images for a specific
platform.
- `post-processor` - A post-processor responsible for taking an artifact from
a builder and turning it into something else.
- `provisioner` - A provisioner to install software on images created by a
builder.
</Tab>
</Tabs>

@ -0,0 +1,17 @@
## Tiers and Namespaces
Packer plugins are published and maintained by a variety of sources, including
HashiCorp, and the Packer community. This website uses tiers and badges to
denote the source of a provider. Additionally, namespaces are used to help users
identify the organization or publisher responsible for the integration, as shown
in the table below.
| Tier | Description | Namespace |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| <PluginBadge type="official" /> | Official plugins are owned and maintained by HashiCorp. | hashicorp |
| <PluginBadge type="community" /> | Community providers are published by individual maintainers, groups of maintainers, or other members of the Packer community. | Third-party organization or maintainer's individual account |
<PluginBadge type="hcp_packer_ready" /> is present when a Packer builder using
this plugin and version will be able to communicate build statuses with HCP
Packer.

@ -0,0 +1,25 @@
---
description: |
Plugins are installable components of Packer
page_title: Packer plugins
---
# Packer plugins
Packer Plugins allow new functionality to be added to Packer without modifying
the core source code. Packer plugins are able to add new components to Packer,
such as builders, provisioners, post-processors, and data sources.
This page documents how to use plugins.
If you're interested in developing plugins, see the [developing
plugins](/docs/plugins/creation#developing-plugins) page.
On the left hand side of this page, you can find the available Packer plugins,
not all of them are 'official'.
@include "plugins/how-plugins-work.mdx"
@include "plugins/plugin-tiers-and-namespaces.mdx"
@include "plugins/installing-plugins.mdx"

@ -726,6 +726,11 @@
{
"title": "Image",
"path": "datasources/hcp/hcp-packer-image"
},
{
"title": "Image-Deprecated",
"path": "datasources/hcp/packer-image-iteration",
"hidden": true
}
]
}
@ -809,6 +814,10 @@
}
]
},
{
"title": "External Plugins",
"href": "/plugins"
},
{
"divider": true
},

@ -0,0 +1,9 @@
[
{
"title": "About External Plugins",
"path": ""
},
{
"divider": true
}
]

@ -10,6 +10,11 @@ export default [
url: '/docs',
type: 'inbound',
},
{
text: 'Plugins',
url: '/plugins',
type: 'inbound',
},
{
text: 'Community',
url: '/community',

@ -3,13 +3,12 @@ import DocsPage from '@hashicorp/react-docs-page'
import Badge from 'components/badge'
import BadgesHeader from 'components/badges-header'
import PluginBadge from 'components/plugin-badge'
import DevAlert from 'components/dev-alert'
import Checklist from 'components/checklist'
// Imports below are only used server-side
import {
generateStaticPaths,
generateStaticProps,
} from 'components/remote-plugin-docs/server'
} from '@hashicorp/react-docs-page/server'
// Configure the docs path and remote plugin docs loading
const additionalComponents = { Badge, BadgesHeader, PluginBadge, Checklist }
@ -18,41 +17,15 @@ const localContentDir = 'content/docs'
const mainBranch = 'master'
const navDataFile = 'data/docs-nav-data.json'
const product = { name: productName, slug: productSlug }
const remotePluginsFile = 'data/docs-remote-plugins.json'
function DocsLayout({ isDevMissingRemotePlugins, ...props }) {
return (
<>
{isDevMissingRemotePlugins ? (
<DevAlert>
<strong className="g-type-label-strong">
Note for local development
</strong>
<p>
<span role="img" aria-label="Alert: ">
🚨
</span>{' '}
<strong>This preview is missing plugin docs</strong> pulled from
remote repos.
</p>
<p>
<span role="img" aria-label="Fix: ">
🛠
</span>{' '}
<strong>To preview docs pulled from plugin repos</strong>, please
include a <code>GITHUB_API_TOKEN</code> in{' '}
<code>website/.env.local</code>.
</p>
</DevAlert>
) : null}
<DocsPage
additionalComponents={additionalComponents}
baseRoute={baseRoute}
product={product}
staticProps={props}
/>
</>
<DocsPage
additionalComponents={additionalComponents}
baseRoute={baseRoute}
product={product}
staticProps={props}
/>
)
}
@ -60,7 +33,6 @@ export async function getStaticPaths() {
const paths = await generateStaticPaths({
localContentDir,
navDataFile,
remotePluginsFile,
})
return { paths, fallback: false }
}
@ -73,7 +45,6 @@ export async function getStaticProps({ params }) {
navDataFile,
params,
product,
remotePluginsFile,
})
return { props }
}

@ -0,0 +1,87 @@
import { productName, productSlug } from 'data/metadata'
import DocsPage from '@hashicorp/react-docs-page'
import { NextPage, InferGetStaticPropsType } from 'next'
import Badge from 'components/badge'
import BadgesHeader from 'components/badges-header'
import PluginBadge from 'components/plugin-badge'
import DevAlert from 'components/dev-alert'
import Checklist from 'components/checklist'
// Imports below are only used server-side
import {
generateStaticPaths,
generateStaticProps,
} from 'components/remote-plugin-docs/server'
// Configure the docs path and remote plugin docs loading
const additionalComponents = { Badge, BadgesHeader, PluginBadge, Checklist }
const baseRoute = 'plugins'
const localContentDir = 'content/plugins'
const mainBranch = 'master'
const navDataFile = 'data/plugins-nav-data.json'
const product = { name: productName, slug: productSlug }
const remotePluginsFile = 'data/plugins-manifest.json'
type Props = InferGetStaticPropsType<typeof getStaticProps>
const DocsLayout: NextPage<Props> = ({
isDevMissingRemotePlugins,
...props
}) => {
return (
<>
{isDevMissingRemotePlugins ? (
<DevAlert>
<strong className="g-type-label-strong">
Note for local development
</strong>
<p>
<span role="img" aria-label="Alert: ">
🚨
</span>{' '}
<strong>This preview is missing plugin docs</strong> pulled from
remote repos.
</p>
<p>
<span role="img" aria-label="Fix: ">
🛠
</span>{' '}
<strong>To preview docs pulled from plugin repos</strong>, please
include a <code>GITHUB_API_TOKEN</code> in{' '}
<code>website/.env.local</code>.
</p>
</DevAlert>
) : null}
<DocsPage
additionalComponents={additionalComponents}
baseRoute={baseRoute}
product={product}
// @ts-expect-error
staticProps={props}
/>
</>
)
}
export async function getStaticPaths() {
const paths = await generateStaticPaths({
navDataFile,
remotePluginsFile,
})
return { paths, fallback: false }
}
export async function getStaticProps({ params }) {
const props = await generateStaticProps({
additionalComponents,
localContentDir,
mainBranch,
navDataFile,
params,
product,
remotePluginsFile,
})
return { props }
}
export default DocsLayout

@ -1,3 +1,100 @@
// Redirects for Packer Plugins extraction
// prettier-ignore
const pluginsExtractionRedirects = [
// base
{source: '/docs/:type/oneandone', destination: '/plugins/:type/oneandone', permanent: true},
{source: '/docs/:type/alicloud', destination: '/plugins/:type/alicloud', permanent: true},
{source: '/docs/:type/amazon', destination: '/plugins/:type/amazon', permanent: true},
{source: '/docs/:type/anka', destination: '/plugins/:type/anka', permanent: true},
{source: '/docs/:type/azure', destination: '/plugins/:type/azure', permanent: true},
{source: '/docs/:type/cloudstack', destination: '/plugins/:type/cloudstack', permanent: true},
{source: '/docs/:type/digitalocean', destination: '/plugins/:type/digitalocean', permanent: true},
{source: '/docs/:type/docker', destination: '/plugins/:type/docker', permanent: true},
{source: '/docs/:type/googlecompute', destination: '/plugins/:type/googlecompute', permanent: true},
{source: '/docs/:type/hashicups', destination: '/plugins/:type/hashicups', permanent: true},
{source: '/docs/:type/hetzner-cloud', destination: '/plugins/:type/hetzner-cloud', permanent: true},
{source: '/docs/:type/huaweicloud', destination: '/plugins/:type/huaweicloud', permanent: true},
{source: '/docs/:type/hyperv', destination: '/plugins/:type/hyperv', permanent: true},
{source: '/docs/:type/hyperone', destination: '/plugins/:type/hyperone', permanent: true},
{source: '/docs/:type/jdcloud', destination: '/plugins/:type/jdcloud', permanent: true},
{source: '/docs/:type/kamatera', destination: '/plugins/:type/kamatera', permanent: true},
{source: '/docs/:type/linode', destination: '/plugins/:type/linode', permanent: true},
{source: '/docs/:type/lxc', destination: '/plugins/:type/lxc', permanent: true},
{source: '/docs/:type/lxd', destination: '/plugins/:type/lxd', permanent: true},
{source: '/docs/:type/ncloud', destination: '/plugins/:type/ncloud', permanent: true},
{source: '/docs/:type/openstack', destination: '/plugins/:type/openstack', permanent: true},
{source: '/docs/:type/oracle', destination: '/plugins/:type/oracle', permanent: true},
{source: '/docs/:type/outscale', destination: '/plugins/:type/outscale', permanent: true},
{source: '/docs/:type/parallels', destination: '/plugins/:type/parallels', permanent: true},
{source: '/docs/:type/profitbricks', destination: '/plugins/:type/profitbricks', permanent: true},
{source: '/docs/:type/proxmox', destination: '/plugins/:type/proxmox', permanent: true},
{source: '/docs/:type/qemu', destination: '/plugins/:type/qemu', permanent: true},
{source: '/docs/:type/scaleway', destination: '/plugins/:type/scaleway', permanent: true},
{source: '/docs/:type/tencentcloud', destination: '/plugins/:type/tencentcloud', permanent: true},
{source: '/docs/:type/triton', destination: '/plugins/:type/triton', permanent: true},
{source: '/docs/:type/ucloud', destination: '/plugins/:type/ucloud', permanent: true},
{source: '/docs/:type/vagrant', destination: '/plugins/:type/vagrant', permanent: true},
{source: '/docs/:type/virtualbox', destination: '/plugins/:type/virtualbox', permanent: true},
{source: '/docs/:type/vmware', destination: '/plugins/:type/vmware', permanent: true},
{source: '/docs/:type/vsphere', destination: '/plugins/:type/vsphere', permanent: true},
{source: '/docs/:type/vultr', destination: '/plugins/:type/vultr', permanent: true},
{source: '/docs/:type/yandex', destination: '/plugins/:type/yandex', permanent: true},
{source: '/docs/:type/git', destination: '/plugins/:type/git', permanent: true},
{source: '/docs/:type/sshkey', destination: '/plugins/:type/sshkey', permanent: true},
{source: '/docs/:type/ansible', destination: '/plugins/:type/ansible', permanent: true},
{source: '/docs/:type/chef', destination: '/plugins/:type/chef', permanent: true},
{source: '/docs/:type/converge', destination: '/plugins/:type/converge', permanent: true},
{source: '/docs/:type/inspec', destination: '/plugins/:type/inspec', permanent: true},
{source: '/docs/:type/puppet', destination: '/plugins/:type/puppet', permanent: true},
{source: '/docs/:type/salt', destination: '/plugins/:type/salt', permanent: true},
// nested
{source: '/docs/:type/oneandone/:path', destination: '/plugins/:type/oneandone/:path', permanent: true},
{source: '/docs/:type/alicloud/:path', destination: '/plugins/:type/alicloud/:path', permanent: true},
{source: '/docs/:type/amazon/:path', destination: '/plugins/:type/amazon/:path', permanent: true},
{source: '/docs/:type/anka/:path', destination: '/plugins/:type/anka/:path', permanent: true},
{source: '/docs/:type/azure/:path', destination: '/plugins/:type/azure/:path', permanent: true},
{source: '/docs/:type/cloudstack/:path', destination: '/plugins/:type/cloudstack/:path', permanent: true},
{source: '/docs/:type/digitalocean/:path', destination: '/plugins/:type/digitalocean/:path', permanent: true},
{source: '/docs/:type/docker/:path', destination: '/plugins/:type/docker/:path', permanent: true},
{source: '/docs/:type/googlecompute/:path', destination: '/plugins/:type/googlecompute/:path', permanent: true},
{source: '/docs/:type/hashicups/:path', destination: '/plugins/:type/hashicups/:path', permanent: true},
{source: '/docs/:type/hetzner-cloud/:path', destination: '/plugins/:type/hetzner-cloud/:path', permanent: true},
{source: '/docs/:type/huaweicloud/:path', destination: '/plugins/:type/huaweicloud/:path', permanent: true},
{source: '/docs/:type/hyperv/:path', destination: '/plugins/:type/hyperv/:path', permanent: true},
{source: '/docs/:type/hyperone/:path', destination: '/plugins/:type/hyperone/:path', permanent: true},
{source: '/docs/:type/jdcloud/:path', destination: '/plugins/:type/jdcloud/:path', permanent: true},
{source: '/docs/:type/kamatera/:path', destination: '/plugins/:type/kamatera/:path', permanent: true},
{source: '/docs/:type/linode/:path', destination: '/plugins/:type/linode/:path', permanent: true},
{source: '/docs/:type/lxc/:path', destination: '/plugins/:type/lxc/:path', permanent: true},
{source: '/docs/:type/lxd/:path', destination: '/plugins/:type/lxd/:path', permanent: true},
{source: '/docs/:type/ncloud/:path', destination: '/plugins/:type/ncloud/:path', permanent: true},
{source: '/docs/:type/openstack/:path', destination: '/plugins/:type/openstack/:path', permanent: true},
{source: '/docs/:type/oracle/:path', destination: '/plugins/:type/oracle/:path', permanent: true},
{source: '/docs/:type/outscale/:path', destination: '/plugins/:type/outscale/:path', permanent: true},
{source: '/docs/:type/parallels/:path', destination: '/plugins/:type/parallels/:path', permanent: true},
{source: '/docs/:type/profitbricks/:path', destination: '/plugins/:type/profitbricks/:path', permanent: true},
{source: '/docs/:type/proxmox/:path', destination: '/plugins/:type/proxmox/:path', permanent: true},
{source: '/docs/:type/qemu/:path', destination: '/plugins/:type/qemu/:path', permanent: true},
{source: '/docs/:type/scaleway/:path', destination: '/plugins/:type/scaleway/:path', permanent: true},
{source: '/docs/:type/tencentcloud/:path', destination: '/plugins/:type/tencentcloud/:path', permanent: true},
{source: '/docs/:type/triton/:path', destination: '/plugins/:type/triton/:path', permanent: true},
{source: '/docs/:type/ucloud/:path', destination: '/plugins/:type/ucloud/:path', permanent: true},
{source: '/docs/:type/vagrant/:path', destination: '/plugins/:type/vagrant/:path', permanent: true},
{source: '/docs/:type/virtualbox/:path', destination: '/plugins/:type/virtualbox/:path', permanent: true},
{source: '/docs/:type/vmware/:path', destination: '/plugins/:type/vmware/:path', permanent: true},
{source: '/docs/:type/vsphere/:path', destination: '/plugins/:type/vsphere/:path', permanent: true},
{source: '/docs/:type/vultr/:path', destination: '/plugins/:type/vultr/:path', permanent: true},
{source: '/docs/:type/yandex/:path', destination: '/plugins/:type/yandex/:path', permanent: true},
{source: '/docs/:type/git/:path', destination: '/plugins/:type/git/:path', permanent: true},
{source: '/docs/:type/sshkey/:path', destination: '/plugins/:type/sshkey/:path', permanent: true},
{source: '/docs/:type/ansible/:path', destination: '/plugins/:type/ansible/:path', permanent: true},
{source: '/docs/:type/chef/:path', destination: '/plugins/:type/chef/:path', permanent: true},
{source: '/docs/:type/converge/:path', destination: '/plugins/:type/converge/:path', permanent: true},
{source: '/docs/:type/inspec/:path', destination: '/plugins/:type/inspec/:path', permanent: true},
{source: '/docs/:type/puppet/:path', destination: '/plugins/:type/puppet/:path', permanent: true},
{source: '/docs/:type/salt/:path', destination: '/plugins/:type/salt/:path', permanent: true},
]
module.exports = [
{
source: '/home',
@ -226,4 +323,6 @@ module.exports = [
// disallow '.html' or '/index.html' in favor of cleaner, simpler paths
{ source: '/:path*/index', destination: '/:path*', permanent: true },
{ source: '/:path*.html', destination: '/:path*', permanent: true },
...pluginsExtractionRedirects,
]

@ -5,7 +5,10 @@ const {
indexContent,
getDocsSearchObject,
} = require('@hashicorp/react-search/tools')
const resolveNavData = require('../components/remote-plugin-docs/utils/resolve-nav-data')
const {
resolveNavData,
} = require('@hashicorp/react-docs-page/server/resolve-nav-data')
const resolveNavDataWithRemotePlugins = require('../components/remote-plugin-docs/utils/resolve-nav-data')
// Run indexing
indexContent({ getSearchObjects })
@ -18,8 +21,7 @@ async function getSearchObjects() {
async function fetchDocsObjects() {
const navFile = 'data/docs-nav-data.json'
const contentDir = 'content/docs'
const opts = { remotePluginsFile: 'data/docs-remote-plugins.json' }
const navData = await resolveNavData(navFile, contentDir, opts)
const navData = await resolveNavData(navFile, contentDir)
return await searchObjectsFromNavData(navData, 'docs')
}
// Fetch objects for `guides` content
@ -36,12 +38,20 @@ async function getSearchObjects() {
const navData = await resolveNavData(navFile, contentDir)
return await searchObjectsFromNavData(navData, 'intro')
}
async function fetchPluginsObjects() {
const navFile = 'data/plugins-nav-data.json'
const opts = { remotePluginsFile: 'data/plugins-manifest.json' }
const navData = await resolveNavDataWithRemotePlugins(navFile, opts)
return await searchObjectsFromNavData(navData, 'plugins')
}
// Collect, flatten and return the collected search objects
const searchObjects = (
await Promise.all([
fetchDocsObjects(),
fetchGuidesObjects(),
fetchIntroObjects(),
fetchPluginsObjects(),
])
).reduce((acc, array) => acc.concat(array), [])
return searchObjects

Loading…
Cancel
Save