This is an automated email from the ASF dual-hosted git repository.

zrhoffman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new 37721fe  Go fmt action (#5077)
37721fe is described below

commit 37721fe7d2854c3ec8030f5a6ec8e7d9c1bc0991
Author: ocket8888 <[email protected]>
AuthorDate: Mon Sep 28 18:30:20 2020 -0600

    Go fmt action (#5077)
    
    * Added script to parse git diff output into GHA actions
    
    * Add code workspaces to .gitignore
    
    * Add doctest for file parsing
    
    * add main function and full diff parsing function
    
    * Add go-fmt action
    
    * Fix docker tag
    
    * Fix missing license header
    
    * introduce diff
    
    * pipe to parse_diffs
    
    * fixed inverted logic
    
    * Try to replace newlines
    
    * Fix introduced diff
    
    * Add more safety replacements to error strings
    
    * Add module docstring
    
    * Lint fixes for the Level class
    
    * Fix the rest of the linting errors
    
    * Add badge to readme
    
    * Go fmt fix
    
    * Remove unnecessary exit code
    
    * Make entrypoint directly executable
    
    * Remove unused line
    
    * Remove unnecessary --no-pager option
---
 .github/actions/go-fmt/Dockerfile    |  24 ++++
 .github/actions/go-fmt/README.md     |  37 +++++
 .github/actions/go-fmt/action.yml    |  22 +++
 .github/actions/go-fmt/entrypoint.sh |  37 +++++
 .github/workflows/go.fmt.yml         |  38 +++++
 .gitignore                           |   2 +-
 README.md                            |   1 +
 misc/parse_diffs.py                  | 261 +++++++++++++++++++++++++++++++++++
 traffic_ops_ort/t3c/util/util.go     |   8 +-
 9 files changed, 425 insertions(+), 5 deletions(-)

diff --git a/.github/actions/go-fmt/Dockerfile 
b/.github/actions/go-fmt/Dockerfile
new file mode 100644
index 0000000..a9d59a9
--- /dev/null
+++ b/.github/actions/go-fmt/Dockerfile
@@ -0,0 +1,24 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+FROM golang:1.14.2-alpine
+
+RUN apk add --no-cache git python3
+
+COPY entrypoint.sh /entrypoint.sh
+
+ENTRYPOINT /entrypoint.sh
diff --git a/.github/actions/go-fmt/README.md b/.github/actions/go-fmt/README.md
new file mode 100644
index 0000000..dec7ff7
--- /dev/null
+++ b/.github/actions/go-fmt/README.md
@@ -0,0 +1,37 @@
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+
+# go-fmt Docker action
+
+This action checks for discrepancies with `go fmt` formatting standards.
+
+## Inputs
+
+## Outputs
+
+### `exit-code`
+
+1 if formatting errors are found, 0 otherwise
+
+## Example usage
+```yaml
+uses: actions/go-fmt@v1
+with:
+  dir: './lib/...'
+```
diff --git a/.github/actions/go-fmt/action.yml 
b/.github/actions/go-fmt/action.yml
new file mode 100644
index 0000000..62b3309
--- /dev/null
+++ b/.github/actions/go-fmt/action.yml
@@ -0,0 +1,22 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: 'go-fmt'
+description: 'Checks for go fmt issues'
+runs:
+  using: 'docker'
+  image: 'Dockerfile'
diff --git a/.github/actions/go-fmt/entrypoint.sh 
b/.github/actions/go-fmt/entrypoint.sh
new file mode 100755
index 0000000..cab06a1
--- /dev/null
+++ b/.github/actions/go-fmt/entrypoint.sh
@@ -0,0 +1,37 @@
+#!/bin/sh -l
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+set -e
+
+GOPATH="$(mktemp -d)"
+SRCDIR="$GOPATH/src/github.com/apache"
+mkdir -p "$SRCDIR"
+ln -s "$PWD" "$SRCDIR/trafficcontrol"
+cd "$SRCDIR/trafficcontrol"
+
+/usr/local/go/bin/go fmt ./...
+DIFF_FILE="$(mktemp)"
+git diff >"$DIFF_FILE"
+
+if [ -s "$DIFF_FILE" ]; then
+       ./misc/parse_diffs.py <"$DIFF_FILE";
+       rm "$DIFF_FILE";
+       exit 1;
+fi
+
+echo "No diff found"
diff --git a/.github/workflows/go.fmt.yml b/.github/workflows/go.fmt.yml
new file mode 100644
index 0000000..d44f330
--- /dev/null
+++ b/.github/workflows/go.fmt.yml
@@ -0,0 +1,38 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: Go Format
+
+on:
+  push:
+    paths:
+      - "**.go"
+  create:
+  pull_request:
+    paths:
+      - "**.go"
+    types: [opened, reopened, edited, synchronize]
+
+jobs:
+  format:
+    runs-on: ubuntu-latest
+
+    steps:
+    - name: Checkout
+      uses: actions/checkout@master
+    - name: Run Go fmt
+      uses: ./.github/actions/go-fmt
diff --git a/.gitignore b/.gitignore
index 9f72bff..033cdd8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,6 +49,7 @@ local.tar.gz
 *.sublime-project
 *.sublime-workspace
 .vscode/
+*.code-workspace
 *.pydevproject
 .idea/
 .project
@@ -64,4 +65,3 @@ local.tar.gz
 ./traffic_ops_golang
 # Created by TP/TO docker integration tests
 junit/
-
diff --git a/README.md b/README.md
index ccd1098..b3f45cb 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@
 ![Weasel License 
Checks](https://github.com/apache/trafficcontrol/workflows/Weasel%20License%20checks/badge.svg)
 ![Traffic Ops Integration 
Tests](https://github.com/apache/trafficcontrol/workflows/Traffic%20Ops%20Go%20client/API%20integration%20tests/badge.svg)
 ![Go Unit 
Tests](https://github.com/apache/trafficcontrol/workflows/Go%20Unit%20Tests/badge.svg)
+![Go 
Formatting](https://github.com/apache/trafficcontrol/workflows/Go%20Format/badge.svg)
 [![Documentation 
Status](https://readthedocs.org/projects/traffic-control-cdn/badge/?version=latest)](http://traffic-control-cdn.readthedocs.io/en/latest/?badge=latest)
 
 # Apache Traffic Control
diff --git a/misc/parse_diffs.py b/misc/parse_diffs.py
new file mode 100755
index 0000000..2baa63b
--- /dev/null
+++ b/misc/parse_diffs.py
@@ -0,0 +1,261 @@
+#!/usr/bin/env python3
+
+"""
+This script parses git diffs and outputs a set of GitHub Action annotations 
where each section of
+each file is an annotation containing the diff chunk as a message. All 
annotations are error-level.
+"""
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import re
+import sys
+import typing
+
+from enum import Enum
+
+class Level(Enum):
+       """
+       Level encodes the level of an annotation.
+       """
+
+       error = "error"
+       notice = "notice" # notice not implemented (yet?)
+       warn = "warning" # warning unused - diffs are assumed errors
+
+class Annotation(typing.NamedTuple):
+       """
+       Annotation represents a GitHub Actions Annotation. To preview the 
GitHub Annotation string,
+       coerce it to a string value. For a fully sanitized, printable 
Annotation, use the sanitize
+       method.
+
+       >>> print(Annotation(level=Level.error, file='test', line=1, 
message='test'))
+       ::error file=test,line=1::test
+       """
+       level: Level
+       file: str
+       line: int
+       message: str
+
+       def __str__(self) -> str:
+               msg = self.message.replace("]", "%5D").replace(";", "%3B")
+               return f"::{self.level.value} 
file={self.file},line={self.line}::{msg}"
+
+       def __repr__(self) -> str:
+               return f"Annotation(level={self.level}, file='{self.file}', 
line={self.line})"
+
+       def sanitize(self) -> str:
+               """
+               sanitize returns a sanitized string, suitable for GHA, but not 
for humans to read because
+               all newlines have been replaced with their URL percent-encoded 
code points.
+               """
+               return str(self).replace("\r", "%0D").replace("\n", "%0A")
+
+CHUNK_HEADER_PATTERN = re.compile(r"^@@ -\d+,\d+ \+(\d+),(\d+) @@")
+
+def parse_chunk(chunk: str, file: str) -> Annotation:
+       """
+       parse_chunk parses a single diff chunk and produces a corresponding 
annotation.
+
+       >>> chunk = '''@@ -1,3 +1,3 @@
+       ...  {
+       ... +    "test": "quest"
+       ... -    "foo": "bar"
+       ...  }'''
+       >>> ann = parse_chunk(chunk, "test")
+       >>> ann
+       Annotation(level=Level.error, file='test', line=2)
+       >>> print(ann)
+       ::error file=test,line=2::Format error
+       ```diff
+       @@ -1,3 +1,3 @@
+        {
+       +    "test": "quest"
+       -    "foo": "bar"
+        }
+       ```
+       """
+       lines = chunk.splitlines()
+       if len(lines) < 2:
+               raise ValueError(f"invalid diff chunk: {chunk}")
+
+       match = CHUNK_HEADER_PATTERN.match(lines[0])
+       if not match or len(match.groups()) != 2:
+               raise ValueError(f"invalid diff chunk header: {lines[0]}")
+
+       line = int(match.groups()[0]) + int(match.groups()[1])//2
+
+       content = "\n".join(["Format error", "```diff", chunk, "```"])
+
+       return Annotation(Level.error, file, line, content)
+
+
+FILE_HEADER_PATTERN = re.compile(r"^diff --git a/(.+) b/(.+)$")
+
+def parse_file(contents: str) -> typing.List[Annotation]:
+       """
+       parse_file parses the diff for a single file and returns the 
corresponding annotations.
+
+       >>> file = '''diff --git a/test b/test
+       ... index c9072dcb7..2b7686061 100644
+       ... --- a/test
+       ... +++ b/test
+       ... @@ -24,7 +24,7 @@ package tc
+       ...  // in: body
+       ...  type ASNsResponse struct {
+       ...         // in: body
+       ... -       Response []ASN `json:"response"`
+       ... +Response []ASN `json:"response"`
+       ...        Alerts
+       ... }
+       ...
+       ... @@ -85,7 +85,7 @@ type ASNNullable struct {
+       ...         // ID of the ASN
+       ...         //
+       ...         // required: true
+       ... -       ID *int `json:"id" db:"id"`
+       ... +ID *int `json:"id" db:"id"`
+       ...
+       ...         // LastUpdated
+       ...         //
+       ... '''
+       >>> anns = parse_file(file)
+       >>> len(anns)
+       2
+       >>> anns[0]
+       Annotation(level=Level.error, file='test', line=27)
+       >>> anns[1]
+       Annotation(level=Level.error, file='test', line=88)
+       """
+       lines = contents.splitlines()
+       if len(lines) < 5:
+               raise ValueError(f"'{contents}' does not represent a file diff 
in git format")
+
+       match = FILE_HEADER_PATTERN.match(lines[0])
+       if not match or len(match.groups()) != 2:
+               raise ValueError(f"invalid git diff file header: '{lines[0]}'")
+
+       fname = match.groups()[1]
+
+       lines = lines[4:]
+
+       chunk = [lines[0]]
+       annotations = []
+       for line in lines[1:]:
+               if CHUNK_HEADER_PATTERN.match(line):
+                       annotations.append(parse_chunk("\n".join(chunk), fname))
+                       chunk = []
+
+               chunk.append(line)
+
+       if chunk:
+               annotations.append(parse_chunk("\n".join(chunk), fname))
+
+       return annotations
+
+def parse_diff(diff: str) -> typing.List[Annotation]:
+       """
+       parse_diff parses a git diff output and returns the corresponding 
annotations.
+
+       >>> diff= '''diff --git a/test b/test
+       ... index c9072dcb7..2b7686061 100644
+       ... --- a/test
+       ... +++ b/test
+       ... @@ -24,7 +24,7 @@ package tc
+       ...  // in: body
+       ...  type ASNsResponse struct {
+       ...         // in: body
+       ... -       Response []ASN `json:"response"`
+       ... +Response []ASN `json:"response"`
+       ...         Alerts
+       ...  }
+       ...
+       ... @@ -85,7 +85,7 @@ type ASNNullable struct {
+       ...         // ID of the ASN
+       ...         //
+       ...         // required: true
+       ... -       ID *int `json:"id" db:"id"`
+       ... +ID *int `json:"id" db:"id"`
+       ...
+       ...         // LastUpdated
+       ...         //
+       ... diff --git a/quest b/quest
+       ... index 283901f14..0c1e2b0c1 100644
+       ... --- a/quest
+       ... +++ b/quest
+       ... @@ -1,7 +1,7 @@
+       ...  package tc
+       ...
+       ...  import (
+       ... -       "database/sql"
+       ... +"database/sql"
+       ...  )
+       ...
+       ...  /*
+       ... '''
+       >>> anns = parse_diff(diff)
+       >>> len(anns)
+       3
+       >>> anns[0]
+       Annotation(level=Level.error, file='test', line=27)
+       >>> anns[1]
+       Annotation(level=Level.error, file='test', line=88)
+       >>> anns[2]
+       Annotation(level=Level.error, file='quest', line=4)
+       """
+
+       lines = diff.splitlines()
+       if len(lines) < 5:
+               raise ValueError(f"'{diff}' does not represent a git diff")
+
+       match = FILE_HEADER_PATTERN.match(lines[0])
+       if not match or len(match.groups()) != 2:
+               raise ValueError(f"invalid git diff file header: '{lines[0]}''")
+
+       file = lines[:4]
+       lines = lines[4:]
+       annotations = []
+       for line in lines:
+               if FILE_HEADER_PATTERN.match(line):
+                       annotations += parse_file("\n".join(file))
+                       file = []
+               file.append(line)
+
+       if file:
+               annotations += parse_file("\n".join(file))
+
+       return annotations
+
+def main() -> int:
+       """
+       Runs the main program, based on the passed-in arguments.
+
+       Returns an exit code based on success or failure of the script - NOT 
based
+       on the presence of any error-level annotations.
+       """
+       try:
+               print(*(x.sanitize() for x in parse_diff(sys.stdin.read())), 
sep="\n")
+               return 0
+       except ValueError as e:
+               print("error:", e, file=sys.stderr)
+               return 1
+       except OSError as e:
+               print("error reading input:", e, file=sys.stderr)
+               return 2
+
+if __name__ == "__main__":
+       sys.exit(main())
diff --git a/traffic_ops_ort/t3c/util/util.go b/traffic_ops_ort/t3c/util/util.go
index ec5e165..623599b 100644
--- a/traffic_ops_ort/t3c/util/util.go
+++ b/traffic_ops_ort/t3c/util/util.go
@@ -106,10 +106,10 @@ func ExecCommand(fullCommand string, arg ...string) 
([]byte, int, error) {
        cmd.Stderr = &errbuf
        err := cmd.Run()
 
-  if err != nil {
-    return outbuf.Bytes(), cmd.ProcessState.ExitCode(), 
-      errors.New("Error executing '" + fullCommand + "': " + errbuf.String())
-  }
+       if err != nil {
+               return outbuf.Bytes(), cmd.ProcessState.ExitCode(),
+                       errors.New("Error executing '" + fullCommand + "': " + 
errbuf.String())
+       }
        return outbuf.Bytes(), cmd.ProcessState.ExitCode(), err
 }
 

Reply via email to