diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c227eab --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,65 @@ +name: CI +on: + pull_request: + push: + branches: + - master +jobs: + build: + runs-on: ubuntu-latest + container: golangci/golangci-lint:v1.44.0 + steps: + - uses: actions/cache@v2 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - uses: actions/checkout@v2 + with: + # Required for Codecov report uploading. + fetch-depth: 0 + - run: make install-changelog BIN_PATH=/usr/local/bin + - run: make test-cover-upload test-update-linters lint test-tidy test-changelog + build-docker: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: docker/build-push-action@v2 + codespell: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: codespell-project/actions-codespell@master + with: + skip: .git,go.sum + check_filenames: true + check_hidden: true + semgrep: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + # Use dedicated action for nice integration with GitHub. + - uses: returntocorp/semgrep-action@v1 + build-release: + runs-on: ubuntu-latest + steps: + - uses: actions/cache@v2 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-go@v2 + with: + go-version: 1.16 + - uses: goreleaser/goreleaser-action@v2 + with: + args: release --snapshot diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..634ec76 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,113 @@ +output: + sort-results: true + +issues: + exclude-use-default: false + max-same-issues: 0 + max-issues-per-linter: 0 + +linters-settings: + errcheck: + check-type-assertions: true + check-blank: true + gci: + local-prefixes: github.com/invidian/terraform-provider-gpg + godot: + capital: true + gofumpt: + extra-rules: true + govet: + enable-all: true + disable: + - fieldalignment + - shadow + makezero: + always: true + nolintlint: + allow-leading-space: false + require-explanation: true + require-specific: true + wsl: + force-err-cuddling: true + +linters: + disable: + # This linter has been deprecated. + - golint + - interfacer + - maligned + enable: + - asciicheck + - bidichk + - bodyclose + - containedctx + - contextcheck + - cyclop + - deadcode + - decorder + - depguard + - dogsled + - dupl + - durationcheck + - errcheck + - errchkjson + - errname + - errorlint + - exhaustive + - exportloopref + - forbidigo + - forcetypeassert + - funlen + - gci + - gochecknoglobals + - gochecknoinits + - gocognit + - goconst + - gocritic + - gocyclo + - godot + - gofmt + - gofumpt + - goheader + - goimports + - gomnd + - gomoddirectives + - gomodguard + - goprintffuncname + - grouper + - ifshort + - importas + - ineffassign + - lll + - maintidx + - makezero + - misspell + - nakedret + - nestif + - nilerr + - nilnil + - nlreturn + - noctx + - nolintlint + - paralleltest + - prealloc + - predeclared + - promlinter + - revive + - rowserrcheck + - sqlclosecheck + - structcheck + - stylecheck + - tagliatelle + - tenv + - thelper + - tparallel + - typecheck + - unconvert + - unparam + - varcheck + - varnamelen + - wastedassign + - whitespace + - wrapcheck + - wsl diff --git a/.semgrep.yml b/.semgrep.yml new file mode 100644 index 0000000..5f9cd55 --- /dev/null +++ b/.semgrep.yml @@ -0,0 +1,25 @@ +rules: + - id: bad-error-wrap-wording + patterns: + - pattern-regex: fmt\.Errorf\(".?([Ff]ailed|[]Uu]nable|[Cc]ould|([Ee]rror)) + message: Word "fail", "failed", "unable", "could not" should not be used when wrapping errors. Use verb in continuous form instead! + languages: [go] + severity: ERROR + - id: printed-messages-must-be-captialized + patterns: + - pattern-regex: (fmt.Print(ln|f)|\bt\.(Error|Fatal|Log)(|f))\("[a-z] + message: Printed messages must be capitalized. + languages: [go] + severity: ERROR + - id: strings-must-be-quoted-using-q + patterns: + - pattern-regex: "'%s'" + message: When formatting, strings must be quoted using %q instead of e.g. '%s'. + languages: [go] + severity: ERROR + - id: error-wrapping-must-be-preceded-by-space + patterns: + - pattern-regex: '[^ ]%w' + message: When wrapping errors, %w must be preceded by space. + languages: [go] + severity: ERROR diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9feae64..0000000 --- a/.travis.yml +++ /dev/null @@ -1,31 +0,0 @@ -language: go - -go: - - '1.15' - - master - -git: - depth: 1 - -notifications: - email: false - -matrix: - allow_failures: - - go: master - -install: make download - -before_script: - - make install-ci - -script: - - make all-cover - -after_script: - - make cover-upload EXIT_CODE=$TRAVIS_TEST_RESULT - -# Build only master branch triggered by merge commits. Other branches should be built via PR trigger. -branches: - only: - - master diff --git a/CHANGELOG.md b/CHANGELOG.md index faade93..a1967a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,54 +5,41 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.3.0] - 2020-08-19 - +## 0.3.0 - 2020-08-19 ### Added - - This provider is now available via [Terraform Registry](https://registry.terraform.io/providers/invidian/gpg/latest). - Added basic unit tests. -### Fixed - -- Fixed `gpg_encrypted_message` resource destroying. -- Fixed found linter warnings. - ### Changed - - Migrated to use Terraform Plugin SDK. - Changelog is now published in [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format. - Updated all dependencies to latest versions. -## [0.2.1] - 2019-06-20 - ### Fixed +- Fixed `gpg_encrypted_message` resource destroying. +- Fixed found linter warnings. -- Fixed `gpg_encrypted_message` resource update issues with Terraform 0.12.x. - +## [0.2.1] - 2019-06-20 ### Changed - - Sensitive fields now use `sensitive: true`, so they do not leak into Terraform plan. - Resource `gpg_encrypted_message` now use SHA256 of message content as resource ID. -### Removed +### Fixed +- Fixed `gpg_encrypted_message` resource update issues with Terraform 0.12.x. +### Removed - Removed use of `SchemaStateFunc` for result, as it has no effect with Terraform 0.12.x. ## [0.2.0] - 2019-06-15 - ### Added - - Added Terraform 0.12 compatibility. ### Changed - - Changed code to standard Terraform provider layout. - Added `Makefile` to document common tasks. ## [0.1.0] - 2019-04-12 - ### Added - - Initial release [0.2.1]: https://github.com/invidian/terraform-provider-gpg/compare/v0.2.0...v0.2.1 diff --git a/Dockerfile b/Dockerfile index 1f23c1c..22ebee3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ ENV GO111MODULE=on RUN apk add curl git build-base # Install linter -RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $HOME/bin v1.16.0 +RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $HOME/bin v1.44.0 # Copy go mod files first and install dependencies to cache this layer ADD ./go.mod ./go.sum /go/src/terraform-provider-gpg/ diff --git a/Makefile b/Makefile index aee0e76..7799ce4 100644 --- a/Makefile +++ b/Makefile @@ -13,8 +13,7 @@ GOBUILD=CGO_ENABLED=$(CGO_ENABLED) $(GOCMD) build -v -buildmode=exe -ldflags $(L GO_PACKAGES=./... GO_TESTS=^.*$ -GOLANGCI_LINT_VERSION=v1.30.0 -DISABLED_LINTERS=gci,goerr113 +GOLANGCI_LINT_VERSION=v1.44.0 BIN_PATH=$$HOME/bin @@ -47,7 +46,7 @@ test: build-test .PHONY: lint lint: - golangci-lint run --enable-all --disable=$(DISABLED_LINTERS) --max-same-issues=0 --max-issues-per-linter=0 --build-tags integration --timeout 10m --exclude-use-default=false $(GO_PACKAGES) + golangci-lint run $(GO_PACKAGES) .PHONY: build-test build-test: @@ -71,15 +70,56 @@ all-cover: build build-test test-cover lint test-cover: build-test $(GOTEST) -run $(GO_TESTS) -coverprofile=$(PROFILEFILE) $(GO_PACKAGES) -.PHONY: cover-upload -cover-upload: codecov - # Make codeclimate as command, as we need to run test-cover twice and make deduplicates that. - # Go test results are cached anyway, so it's fine to run it multiple times. - make codeclimate - -.PHONY: codecov -codecov: PROFILEFILE=coverage.txt -codecov: SHELL=/bin/bash -codecov: test-cover -codecov: - bash <(curl -s https://codecov.io/bash) +.PHONY: test-cover-upload-codecov +test-cover-upload-codecov: SHELL=/bin/bash +test-cover-upload-codecov: test-cover +test-cover-upload-codecov: + bash <(curl -s https://codecov.io/bash) -f $(COVERPROFILE) + +.PHONY: test-cover-upload-codeclimate +test-cover-upload-codeclimate: test-cover +test-cover-upload-codeclimate: + env CC_TEST_REPORTER_ID=$(CC_TEST_REPORTER_ID) cc-test-reporter after-build -t gocov -p $$(go list -m) + +.PHONY: test-cover-upload +test-cover-upload: test-cover-upload-codecov test-cover-upload-codeclimate + +.PHONY: codespell +codespell: + codespell -S .git,state.yaml,go.sum,terraform.tfstate,terraform.tfstate.backup,./local-testing/resources -L uptodate,decorder + +.PHONY: codespell-pr +codespell-pr: + git diff master..HEAD | grep -v ^- | codespell - + git log master..HEAD | codespell - + +.PHONY: test-working-tree-clean +test-working-tree-clean: + @test -z "$$(git status --porcelain)" || (echo "Commit all changes before running this target"; exit 1) + +.PHONY: test-changelog +test-changelog: test-working-tree-clean + make format-changelog + @test -z "$$(git status --porcelain)" || (echo "Please run 'make format-changelog' and commit generated changes."; git diff; exit 1) + +.PHONY: format-changelog +format-changelog: + changelog fmt -o CHANGELOG.md.fmt + mv CHANGELOG.md.fmt CHANGELOG.md + +.PHONY: install-changelog +install-changelog: + go get github.com/rcmachado/changelog@0.7.0 + go mod tidy + +.PHONY: update-linters +update-linters: + # Remove all enabled linters. + sed -i '/^ enable:/q0' .golangci.yml + # Then add all possible linters to config. + golangci-lint linters | grep -E '^\S+:' | cut -d: -f1 | sort | sed 's/^/ - /g' | grep -v -E "($$(grep '^ disable:' -A 100 .golangci.yml | grep -E ' - \S+$$' | awk '{print $$2}' | tr \\n '|' | sed 's/|$$//g'))" >> .golangci.yml + +.PHONY: test-update-linters +test-update-linters: test-working-tree-clean + make update-linters + @test -z "$$(git status --porcelain)" || (echo "Linter configuration outdated. Run 'make update-linters' and commit generated changes to fix."; exit 1) diff --git a/gpg/provider_test.go b/gpg/provider_test.go index d5c9f46..a57af3e 100644 --- a/gpg/provider_test.go +++ b/gpg/provider_test.go @@ -9,7 +9,14 @@ import ( ) func TestProvider(t *testing.T) { - if err := gpg.Provider().(*schema.Provider).InternalValidate(); err != nil { + t.Parallel() + + provider, ok := gpg.Provider().(*schema.Provider) + if !ok { + t.Fatalf("Got unexpected provider type, expected %T, got %T", &schema.Provider{}, gpg.Provider()) + } + + if err := provider.InternalValidate(); err != nil { t.Fatalf("validating provider internally: %v", err) } } diff --git a/gpg/resource_gpg_encrypted_message.go b/gpg/resource_gpg_encrypted_message.go index 21f5bfd..baa3654 100644 --- a/gpg/resource_gpg_encrypted_message.go +++ b/gpg/resource_gpg_encrypted_message.go @@ -60,12 +60,17 @@ func resourceGPGEncryptedMessage() *schema.Resource { } } -func getRecipients(d *schema.ResourceData) ([]*openpgp.Entity, error) { +func getRecipients(data *schema.ResourceData) ([]*openpgp.Entity, error) { // Store recipients for encryption. recipients := []*openpgp.Entity{} // Iterate over public keys, decode, parse, collect their IDs and add to recipients list. - for i, pk := range d.Get("public_keys").([]interface{}) { + publicKeys, ok := data.Get("public_keys").([]interface{}) + if !ok { + return nil, fmt.Errorf("expected type %T on key %q, got %T", "public_keys", []interface{}{}, data.Get("public_keys")) + } + + for i, pk := range publicKeys { recipient, err := entityFromString(pk.(string)) if err != nil { return nil, fmt.Errorf("decoding public key #%d: %w", i, err) @@ -77,7 +82,7 @@ func getRecipients(d *schema.ResourceData) ([]*openpgp.Entity, error) { return recipients, nil } -func savePublicKeys(d *schema.ResourceData, recipients []*openpgp.Entity) error { +func savePublicKeys(data *schema.ResourceData, recipients []*openpgp.Entity) error { // Store ID of each public key, to store them in state (StateFunc does not work for TypeList for some reason). pksIDs := []string{} @@ -85,7 +90,7 @@ func savePublicKeys(d *schema.ResourceData, recipients []*openpgp.Entity) error pksIDs = append(pksIDs, recipient.PrimaryKey.KeyIdString()) } - if err := d.Set("public_keys", pksIDs); err != nil { + if err := data.Set("public_keys", pksIDs); err != nil { return fmt.Errorf("setting %q property: %w", "public_keys", err) } @@ -109,47 +114,47 @@ func encryptMessage(recipients []*openpgp.Entity, message string, destination io return nil } -func encryptAndEncodeMessage(recipients []*openpgp.Entity, message string) (string, error) { +func encryptAndEncodeMessage(recipients []*openpgp.Entity, message string) ([]byte, error) { var buf bytes.Buffer // We produce output in ASCII-armor format. wcEncode, err := armor.Encode(&buf, "PGP MESSAGE", nil) if err != nil { - return "", fmt.Errorf("encoding message: %w", err) + return nil, fmt.Errorf("encoding message: %w", err) } if err := encryptMessage(recipients, message, wcEncode); err != nil { - return "", fmt.Errorf("encrypting message: %w", err) + return nil, fmt.Errorf("encrypting message: %w", err) } if err := wcEncode.Close(); err != nil { - return "", fmt.Errorf("closing encoded message: %w", err) + return nil, fmt.Errorf("closing encoded message: %w", err) } - return buf.String(), nil + return buf.Bytes(), nil } -func resourceGPGEncryptedMessageCreate(d *schema.ResourceData, m interface{}) error { - recipients, err := getRecipients(d) +func resourceGPGEncryptedMessageCreate(data *schema.ResourceData, m interface{}) error { + recipients, err := getRecipients(data) if err != nil { return fmt.Errorf("getting recipients: %w", err) } - if err := savePublicKeys(d, recipients); err != nil { + if err := savePublicKeys(data, recipients); err != nil { return fmt.Errorf("saving public keys: %w", err) } - encryptedMessage, err := encryptAndEncodeMessage(recipients, d.Get("content").(string)) + encryptedMessage, err := encryptAndEncodeMessage(recipients, data.Get("content").(string)) if err != nil { return fmt.Errorf("encrypting message: %w", err) } - if err := d.Set("result", encryptedMessage); err != nil { + if err := data.Set("result", encryptedMessage); err != nil { return fmt.Errorf("setting %q property: %w", "result", err) } // Calculate SHA-256 checksum of message for ID. - d.SetId(sha256sum(encryptedMessage)) + data.SetId(sha256sum(encryptedMessage)) return nil } @@ -179,5 +184,11 @@ func entityFromString(key string) (*openpgp.Entity, error) { } func sha256sum(data interface{}) string { - return fmt.Sprintf("%x", sha256.Sum256([]byte(data.(string)))) + bytes, ok := data.(string) + if !ok { + // There is no way to handle this gracefully with existing SDK. + panic(fmt.Sprintf("Expected state data to be of type %T, got %T", "", data)) + } + + return fmt.Sprintf("%x", sha256.Sum256([]byte(bytes))) }