-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[HPR-1190] Add
packer-sdc fix
command (#190)
* packer-sdc: Add fix sub-command Fix rewrites parts of the plugin codebase to address known issues or common workarounds used within plugins consuming the Packer plugin SDK. ``` ~> ./packer-sdc fix -check ../../../packer-plugin-tencentcloud Found the working directory /Users/wilken/Development/linux-dev/packer-plugin-tencentcloud gocty Unfixed! ``` * Update fix command 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
Wilken Rivera
authored
Jul 14, 2023
1 parent
2a6d852
commit 7d3a4b2
Showing
22 changed files
with
639 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
## `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. | ||
|
||
Options: | ||
|
||
-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 | ||
|
||
|
||
### 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) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package fix | ||
|
||
// 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) | ||
} | ||
|
||
type fix struct { | ||
name, description string | ||
scan func(dir string) ([]string, error) | ||
fixer | ||
} | ||
|
||
var ( | ||
// availableFixes to apply to a plugin - refer to init func | ||
availableFixes []fix | ||
) | ||
|
||
func init() { | ||
availableFixes = []fix{ | ||
goctyFix, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package fix | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path" | ||
|
||
"golang.org/x/mod/modfile" | ||
) | ||
|
||
const ( | ||
sdkPath string = "github.com/hashicorp/packer-plugin-sdk" | ||
oldPath string = "github.com/zclconf/go-cty" | ||
newPath string = "github.com/nywilken/go-cty" | ||
newVersion string = "1.12.1" | ||
modFilename string = "go.mod" | ||
) | ||
|
||
var goctyFix = fix{ | ||
name: "gocty", | ||
description: "Adds a replace directive for github.com/zclconf/go-cty to github.com/nywilken/go-cty", | ||
scan: modPaths, | ||
fixer: goCtyFix{ | ||
OldPath: oldPath, | ||
NewPath: newPath, | ||
NewVersion: newVersion, | ||
}, | ||
} | ||
|
||
// modPaths scans the incoming dir for potential go.mod files to fix. | ||
func modPaths(dir string) ([]string, error) { | ||
paths := []string{ | ||
path.Join(dir, modFilename), | ||
} | ||
return paths, nil | ||
|
||
} | ||
|
||
type goCtyFix struct { | ||
OldPath, NewPath, NewVersion string | ||
} | ||
|
||
func (f goCtyFix) modFileFormattedVersion() string { | ||
return fmt.Sprintf("v%s", f.NewVersion) | ||
} | ||
|
||
// Fix applies a replace directive in a projects go.mod file for f.OldPath to f.NewPath. | ||
// This fix applies to the replacement of github.com/zclconf/go-cty, as described in https://github.com/hashicorp/packer-plugin-sdk/issues/187 | ||
// The return data contains the data file with the applied fix. In cases where the fix is already applied or not needed the original data is returned. | ||
func (f goCtyFix) fix(modFilePath string, data []byte) ([]byte, error) { | ||
if _, err := os.Stat(modFilePath); err != nil { | ||
return nil, fmt.Errorf("failed to find go.mod file %s", modFilePath) | ||
} | ||
|
||
mf, err := modfile.Parse(modFilePath, data, nil) | ||
if err != nil { | ||
return nil, fmt.Errorf("%s: failed to parse go.mod file: %v", modFilePath, err) | ||
} | ||
|
||
// fix doesn't apply to go.mod with no module dependencies | ||
if len(mf.Require) == 0 { | ||
return data, nil | ||
} | ||
|
||
var requiresSDK, requiresGoCty bool | ||
for _, req := range mf.Require { | ||
if req.Mod.Path == sdkPath { | ||
requiresSDK = true | ||
} | ||
if req.Mod.Path == f.OldPath { | ||
requiresGoCty = true | ||
} | ||
|
||
if requiresSDK && requiresGoCty { | ||
break | ||
} | ||
} | ||
|
||
if !(requiresSDK && requiresGoCty) { | ||
return data, nil | ||
} | ||
|
||
for _, r := range mf.Replace { | ||
if r.Old.Path != f.OldPath { | ||
continue | ||
} | ||
|
||
if r.New.Path != f.NewPath { | ||
return nil, fmt.Errorf("%s: found unexpected replace for %s", modFilePath, r.Old.Path) | ||
} | ||
|
||
if r.New.Version == f.modFileFormattedVersion() { | ||
return data, nil | ||
} | ||
} | ||
|
||
if err := mf.DropReplace(f.OldPath, ""); err != nil { | ||
return nil, fmt.Errorf("%s: failed to drop previously added replacement fix %v", modFilePath, err) | ||
} | ||
|
||
commentSuffix := " // added by packer-sdc fix as noted in github.com/hashicorp/packer-plugin-sdk/issues/187" | ||
if err := mf.AddReplace(f.OldPath, "", f.NewPath, f.modFileFormattedVersion()+commentSuffix); err != nil { | ||
return nil, fmt.Errorf("%s: failed to apply go-cty fix: %v", modFilePath, err) | ||
} | ||
|
||
newData, err := mf.Format() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return newData, nil | ||
} |
Oops, something went wrong.