From b3341164cf40c085c8556b4c7a13dfbe2f8b01af Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 11 Jan 2022 10:26:05 -0500 Subject: [PATCH] feat: extract `/plugins` (#11464) * feat: extract `/plugins` Co-authored-by: Zachary Shilton <4624598+zchsh@users.noreply.github.com> Co-authored-by: Adrien Delorme --- .../components/remote-plugin-docs/server.js | 13 +- .../utils/resolve-nav-data.js | 145 +++------ website/content/docs/plugins/index.mdx | 297 +----------------- .../partials/plugins/how-plugins-work.mdx | 13 + .../partials/plugins/installing-plugins.mdx | 258 +++++++++++++++ .../plugins/plugin-tiers-and-namespaces.mdx | 17 + website/content/plugins/index.mdx | 25 ++ website/data/docs-nav-data.json | 9 + ...ote-plugins.json => plugins-manifest.json} | 0 website/data/plugins-nav-data.json | 9 + website/data/subnav.js | 5 + website/pages/docs/[[...page]].jsx | 43 +-- website/pages/plugins/[[...page]].tsx | 87 +++++ website/redirects.next.js | 99 ++++++ website/scripts/index_search_content.js | 16 +- 15 files changed, 605 insertions(+), 431 deletions(-) create mode 100644 website/content/partials/plugins/how-plugins-work.mdx create mode 100644 website/content/partials/plugins/installing-plugins.mdx create mode 100644 website/content/partials/plugins/plugin-tiers-and-namespaces.mdx create mode 100644 website/content/plugins/index.mdx rename website/data/{docs-remote-plugins.json => plugins-manifest.json} (100%) create mode 100644 website/data/plugins-nav-data.json create mode 100644 website/pages/plugins/[[...page]].tsx diff --git a/website/components/remote-plugin-docs/server.js b/website/components/remote-plugin-docs/server.js index 26522e520..04230c9ba 100644 --- a/website/components/remote-plugin-docs/server.js +++ b/website/components/remote-plugin-docs/server.js @@ -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 } diff --git a/website/components/remote-plugin-docs/utils/resolve-nav-data.js b/website/components/remote-plugin-docs/utils/resolve-nav-data.js index 21e8ae518..49f76bf96 100644 --- a/website/components/remote-plugin-docs/utils/resolve-nav-data.js +++ b/website/components/remote-plugin-docs/utils/resolve-nav-data.js @@ -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} 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 diff --git a/website/content/docs/plugins/index.mdx b/website/content/docs/plugins/index.mdx index d0218ffa1..b0b80497f 100644 --- a/website/content/docs/plugins/index.mdx +++ b/website/content/docs/plugins/index.mdx @@ -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 | -| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | -| | Official plugins are owned and maintained by HashiCorp. | hashicorp | -| | 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. - - - - -~> **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:** 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. - - - - -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. - - - - -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. - - - +@include "plugins/installing-plugins.mdx" diff --git a/website/content/partials/plugins/how-plugins-work.mdx b/website/content/partials/plugins/how-plugins-work.mdx new file mode 100644 index 000000000..0c90a0a85 --- /dev/null +++ b/website/content/partials/plugins/how-plugins-work.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. diff --git a/website/content/partials/plugins/installing-plugins.mdx b/website/content/partials/plugins/installing-plugins.mdx new file mode 100644 index 000000000..fa2ff799f --- /dev/null +++ b/website/content/partials/plugins/installing-plugins.mdx @@ -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. + + + + +~> **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:** 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. + + + + +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. + + + + +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. + + + diff --git a/website/content/partials/plugins/plugin-tiers-and-namespaces.mdx b/website/content/partials/plugins/plugin-tiers-and-namespaces.mdx new file mode 100644 index 000000000..d3c15440b --- /dev/null +++ b/website/content/partials/plugins/plugin-tiers-and-namespaces.mdx @@ -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 | +| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | +| | Official plugins are owned and maintained by HashiCorp. | hashicorp | +| | 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 | + + + is present when a Packer builder using +this plugin and version will be able to communicate build statuses with HCP +Packer. diff --git a/website/content/plugins/index.mdx b/website/content/plugins/index.mdx new file mode 100644 index 000000000..637d101a6 --- /dev/null +++ b/website/content/plugins/index.mdx @@ -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" diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 21d43e971..b131d274c 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -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 }, diff --git a/website/data/docs-remote-plugins.json b/website/data/plugins-manifest.json similarity index 100% rename from website/data/docs-remote-plugins.json rename to website/data/plugins-manifest.json diff --git a/website/data/plugins-nav-data.json b/website/data/plugins-nav-data.json new file mode 100644 index 000000000..e7c89da48 --- /dev/null +++ b/website/data/plugins-nav-data.json @@ -0,0 +1,9 @@ +[ + { + "title": "About External Plugins", + "path": "" + }, + { + "divider": true + } +] diff --git a/website/data/subnav.js b/website/data/subnav.js index 24a09165a..d5afdc616 100644 --- a/website/data/subnav.js +++ b/website/data/subnav.js @@ -10,6 +10,11 @@ export default [ url: '/docs', type: 'inbound', }, + { + text: 'Plugins', + url: '/plugins', + type: 'inbound', + }, { text: 'Community', url: '/community', diff --git a/website/pages/docs/[[...page]].jsx b/website/pages/docs/[[...page]].jsx index 9feeee42d..78c223c0b 100644 --- a/website/pages/docs/[[...page]].jsx +++ b/website/pages/docs/[[...page]].jsx @@ -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 ? ( - - - Note for local development - -

- - 🚨 - {' '} - This preview is missing plugin docs pulled from - remote repos. -

- -

- - 🛠 - {' '} - To preview docs pulled from plugin repos, please - include a GITHUB_API_TOKEN in{' '} - website/.env.local. -

-
- ) : null} - - + ) } @@ -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 } } diff --git a/website/pages/plugins/[[...page]].tsx b/website/pages/plugins/[[...page]].tsx new file mode 100644 index 000000000..ccfa56a15 --- /dev/null +++ b/website/pages/plugins/[[...page]].tsx @@ -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 +const DocsLayout: NextPage = ({ + isDevMissingRemotePlugins, + ...props +}) => { + return ( + <> + {isDevMissingRemotePlugins ? ( + + + Note for local development + +

+ + 🚨 + {' '} + This preview is missing plugin docs pulled from + remote repos. +

+ +

+ + 🛠 + {' '} + To preview docs pulled from plugin repos, please + include a GITHUB_API_TOKEN in{' '} + website/.env.local. +

+
+ ) : null} + + + ) +} + +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 diff --git a/website/redirects.next.js b/website/redirects.next.js index 47029cea7..6ae2ee5b3 100644 --- a/website/redirects.next.js +++ b/website/redirects.next.js @@ -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, ] diff --git a/website/scripts/index_search_content.js b/website/scripts/index_search_content.js index b3f2f7cab..6ac545372 100644 --- a/website/scripts/index_search_content.js +++ b/website/scripts/index_search_content.js @@ -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