Skip to content

Commit

Permalink
Update fix command
Browse files Browse the repository at this point in the history
This change is major refactor of the fix command, and it's underlying
fixer interface. This change adds support for the `-diff` flag which
displays the diff between the unfixed and fixed files, if available.

Along with the refactor the ability to apply multiple fixes to the same
file without potential write conflicts where previous changes were
removed after reprocessing.

* Add a scan function that returns a list of files to apply a fix on
to provide flexibility in the future for collecting a list of files.

* Replace cmp.Diff with github.com/pkg/diff for better file diffs
* Return error when missing directory argument
* Add error handling when trying to read any scanned files
  • Loading branch information
nywilken committed Jul 14, 2023
1 parent 5527585 commit 5e36021
Show file tree
Hide file tree
Showing 15 changed files with 317 additions and 206 deletions.
14 changes: 9 additions & 5 deletions cmd/packer-sdc/internal/fix/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
## `fix`
## `packer-sdc fix`

Fix rewrites parts of the plugin codebase to address known issues or common workarounds used within plugins consuming the Packer plugin SDK.
Fix rewrites parts of the plugin codebase to address known issues or common workarounds used within plugins consuming the Packer plugin SDK.

Options:

-check Check for potential fixes [dry-run].
-diff If the -diff flag is set, no files are rewritten. Instead, fix prints the differences a rewrite would introduce.

Available Fixes:

gocty
Adds a replace directive for github.com/zclconf/go-cty to github.com/nywilken/go-cty
gocty Adds a replace directive for github.com/zclconf/go-cty to github.com/nywilken/go-cty


### Related Issues
Use `packer-sdc fix` to resolve the [cty.Value does not implement gob.GobEncoder](https://github.com/hashicorp/packer-plugin-sdk/issues/187)

158 changes: 158 additions & 0 deletions cmd/packer-sdc/internal/fix/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package fix

import (
"bytes"
"errors"
"flag"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/pkg/diff"
)

const cmdPrefix string = "fix"

// Command is the base entry for the fix sub-command.
type Command struct {
Dir string
Diff bool
}

// Flags contain the default flags for the fix sub-command.
func (cmd *Command) Flags() *flag.FlagSet {
fs := flag.NewFlagSet(cmdPrefix, flag.ExitOnError)
fs.BoolVar(&cmd.Diff, "diff", false, "if set prints the differences a rewrite would introduce.")
return fs
}

// Help displays usage for the command.
func (cmd *Command) Help() string {
var s strings.Builder
for _, fix := range availableFixes {
s.WriteString(fmt.Sprintf(" %s\t\t%s\n", fix.name, fix.description))
}

helpText := `
Usage: packer-sdc fix [options] directory
Fix rewrites parts of the plugin codebase to address known issues or
common workarounds used within plugins consuming the Packer plugin SDK.
Options:
-diff If the -diff flag is set fix prints the differences an applied fix would introduce.
Available fixes:
%s`
return fmt.Sprintf(helpText, s.String())
}

// Run executes the command
func (cmd *Command) Run(args []string) int {
if err := cmd.run(args); err != nil {
fmt.Printf("%v", err)
return 1
}
return 0
}

func (cmd *Command) run(args []string) error {
f := cmd.Flags()
err := f.Parse(args)
if err != nil {
return errors.New("unable to parse flags for fix command")
}

if f.NArg() != 1 {
err := fmt.Errorf("packer-sdc fix: missing directory argument\n%s", cmd.Help())
return err
}

dir := f.Arg(0)
if dir == "." || dir == "./..." {
dir, _ = os.Getwd()
}

info, err := os.Stat(dir)
if err != nil && os.IsNotExist(err) {
return errors.New("a plugin root directory must be specified or a dot for the current directory")
}

if !info.IsDir() {
return errors.New("a plugin root directory must be specified or a dot for the current directory")
}

dir, err = filepath.Abs(dir)
if err != nil {
return errors.New("unable to determine the absolute path for the provided plugin root directory")
}
cmd.Dir = dir

return processFiles(cmd.Dir, cmd.Diff)
}

func (cmd *Command) Synopsis() string {
return "Rewrites parts of the plugin codebase to address known issues or common workarounds within plugins consuming the Packer plugin SDK."
}

func processFiles(rootDir string, showDiff bool) error {
srcFiles := make(map[string][]byte)
fixedFiles := make(map[string][]byte)

var hasErrors error
for _, f := range availableFixes {
matches, err := f.scan(rootDir)
if err != nil {
return fmt.Errorf("failed to apply %s fix: %s", f.name, err)
}

//matches contains all files to apply the said fix on
for _, filename := range matches {
if _, ok := srcFiles[filename]; !ok {
bs, err := os.ReadFile(filename)
if err != nil {
hasErrors = errors.Join(hasErrors, err)
}
srcFiles[filename] = bytes.Clone(bs)
}

fixedData, ok := fixedFiles[filename]
if !ok {
fixedData = bytes.Clone(srcFiles[filename])
}

fixedData, err := f.fix(filename, fixedData)
if err != nil {
hasErrors = errors.Join(hasErrors, err)
continue
}
if bytes.Equal(fixedData, srcFiles[filename]) {
continue
}
fixedFiles[filename] = bytes.Clone(fixedData)
}
}

if hasErrors != nil {
return hasErrors
}

if showDiff {
for filename, fixedData := range fixedFiles {
diff.Text(filename, filename+"fixed", string(srcFiles[filename]), string(fixedData), os.Stdout)
}
return nil
}

for filename, fixedData := range fixedFiles {
fmt.Println(filename)
info, _ := os.Stat(filename)
os.WriteFile(filename, fixedData, info.Mode())
}

return nil
}
115 changes: 12 additions & 103 deletions cmd/packer-sdc/internal/fix/fix.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,116 +3,25 @@

package fix

import (
_ "embed" //used for embedding the command README
"flag"
"log"
"os"
"path/filepath"

"github.com/pkg/errors"
)

const cmdPrefix string = "fix"

var (
// availableFixes to apply to a plugin - refer to init func
availableFixes fixes

//go:embed README.md
readme string
)

// Command is the base entry for the fix sub-command
type Command struct {
Dir string
Check bool
// Fixer applies all defined fixes on a plugin dir; a Fixer should be idempotent.
// The caller of any fix is responsible for checking if the file context have changed.
type fixer interface {
fix(filename string, data []byte) ([]byte, error)
}

func (cmd *Command) Flags() *flag.FlagSet {
fs := flag.NewFlagSet(cmdPrefix, flag.ExitOnError)
fs.BoolVar(&cmd.Check, "check", false, "Check plugin for potential fixes [dry-run].")
return fs
}

// Help displays usage for the command.
func (cmd *Command) Help() string {
return "\n" + readme
}

// Run executes the command
func (cmd *Command) Run(args []string) int {
if err := cmd.run(args); err != nil {
log.Printf("%v", err)
return 1
}
return 0
}

func (cmd *Command) run(args []string) error {
f := cmd.Flags()
err := f.Parse(args)
if err != nil {
return errors.Wrap(err, "[packer-sdc fix] unable to parse flags")
}

if f.NArg() != 1 {
cmd.Help()
return errors.New("a plugin root directory must be specified")
}

dir := f.Arg(0)
if dir == "." {
dir, _ = os.Getwd()
}

info, err := os.Stat(dir)
if err != nil && os.IsNotExist(err) {
return errors.New("a plugin root directory must be specified")
}

if !info.IsDir() {
return errors.New("a plugin root directory must be specified")
}

dir, err = filepath.Abs(dir)
if err != nil {
return errors.New("unable to determine the absolute path for the plugin root directory")
}
cmd.Dir = dir

for _, f := range availableFixes {
err = f.Fix(cmd.Dir, cmd.Check)
if err != nil {
return errors.Errorf("failed to apply %s fix: %s", f.Name, err)
}
}
return nil
}

func (cmd *Command) Synopsis() string {
return "Rewrites parts of the plugin codebase to address known issues or common workarounds within the plugin API."
}

// Fixer applies all defined fixes on the provide plugin dir.
// A dryRun argument is available to check if a fix will be applied without executing.
// A Fixer should be idempotent.
type Fixer interface {
Fix(pluginRootDir string, dryRun bool) error
}
type fix struct {
Name, Description string
Fixer
name, description string
scan func(dir string) ([]string, error)
fixer
}

type fixes []fix
var (
// availableFixes to apply to a plugin - refer to init func
availableFixes []fix
)

func init() {
availableFixes = []fix{
{
Name: "gocty",
Description: "Adds a replace directive for github.com/zclconf/go-cty to github.com/nywilken/go-cty",
Fixer: NewGoCtyFixer(),
},
goctyFix,
}
}
Loading

0 comments on commit 5e36021

Please sign in to comment.