diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..adf8f72 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# ---> Go +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..4cb2a6a --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,102 @@ +# options for analysis running +run: + # timeout for analysis, e.g. 30s, 5m, default is 1m + timeout: 1m + + # exit code when at least one issue was found, default is 1 + issues-exit-code: 0 + + # include test files or not, default is true + tests: false + + # which dirs to skip: issues from them won't be reported; + # can use regexp here: generated.*, regexp is applied on full path; + # default value is empty list, but default dirs are skipped independently + # from this option's value (see skip-dirs-use-default). + # "/" will be replaced by current OS file path separator to properly work + # on Windows. + skip-dirs: + - wasm + - static + - node_modules + - documents + - docker + - bind-* + + # default is true. Enables skipping of directories: + # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ + skip-dirs-use-default: true + + # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules": + # If invoked with -mod=readonly, the go command is disallowed from the implicit + # automatic updating of go.mod described above. Instead, it fails when any changes + # to go.mod are needed. This setting is most useful to check that go.mod does + # not need updates, such as in a continuous integration and testing system. + # If invoked with -mod=vendor, the go command assumes that the vendor + # directory holds the correct copies of dependencies and ignores + # the dependency descriptions in go.mod. + modules-download-mode: mod + + # Allow multiple parallel golangci-lint instances running. + # If false (default) - golangci-lint acquires file lock on start. + allow-parallel-runners: true + + +# output configuration options +output: + # colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions + # default is "colored-line-number" + format: colored-line-number + + # print lines of code with issue, default is true + print-issued-lines: true + + # print linter name in the end of issue text, default is true + print-linter-name: true + + # make issues output unique by line, default is true + uniq-by-line: true + + # add a prefix to the output file references; default is no prefix + path-prefix: "" + + # sorts results by: filepath, line and column + sort-results: false + + +# all available settings of specific linters +linters-settings: + gofmt: + # simplify code: gofmt with `-s` option, true by default + simplify: true + staticcheck: + # Select the Go version to target. The default is '1.13'. + go: "1.16" + # https://staticcheck.io/docs/options#checks + checks: ["all"] + stylecheck: + # Select the Go version to target. The default is '1.13'. + go: "1.18" + # https://staticcheck.io/docs/options#checks + checks: ["all"] + errcheck: + # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; + # default is false: such cases aren't reported by default. + check-blank: true + +linters: + enable: + # go install github.com/kisielk/errcheck@latest + # go install github.com/gordonklaus/ineffassign@latest + # go install honnef.co/go/tools/cmd/staticcheck@latest + # go install gitlab.com/opennota/check/cmd/varcheck@latest + # go install github.com/go-critic/go-critic/cmd/gocritic@latest + - errcheck + - staticcheck + - stylecheck + - ineffassign + - varcheck + - gofmt + - gocritic + - wsl + fast: false diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c2d3fe4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM golang:1.18-bullseye +LABEL author="Thomas Bellembois" + +# Copying sources. +WORKDIR /go/src/github.com/tbellembois/codefirst-menuwrapper/ +COPY . . + +# Installing. +RUN go install -v ./... +RUN chmod +x /go/bin/codefirst-menuwrapper + +# Copying entrypoint. +COPY entrypoint.sh / +RUN chmod +x /entrypoint.sh + +USER www-data +EXPOSE 8081 +CMD ["/entrypoint.sh"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1e5a809 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# codefirst-menuwrapper + diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..1a4095f --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +debug="" + +if [ ! -z "$DEBUG" ] +then + debug="-debug" +fi + +/go/bin/codefirst-menuwrapper -proxyurl=$PROXYURL -codefirsturl=$CODEFIRSTURL $debug \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f549282 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module codefirst.iut.uca.fr/git/thomas.bellembois/codefirst-menuwrapper + +go 1.19 + +require ( + codefirst.iut.uca.fr/git/thomas.bellembois/codefirst-menu/v2 v2.0.0-20220901141305-9da5f43fc873 + github.com/sirupsen/logrus v1.9.0 +) + +require golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7895a3d --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +codefirst.iut.uca.fr/git/thomas.bellembois/codefirst-menu/v2 v2.0.0-20220901141305-9da5f43fc873 h1:GYYkgzYWIYV5qLSVguwerESYAfFSESlsJAV3drN1mno= +codefirst.iut.uca.fr/git/thomas.bellembois/codefirst-menu/v2 v2.0.0-20220901141305-9da5f43fc873/go.mod h1:hQ1fdhe5i32sO3pl+kic2sPFTA1uw3X3U82aDV77LoU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..aa7e847 --- /dev/null +++ b/main.go @@ -0,0 +1,204 @@ +package main + +import ( + "bytes" + "compress/gzip" + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/http/httputil" + "net/url" + "os" + "strings" + + "codefirst.iut.uca.fr/git/thomas.bellembois/codefirst-menu/v2/menu" + log "github.com/sirupsen/logrus" +) + +var ( + updatedMenu, updatedStringbody string + proxyURL, codefirstURL string + debug, dump bool +) + +func gUnzipData(data []byte) (resData []byte, err error) { + b := bytes.NewBuffer(data) + + var r io.Reader + r, err = gzip.NewReader(b) + + if err != nil { + return + } + + var resB bytes.Buffer + _, err = resB.ReadFrom(r) + + if err != nil { + return + } + + resData = resB.Bytes() + + return +} + +func UpdateResponse(r *http.Response) error { + + contentType := r.Header[http.CanonicalHeaderKey("content-type")] + contentEncoding := r.Header[http.CanonicalHeaderKey("content-encoding")] + location := r.Header[http.CanonicalHeaderKey("location")] + statusCode := r.StatusCode + + log.WithFields( + log.Fields{ + "r.Header": r.Header, + "statusCode": statusCode, + "contentType": contentType, + "contentEncoding": contentEncoding, + "location": location, + }, + ).Debug() + + if statusCode == http.StatusFound && strings.HasPrefix(proxyURL, "http://pastebin") && len(contentType) == 0 { + if len(location) > 0 { + r.Header.Del("location") + r.Header.Set("location", fmt.Sprintf("/pastebin%s", location[0])) + } + } + + if statusCode != http.StatusOK { + log.Debug("skipping not status ok reponse") + return nil + } + + if len(contentType) > 0 && strings.HasPrefix(contentType[0], "text/html") { + var ( + body []byte + err error + ) + + if body, err = io.ReadAll(r.Body); err != nil { + fmt.Println(err) + return err + } + + if len(contentEncoding) > 0 && strings.HasPrefix(contentEncoding[0], "gzip") { + if body, err = gUnzipData(body); err != nil { + fmt.Println(err) + return err + } + } + + stringBody := string(body) + + if strings.HasPrefix(proxyURL, "http://pastebin") { + updatedStringbody = strings.Replace(stringBody, "action=\"/\"", "action=\"/pastebin/\"", 1) + } else if strings.HasPrefix(proxyURL, "http://cinny") { + updatedStringbody = strings.Replace(stringBody, ``, ``+updatedMenu, 1) + } else { + updatedStringbody = strings.Replace(stringBody, "", ""+updatedMenu, 1) + } + + buf := bytes.NewBufferString(updatedStringbody) + r.Body = ioutil.NopCloser(buf) + + r.Header.Del("content-encoding") + r.Header.Del("content-type") + r.Header.Set("content-type", "text/html; charset=utf-8") + r.Header.Set("content-length", fmt.Sprint(buf.Len())) + } + + return nil +} + +func init() { + flag.StringVar(&proxyURL, "proxyurl", "", "proxy URL") + flag.StringVar(&codefirstURL, "codefirsturl", "", "codefirst URL") + flag.BoolVar(&debug, "debug", false, "enable debug") + flag.BoolVar(&dump, "dump", false, "dump menu") + flag.Parse() + + updatedMenu = menu.CodeFirstMenu + + if dump { + updatedMenu = strings.Replace(updatedMenu, "", "", 1) + fmt.Println(updatedMenu) + os.Exit(0) + } + + if proxyURL == "" { + panic(errors.New("empty proxy URL")) + } + + if debug { + log.SetLevel(log.DebugLevel) + } + + updatedMenu = strings.ReplaceAll(updatedMenu, "CODEFIRST_HOSTNAME", codefirstURL) + + if strings.HasPrefix(proxyURL, "http://gitea") { + updatedMenu = strings.Replace(updatedMenu, "", "", 1) + } else if strings.HasPrefix(proxyURL, "http://drone") { + updatedMenu = strings.Replace(updatedMenu, "", "", 1) + } else if strings.HasPrefix(proxyURL, "http://dockerrunner") { + updatedMenu = strings.Replace(updatedMenu, "", "", 1) + } else if strings.HasPrefix(proxyURL, "http://registryui") { + updatedMenu = strings.Replace(updatedMenu, "", "", 1) + } else if strings.HasPrefix(proxyURL, "http://sonarqube") { + updatedMenu = strings.Replace(updatedMenu, "", "", 1) + } else if strings.HasPrefix(proxyURL, "http://nginx") { + updatedMenu = strings.Replace(updatedMenu, "", "", 1) + } else if strings.HasPrefix(proxyURL, "http://cinny") { + updatedMenu = strings.Replace(updatedMenu, "", "", 1) + } +} + +func main() { + remote, err := url.Parse(proxyURL) + if err != nil { + panic(err) + } + + log.WithFields( + log.Fields{ + "proxyURL": proxyURL, + "updatedMenu": updatedMenu, + }, + ).Debug() + + handler := func(p *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + log.WithFields( + log.Fields{ + "r.Host": r.Host, + "r.RequestURI": r.RequestURI, + }, + ).Debug() + + r.Host = remote.Host + p.ServeHTTP(w, r) + } + } + + proxy := httputil.NewSingleHostReverseProxy(remote) + proxy.ModifyResponse = UpdateResponse + + http.HandleFunc("/", handler(proxy)) + err = http.ListenAndServe(":8081", nil) + + if err != nil { + panic(err) + } +}