From 4616fbf38b2e3bf82a507c3ee6375115823e3b27 Mon Sep 17 00:00:00 2001 From: Tung Bui Date: Wed, 12 Jul 2023 18:36:56 +0700 Subject: [PATCH 1/2] introducing golang ci --- .github/workflows/go-lint.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/go-lint.yml diff --git a/.github/workflows/go-lint.yml b/.github/workflows/go-lint.yml new file mode 100644 index 000000000..28a436782 --- /dev/null +++ b/.github/workflows/go-lint.yml @@ -0,0 +1,24 @@ +name: golangci-lint +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + pull-requests: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: '1.20' + cache: true + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest From c044a3b621f873581edcdbb9907744bde0c383c1 Mon Sep 17 00:00:00 2001 From: Tung Bui Date: Wed, 12 Jul 2023 21:45:16 +0700 Subject: [PATCH 2/2] fix linter --- cmd/notation/internal/cmdutil/confirmation.go | 31 +++++++ cmd/notation/internal/errors/errors.go | 38 +++++++++ internal/osutil/file.go | 82 +++++++++++++++++++ internal/slices/slices.go | 10 +++ internal/slices/slices_test.go | 24 ++++++ internal/tree/tree.go | 56 +++++++++++++ internal/tree/tree_test.go | 82 +++++++++++++++++++ internal/version/version.go | 23 ++++++ internal/version/version_test.go | 23 ++++++ test/e2e/plugin/internal/io/io.go | 15 ++++ test/e2e/plugin/mock/common.go | 10 +++ 11 files changed, 394 insertions(+) diff --git a/cmd/notation/internal/cmdutil/confirmation.go b/cmd/notation/internal/cmdutil/confirmation.go index 15823cd14..405893b10 100644 --- a/cmd/notation/internal/cmdutil/confirmation.go +++ b/cmd/notation/internal/cmdutil/confirmation.go @@ -30,3 +30,34 @@ func AskForConfirmation(r io.Reader, prompt string, confirmed bool) (bool, error return false, nil } } + +import ( + "bufio" + "fmt" + "io" + "strings" +) + +// AskForConfirmation prints a propmt to ask for confirmation before doing an +// action and takes user input as response. +func AskForConfirmation(r io.Reader, prompt string, confirmed bool) (bool, error) { + if confirmed { + return true, nil + } + + fmt.Print(prompt, " [y/N] ") + + scanner := bufio.NewScanner(r) + if ok := scanner.Scan(); !ok { + return false, scanner.Err() + } + response := scanner.Text() + + switch strings.ToLower(response) { + case "y", "yes": + return true, nil + default: + fmt.Println("Operation cancelled.") + return false, nil + } +} diff --git a/cmd/notation/internal/errors/errors.go b/cmd/notation/internal/errors/errors.go index 6d56cb21e..7275f32b9 100644 --- a/cmd/notation/internal/errors/errors.go +++ b/cmd/notation/internal/errors/errors.go @@ -37,3 +37,41 @@ type ErrorExceedMaxSignatures struct { func (e ErrorExceedMaxSignatures) Error() string { return fmt.Sprintf("exceeded configured limit of max signatures %d to examine", e.MaxSignatures) } + +import "fmt" + +// ErrorReferrersAPINotSupported is used when the target registry does not +// support the Referrers API +type ErrorReferrersAPINotSupported struct { + Msg string +} + +func (e ErrorReferrersAPINotSupported) Error() string { + if e.Msg != "" { + return e.Msg + } + return "referrers API not supported" +} + +// ErrorOCILayoutMissingReference is used when signing local content in oci +// layout folder but missing input tag or digest. +type ErrorOCILayoutMissingReference struct { + Msg string +} + +func (e ErrorOCILayoutMissingReference) Error() string { + if e.Msg != "" { + return e.Msg + } + return "reference is missing either digest or tag" +} + +// ErrorExceedMaxSignatures is used when the number of signatures has surpassed +// the maximum limit that can be evaluated. +type ErrorExceedMaxSignatures struct { + MaxSignatures int +} + +func (e ErrorExceedMaxSignatures) Error() string { + return fmt.Sprintf("exceeded configured limit of max signatures %d to examine", e.MaxSignatures) +} diff --git a/internal/osutil/file.go b/internal/osutil/file.go index 139cffd3f..c3eb3686e 100644 --- a/internal/osutil/file.go +++ b/internal/osutil/file.go @@ -81,3 +81,85 @@ func IsRegularFile(path string) (bool, error) { return fileStat.Mode().IsRegular(), nil } + +import ( + "fmt" + "io" + "io/fs" + "os" + "path/filepath" +) + +// WriteFile writes to a path with all parent directories created. +func WriteFile(path string, data []byte) error { + if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { + return err + } + return os.WriteFile(path, data, 0o600) +} + +// WriteFileWithPermission writes to a path with all parent directories created. +func WriteFileWithPermission(path string, data []byte, perm fs.FileMode, overwrite bool) error { + if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { + return err + } + flag := os.O_WRONLY | os.O_CREATE + if overwrite { + flag |= os.O_TRUNC + } else { + flag |= os.O_EXCL + } + file, err := os.OpenFile(path, flag, perm) + if err != nil { + return err + } + _, err = file.Write(data) + if err != nil { + file.Close() + return err + } + return file.Close() +} + +// CopyToDir copies the src file to dst. Existing file will be overwritten. +func CopyToDir(src, dst string) (int64, error) { + sourceFileStat, err := os.Stat(src) + if err != nil { + return 0, err + } + + if !sourceFileStat.Mode().IsRegular() { + return 0, fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return 0, err + } + defer source.Close() + + if err := os.MkdirAll(dst, 0o700); err != nil { + return 0, err + } + certFile := filepath.Join(dst, filepath.Base(src)) + destination, err := os.Create(certFile) + if err != nil { + return 0, err + } + defer destination.Close() + err = destination.Chmod(0o600) + if err != nil { + return 0, err + } + return io.Copy(destination, source) +} + +// IsRegularFile checks if path is a regular file +func IsRegularFile(path string) (bool, error) { + fileStat, err := os.Stat(path) + if err != nil { + return false, err + } + + return fileStat.Mode().IsRegular(), nil +} diff --git a/internal/slices/slices.go b/internal/slices/slices.go index 0d348cdf1..fb10d1b23 100644 --- a/internal/slices/slices.go +++ b/internal/slices/slices.go @@ -9,3 +9,13 @@ func Contains[E comparable](s []E, v E) bool { } return false } + +// Contains reports whether v is present in s. +func Contains[E comparable](s []E, v E) bool { + for _, vs := range s { + if v == vs { + return true + } + } + return false +} diff --git a/internal/slices/slices_test.go b/internal/slices/slices_test.go index 2c19ff559..fe33b5f7d 100644 --- a/internal/slices/slices_test.go +++ b/internal/slices/slices_test.go @@ -23,3 +23,27 @@ func TestContainerElement(t *testing.T) { } } } + +import ( + "testing" +) + +func TestContainerElement(t *testing.T) { + tests := []struct { + c []string + v string + want bool + }{ + {nil, "", false}, + {[]string{}, "", false}, + {[]string{"1", "2", "3"}, "4", false}, + {[]string{"1", "2", "3"}, "2", true}, + {[]string{"1", "2", "2", "3"}, "2", true}, + {[]string{"1", "2", "3", "2"}, "2", true}, + } + for _, tt := range tests { + if got := Contains(tt.c, tt.v); got != tt.want { + t.Errorf("ContainerElement() = %v, want %v", got, tt.want) + } + } +} diff --git a/internal/tree/tree.go b/internal/tree/tree.go index 4cf224057..c3eb1a904 100644 --- a/internal/tree/tree.go +++ b/internal/tree/tree.go @@ -55,3 +55,59 @@ func print(prefix string, itemMarker string, nextPrefix string, n *Node) { } } } + +import ( + "fmt" +) + +const ( + treeItemPrefix = "├── " + treeItemPrefixLast = "└── " + subTreePrefix = "│ " + subTreePrefixLast = " " +) + +// represents a Node in a tree +type Node struct { + Value string + Children []*Node +} + +// creates a new Node with the given value +func New(value string) *Node { + return &Node{Value: value} +} + +// adds a new child node with the given value +func (parent *Node) Add(value string) *Node { + node := New(value) + parent.Children = append(parent.Children, node) + return node +} + +// adds a new child node with the formatted pair as the value +func (parent *Node) AddPair(key string, value string) *Node { + return parent.Add(key + ": " + value) +} + +// prints the tree represented by the root node +func (root *Node) Print() { + print("", "", "", root) +} + +func print(prefix string, itemMarker string, nextPrefix string, n *Node) { + fmt.Println(prefix + itemMarker + n.Value) + + nextItemPrefix := treeItemPrefix + nextSubTreePrefix := subTreePrefix + + if len(n.Children) > 0 { + for i, child := range n.Children { + if i == len(n.Children)-1 { + nextItemPrefix = treeItemPrefixLast + nextSubTreePrefix = subTreePrefixLast + } + print(nextPrefix, nextItemPrefix, nextPrefix+nextSubTreePrefix, child) + } + } +} diff --git a/internal/tree/tree_test.go b/internal/tree/tree_test.go index b0284a582..46add26e8 100644 --- a/internal/tree/tree_test.go +++ b/internal/tree/tree_test.go @@ -81,3 +81,85 @@ func (n *Node) ContainsChild(value string) bool { return false } + +import ( + "reflect" + "testing" +) + +func TestNodeCreation(t *testing.T) { + node := New("root") + expected := Node{Value: "root"} + + if !reflect.DeepEqual(*node, expected) { + t.Fatalf("expected %+v, got %+v", expected, *node) + } +} + +func TestNodeAdd(t *testing.T) { + root := New("root") + root.Add("child") + + if !root.ContainsChild("child") { + t.Error("expected root to have child node with value 'child'") + t.Fatalf("actual root: %+v", root) + } +} + +func TestNodeAddPair(t *testing.T) { + root := New("root") + root.AddPair("key", "value") + + if !root.ContainsChild("key: value") { + t.Error("expected root to have child node with value 'key: value'") + t.Fatalf("actual root: %+v", root) + } +} + +func ExampleRootPrint() { + root := New("root") + root.Print() + + // Output: + // root +} + +func ExampleSingleLayerPrint() { + root := New("root") + root.Add("child1") + root.Add("child2") + root.Print() + + // Output: + // root + // ├── child1 + // └── child2 +} + +func ExampleMultiLayerPrint() { + root := New("root") + child1 := root.Add("child1") + child1.AddPair("key", "value") + child2 := root.Add("child2") + child2.Add("child2.1") + child2.Add("child2.2") + root.Print() + + // Output: + // root + // ├── child1 + // │ └── key: value + // └── child2 + // ├── child2.1 + // └── child2.2 +} + +func (n *Node) ContainsChild(value string) bool { + for _, child := range n.Children { + if child.Value == value { + return true + } + } + + return false +} diff --git a/internal/version/version.go b/internal/version/version.go index 6b696285e..0bb0b7fec 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -22,3 +22,26 @@ func GetVersion() string { } return Version + "+" + BuildMetadata } + +var ( + // Version shows the current notation version, optionally with pre-release. + Version = "v1.0.0-rc.7" + + // BuildMetadata stores the build metadata. + // + // When execute `make build` command, it will be overridden by + // environment variable `BUILD_METADATA`. If commit tag was set, + // BuildMetadata will be empty. + BuildMetadata = "unreleased" + + // GitCommit stores the git HEAD commit id + GitCommit = "" +) + +// GetVersion returns the version string in SemVer 2. +func GetVersion() string { + if BuildMetadata == "" { + return Version + } + return Version + "+" + BuildMetadata +} diff --git a/internal/version/version_test.go b/internal/version/version_test.go index 01d2f7971..970e86063 100644 --- a/internal/version/version_test.go +++ b/internal/version/version_test.go @@ -22,3 +22,26 @@ func TestGetVersion(t *testing.T) { } }) } + +import "testing" + +func TestGetVersion(t *testing.T) { + t.Run("BuildMetadata is empty", func(t *testing.T) { + Version = "1.0" + BuildMetadata = "" + v := GetVersion() + if Version != v { + t.Errorf("Should return Version = %s, got %s", Version, v) + } + }) + + t.Run("BuildMetadata is not empty", func(t *testing.T) { + Version = "1.0" + BuildMetadata = "unreleased" + v := GetVersion() + want := "1.0+unreleased" + if want != v { + t.Errorf("Should return Version = %s, got %s", want, v) + } + }) +} diff --git a/test/e2e/plugin/internal/io/io.go b/test/e2e/plugin/internal/io/io.go index f6f86fb02..c5fc74d4c 100644 --- a/test/e2e/plugin/internal/io/io.go +++ b/test/e2e/plugin/internal/io/io.go @@ -14,3 +14,18 @@ func UnmarshalRequest(req any) error { func PrintResponse(resp any) error { return json.NewEncoder(os.Stdout).Encode(resp) } + +import ( + "encoding/json" + "os" +) + +// UnmarshalRequest read the STDIN and unmarshal to struct pointed by req. +func UnmarshalRequest(req any) error { + return json.NewDecoder(os.Stdin).Decode(req) +} + +// PrintResponse marshal and print the struct data pointed by resp. +func PrintResponse(resp any) error { + return json.NewEncoder(os.Stdout).Encode(resp) +} diff --git a/test/e2e/plugin/mock/common.go b/test/e2e/plugin/mock/common.go index d35670ad5..df77b82f6 100644 --- a/test/e2e/plugin/mock/common.go +++ b/test/e2e/plugin/mock/common.go @@ -9,3 +9,13 @@ const ( TamperSignatureEnvelopeType = "TAMPER_SIGNATURE_ENVELOPE_TYPE" TamperAnnotation = "TAMPER_ANNOTATION" ) + +const ( + TamperKeyID = "TAMPER_KEY_ID" + TamperSignature = "TAMPER_SIGNATURE" + TamperSignatureAlgorithm = "TAMPER_SIGNATURE_ALGORITHM" + TamperCertificateChain = "TAMPER_CERTIFICATE_CHAIN" + TamperSignatureEnvelope = "TAMPER_SIGNATURE_ENVELOPE" + TamperSignatureEnvelopeType = "TAMPER_SIGNATURE_ENVELOPE_TYPE" + TamperAnnotation = "TAMPER_ANNOTATION" +)