Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd: Add --init for installing plugins automatically #1119

Merged
merged 1 commit into from
May 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/vendor
/dist
tflint-ruleset-*
!tflint-ruleset-*/
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ install:
go install

e2e: prepare install
go test -timeout 5m ./integrationtest/inspection ./integrationtest/langserver ./integrationtest/bundled
go test -timeout 5m ./integrationtest/inspection ./integrationtest/langserver ./integrationtest/bundled ./integrationtest/init

lint:
go run golang.org/x/lint/golint --set_exit_status $$(go list ./...)
Expand Down
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,27 @@ For AWS users, you can use the bundled plugin built into the TFLint binary witho

Rules for the Terraform Language is built into the TFLint binary, so you don't need to install any plugins. Please see [Rules](docs/rules) for a list of available rules.

If you want to extend TFLint with other plugins, you can declare the plugins in the config file and easily install them with `tflint --init`.

```hcl
plugin "foo" {
enabled = true
version = "0.1.0"
source = "github.com/org/tflint-ruleset-foo"

signing_key = <<-KEY
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQINBFzpPOMBEADOat4P4z0jvXaYdhfy+UcGivb2XYgGSPQycTgeW1YuGLYdfrwz
9okJj9pMMWgt/HpW8WrJOLv7fGecFT3eIVGDOzyT8j2GIRJdXjv8ZbZIn1Q+1V72
AkqlyThflWOZf8GFrOw+UAR1OASzR00EDxC9BqWtW5YZYfwFUQnmhxU+9Cd92e6i
...
KEY
}
```

See also [Configuring Plugins](docs/user-guide/plugins.md).

## Usage

TFLint inspects files under the current directory by default. You can change the behavior with the following options/arguments:
Expand All @@ -70,6 +91,7 @@ Usage:

Application Options:
-v, --version Print TFLint version
--init Install plugins
--langserver Start language server
-f, --format=[default|json|checkstyle|junit|compact] Output format (default: default)
-c, --config=FILE Config file name (default: .tflint.hcl)
Expand All @@ -83,7 +105,7 @@ Application Options:
--module Inspect modules
--force Return zero exit status even if issues found
--no-color Disable colorized output
--loglevel=[trace|debug|info|warn|error] Change the loglevel (default: none)
--loglevel=[trace|debug|info|warn|error] Change the loglevel

Help Options:
-h, --help Show this help message
Expand Down
2 changes: 2 additions & 0 deletions cmd/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ func (cli *CLI) Run(args []string) int {
switch {
case opts.Version:
return cli.printVersion(opts)
case opts.Init:
return cli.init(opts)
case opts.Langserver:
return cli.startLanguageServer(opts.Config, opts.toConfig())
case opts.ActAsAwsPlugin:
Expand Down
55 changes: 55 additions & 0 deletions cmd/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package cmd

import (
"fmt"
"os"

"github.com/fatih/color"
tfplugin "github.com/terraform-linters/tflint/plugin"
"github.com/terraform-linters/tflint/tflint"
)

func (cli *CLI) init(opts Options) int {
cfg, err := tflint.LoadConfig(opts.Config)
if err != nil {
cli.formatter.Print(tflint.Issues{}, tflint.NewContextError("Failed to load TFLint config", err), map[string][]byte{})
return ExitCodeError
}

for _, pluginCfg := range cfg.Plugins {
installCfg := tfplugin.NewInstallConfig(pluginCfg)

// If version or source is not set, you need to install it manually
if installCfg.ManuallyInstalled() {
continue
}

_, err := tfplugin.FindPluginPath(installCfg)
if os.IsNotExist(err) {
fmt.Fprintf(cli.outStream, "Installing `%s` plugin...\n", pluginCfg.Name)

sigchecker := tfplugin.NewSignatureChecker(installCfg)
if !sigchecker.HasSigningKey() {
color.New(color.FgYellow).Fprintln(cli.outStream, "No signing key configured. Set `signing_key` to verify that the release is signed by the plugin developer")
}

_, err = installCfg.Install()
if err != nil {
cli.formatter.Print(tflint.Issues{}, tflint.NewContextError("Failed to install a plugin", err), map[string][]byte{})
return ExitCodeError
}

fmt.Fprintf(cli.outStream, "Installed `%s` (source: %s, version: %s)\n", pluginCfg.Name, pluginCfg.Source, pluginCfg.Version)
continue
}

if err != nil {
cli.formatter.Print(tflint.Issues{}, tflint.NewContextError("Failed to find a plugin", err), map[string][]byte{})
return ExitCodeError
}

fmt.Fprintf(cli.outStream, "Plugin `%s` is already installed\n", pluginCfg.Name)
}

return ExitCodeOK
}
1 change: 1 addition & 0 deletions cmd/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
// Options is an option specified by arguments.
type Options struct {
Version bool `short:"v" long:"version" description:"Print TFLint version"`
Init bool `long:"init" description:"Install plugins"`
Langserver bool `long:"langserver" description:"Start language server"`
Format string `short:"f" long:"format" description:"Output format" choice:"default" choice:"json" choice:"checkstyle" choice:"junit" choice:"compact" default:"default"`
Config string `short:"c" long:"config" description:"Config file name" value-name:"FILE" default:".tflint.hcl"`
Expand Down
58 changes: 56 additions & 2 deletions docs/developer-guide/plugins.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,59 @@
# Writing Plugins

If you want to add or change rules, you need to write plugins. When changing plugins, refer to the repository of each plugin and refer to how to build and install.
If you want to add custom rules, you can write ruleset plugins.

If you want to create a new plugin, please refer to [tflint-ruleset-template](https://github.com/terraform-linters/tflint-ruleset-template). The plugin can use [tflint-plugin-sdk](https://github.com/terraform-linters/tflint-plugin-sdk) to communicate with the host process. You can easily create a new repository from "Use this template".
## Overview

Plugins are independent binaries and use [go-plugin](https://github.com/hashicorp/go-plugin) to communicate with TFLint over RPC. TFLint executes the binary when the plugin is enabled, and the plugin process must act as an RPC server for TFLint.

If you want to create a new plugin, [The template repository](https://github.com/terraform-linters/tflint-ruleset-template) is available to satisfy these specification. You can create your own repository from "Use this template" and easily add rules based on some reference rules.

The template repository uses the [SDK](https://github.com/terraform-linters/tflint-plugin-sdk) that wraps the go-plugin for communication with TFLint. See also the [Architecture](https://github.com/terraform-linters/tflint-plugin-sdk#architecture) section for the architecture of the plugin system.

## 1. Creating a repository from the template

Visit [tflint-ruleset-template](https://github.com/terraform-linters/tflint-ruleset-template) and click the "Use this template" button. Repository name must be `tflint-ruleset-*`.

## 2. Building and installing the plugin

The created repository can be installed locally with `make install`. Enable the plugin as follows and verify that the installed plugin works.

```hcl
plugin "template" {
enabled = true
}
```

```console
$ make install
go build
mkdir -p ~/.tflint.d/plugins
mv ./tflint-ruleset-template ~/.tflint.d/plugins
$ tflint -v
TFLint version 0.28.1
+ ruleset.template (0.1.0)
```

## 3. Changing/Adding the rules

Rename the ruleset and add/edit rules. After making changes, you can check the behavior with `make install`. See also the [tflint-plugin-sdk API reference](https://pkg.go.dev/github.com/terraform-linters/tflint-plugin-sdk) for communication with the host process.

## 4. Creating a GitHub Release

You can build and install your own ruleset locally as described above, but you can also install it automatically with `tflint --init`.

The requirements to support automatic installation are as follows:

- The built plugin binaries must be published on GitHub Release
- The release must be tagged with a name like `v1.1.1`
- The release must contain an asset with a name like `tflint-ruleset-{name}_{GOOS}_{GOARCH}.zip`
- The zip file must contain a binary named `tflint-ruleset-{name}` (`tflint-ruleset-{name}.exe` in Windows)
- The release must contain a checksum file for the zip file with the name `checksums.txt`
- The checksum file must contain a sha256 hash and filename

When signing a release, the release must additionally meet the following requirements:

- The release must contain a signature file for the checksum file with the name `checksums.txt.sig`
- The signature file must be binary OpenPGP format

Releases that meet these requirements can be easily created by following the GoReleaser config in the template repository.
1 change: 1 addition & 0 deletions docs/user-guide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This guide describes the various features of TFLint for end users.

- [Introduction](../../README.md) (README)
- [Configuring TFLint](config.md)
- [Configuring Plugins](plugins.md)
- [Module Inspection](module-inspection.md)
- [Annotations](annotations.md)
- [Compatibility with Terraform](compatibility.md)
Expand Down
28 changes: 8 additions & 20 deletions docs/user-guide/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ You can change the behavior not only in CLI flags but also in config files. By d
- Current directory (`./.tflint.hcl`)
- Home directory (`~/.tflint.hcl`)

The config file is written in [HCL](https://github.com/hashicorp/hcl/tree/hcl2). An example is shown below:
The config file is written in [HCL](https://github.com/hashicorp/hcl). An example is shown below:

```hcl
config {
Expand All @@ -22,12 +22,14 @@ config {
variables = ["foo=bar", "bar=[\"baz\"]"]
}

rule "aws_instance_invalid_type" {
enabled = false
}

plugin "aws" {
enabled = true
version = "0.4.0"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}

rule "aws_instance_invalid_type" {
enabled = false
}
```

Expand Down Expand Up @@ -146,18 +148,4 @@ Each rule can have its own configs. See the documentation for each rule for deta

## `plugin` blocks

You can enable each plugin in the `plugin` block. All plugins have the `enabled` attribute, and when it is true, the plugin is enabled.

```hcl
plugin "NAME" {
enabled = true
}
```

When the plugin is enabled, TFLint invokes the `tflint-ruleset-<NAME>` (`tflint-ruleset-<NAME>.exe` on Windows) binary in the `~/.tflint.d/plugins` (or `./.tflint.d/plugins`) directory. So you should install the binary into the directory in advance.

**NOTE:** AWS plugin is bundled with the TFLint binary for backward compatibility, so you can use it without installing it separately. And it is automatically enabled when your Terraform configuration requires AWS provider.

You can also change the plugin directory with the `TFLINT_PLUGIN_DIR` environment variable.

Each plugin can have its own configs. See the documentation for each plugin for details.
You can declare the plugin to use. See [Configuring Plugins](plugins.md)
73 changes: 73 additions & 0 deletions docs/user-guide/plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Configuring Plugins

You can extend TFLint by installing any plugin. Declare plugins you want to use in the config file as follows:

```hcl
plugin "foo" {
enabled = true
version = "0.1.0"
source = "github.com/org/tflint-ruleset-foo"

signing_key = <<-KEY
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQINBFzpPOMBEADOat4P4z0jvXaYdhfy+UcGivb2XYgGSPQycTgeW1YuGLYdfrwz
9okJj9pMMWgt/HpW8WrJOLv7fGecFT3eIVGDOzyT8j2GIRJdXjv8ZbZIn1Q+1V72
AkqlyThflWOZf8GFrOw+UAR1OASzR00EDxC9BqWtW5YZYfwFUQnmhxU+9Cd92e6i
...
KEY
}
```

After declaring the `version` and `source`, `tflint --init` can automatically install the plugin.

```console
$ tflint --init
Installing `foo` plugin...
Installed `foo` (source: github.com/org/tflint-ruleset-foo, version: 0.1.0)
$ tflint -v
TFLint version 0.28.1
+ ruleset.foo (0.1.0)
```

See also [Configuring TFLint](config.md) for the config file schema.

## Attributes

This section describes the attributes reserved by TFLint. Except for these, each plugin can extend the schema by defining any attributes/blocks. See the documentation for each plugin for details.

### `enabled` (required)

Enable the plugin. If set to false, the rules will not be used even if the plugin is installed.

### `source`

The source URL to install the plugin. Must be in the format `github.com/org/repo`.

### `version`

Plugin version. Do not prefix with "v". This attribute cannot be omitted when the `source` is set. Version constraints (like `>= 0.3`) are not supported.

### `signing_key`

Plugin developer's PGP public signing key. When this attribute is set, TFLint will automatically verify the signature of the checksum file downloaded from GitHub. It is recommended to set it to prevent supply chain attacks.

Plugins under the terraform-linters organization (AWS/GCP/Azure ruleset plugins) can use the built-in signing key, so this attribute can be omitted.

## Compatibility Notice

AWS plugin is bundled with the TFLint binary for backward compatibility, so you can use it without installing it separately. And it is automatically enabled when your Terraform configuration requires AWS provider.

## Advanced Usage

You can also install the plugin manually. This is mainly useful for plugin development and for plugins that are not published on GitHub. In that case, omit the `source` and `version` attributes.

```hcl
plugin "foo" {
enabled = true
}
```

When the plugin is enabled, TFLint invokes the `tflint-ruleset-<NAME>` (`tflint-ruleset-<NAME>.exe` on Windows) binary in the `~/.tflint.d/plugins` (or `./.tflint.d/plugins`) directory. So you should move the binary into the directory in advance.

You can also change the plugin directory with the `TFLINT_PLUGIN_DIR` environment variable.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/fatih/color v1.10.0
github.com/golang/mock v1.5.0
github.com/google/go-cmp v0.5.5
github.com/google/go-github/v35 v35.2.0
github.com/hashicorp/go-plugin v1.4.1
github.com/hashicorp/go-version v1.3.0
github.com/hashicorp/hcl/v2 v2.10.0
Expand All @@ -21,5 +22,6 @@ require (
github.com/terraform-linters/tflint-plugin-sdk v0.8.2
github.com/terraform-linters/tflint-ruleset-aws v0.4.0
github.com/zclconf/go-cty v1.8.3
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
golang.org/x/lint v0.0.0-20200302205851-738671d3881b
)
Loading