diff --git a/.github/workflows/inspect.yaml b/.github/workflows/inspect.yaml new file mode 100644 index 0000000..0026b72 --- /dev/null +++ b/.github/workflows/inspect.yaml @@ -0,0 +1,32 @@ +# Copyright 2021 ABSA Group Limited +# +# Licensed 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. +# +# Generated by GoLic, for more details see: https://github.com/kuritka/golic +name: Inspect [linters, tests] + +on: + [workflow_dispatch, push] +jobs: + go-inspect: + name: Inspect packages + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + # see: https://golangci-lint.run/usage/configuration/#config-file + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + version: v1.32 + - name: go test + run: go test ./... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c9ef62e --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Ignore everything +* + +# But not these files... +!CODEOWNERS +!LICENSE +!README.md +!/.gitignore +!.licignore +!.golangci +!*.yaml +!Makefile +!*.go +!go.mod +!go.sum + +# ...even if they are in subdirectories +!*/ + diff --git a/.golangci b/.golangci new file mode 100644 index 0000000..1dda32c --- /dev/null +++ b/.golangci @@ -0,0 +1,22 @@ +linters: + disable-all: true + enable: + - bodyclose + - deadcode + - errcheck + - gocritic + - gocyclo + - goimports + - golint + - gosec + - gosimple + - govet + - ineffassign + - misspell + - nakedret + - lll +run: + deadline: 3m +linters-settings: + lll: + line-length: 150 diff --git a/.licignore b/.licignore new file mode 100644 index 0000000..3bbe2cd --- /dev/null +++ b/.licignore @@ -0,0 +1,16 @@ +# GoLic, for more details see: https://github.com/AbsaOSS/golic +# Ignore everything +* + +# But not these files... + +!*.yaml +!*.go +!Makefile + +# ...even if they are in subdirectories +!*/ + +# except these in subdirs +/**/.gitignore +/.idea/**/*.xml \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..600a29a --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +# Copyright 2021 ABSA Group Limited +# +# Licensed 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. +# +# Generated by GoLic, for more details see: https://github.com/kuritka/golic +.PHONY: lint +lint: + golangci-lint run + +.PHONY: license +license: + GO111MODULE=on go get github.com/AbsaOSS/golic@v0.1.0 + $(GOBIN)/golic inject -c="2021 ABSA Group Limited" -l=.licignore diff --git a/README.md b/README.md new file mode 100644 index 0000000..aaf5aa9 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# golic +license generator +``` +golic inject -c="2021 SuperPower Group Limited" -l=.licignore --dry +``` +![Screenshot 2021-03-08 at 11 42 52](https://user-images.githubusercontent.com/7195836/110310942-6d2f3680-8003-11eb-9540-b2e21b4f2b87.png) + + +## Running from commandline + +create `.licignore` +```shell +# Ignore everything +* + +# But not these files... +!Makefile +!*.go + +# ...even if they are in subdirectories +!*/ +```` +And run **GOLIC** +```shell +GO111MODULE=on go get github.com/AbsaOSS/golic@v0.1.0 +$(GOBIN)/golic inject -c="2021 MyCompany Group Limited" -l=.licignore +``` + + +## Usage +```shell +Usage: + inject [flags] + +Flags: + -u, --config-url string config URL (default "https://raw.githubusercontent.com/AbsaOSS/golic/main/config.yaml") + -c, --copyright string company initials entered into license (default "2021 MyCompany") + -d, --dry dry run + -h, --help help for inject + -l, --licignore string .licignore path + -t, --template string license key (default "apache2") + +Global Flags: + -v, --verbose verbose output +``` + +## Configuration +For more details see: [default configuration](https://raw.githubusercontent.com/AbsaOSS/golic/main/config.yaml). +Use `-u` flag to run against custom configuration or create PR. + diff --git a/cmd/inject.go b/cmd/inject.go new file mode 100644 index 0000000..0c924d2 --- /dev/null +++ b/cmd/inject.go @@ -0,0 +1,59 @@ +/* +Copyright 2021 ABSA Group Limited + +Licensed 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. + +Generated by GoLic, for more details see: https://github.com/kuritka/golic +*/ +package cmd + +import ( + "net/url" + "os" + + "github.com/AbsaOSS/golic/impl/inject" + "github.com/spf13/cobra" +) + +var injectOptions inject.Options + +var injectCmd = &cobra.Command{ + Use: "inject", + Short: "", + Long: ``, + + Run: func(cmd *cobra.Command, args []string) { + if _, err := os.Stat(injectOptions.LicIgnore); os.IsNotExist(err) { + logger.Error().Msgf("invalid license path '%s'",injectOptions.LicIgnore) + _ = cmd.Help() + os.Exit(0) + } + if _,err := url.Parse(injectOptions.ConfigURL); err != nil { + logger.Error().Msgf("invalid config.yaml url '%s'",injectOptions.ConfigURL) + _ = cmd.Help() + os.Exit(0) + } + i := inject.New(ctx, injectOptions) + Command(i).MustRun() + }, +} + +func init() { + injectCmd.Flags().StringVarP(&injectOptions.LicIgnore, "licignore", "l", "", ".licignore path") + injectCmd.Flags().StringVarP(&injectOptions.Template, "template", "t", "apache2", "license key") + injectCmd.Flags().StringVarP(&injectOptions.Copyright, "copyright", "c", "2021 MyCompany", + "company initials entered into license") + injectCmd.Flags().BoolVarP(&injectOptions.Dry, "dry", "d", false, "dry run") + injectCmd.Flags().StringVarP(&injectOptions.ConfigURL, "config-url", "u", "https://raw.githubusercontent.com/AbsaOSS/golic/main/config.yaml", "config URL") + rootCmd.AddCommand(injectCmd) +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..af0acdb --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,62 @@ +/* +Copyright 2021 ABSA Group Limited + +Licensed 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. + +Generated by GoLic, for more details see: https://github.com/kuritka/golic +*/ +//package provides cobra commands +package cmd + +import ( + "context" + "fmt" + "github.com/enescakir/emoji" + "os" + + "github.com/AbsaOSS/golic/utils/log" + + "github.com/spf13/cobra" +) + +var Verbose bool + +var ctx = context.Background() + +var logger = log.Log + +var rootCmd = &cobra.Command{ + Short: "golic license injector", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + logger.Error().Msg("no parameters included") + _ = cmd.Help() + os.Exit(0) + } + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + logger.Info().Msgf("done %3s%s%s", emoji.Rocket,emoji.Rocket,emoji.Rocket) + }, +} + +func init() { + rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") +} + +func Execute() { + fmt.Println() + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/cmd/runner.go b/cmd/runner.go new file mode 100644 index 0000000..066106f --- /dev/null +++ b/cmd/runner.go @@ -0,0 +1,45 @@ +/* +Copyright 2021 ABSA Group Limited + +Licensed 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. + +Generated by GoLic, for more details see: https://github.com/kuritka/golic +*/ +package cmd + +import ( + "github.com/AbsaOSS/golic/utils/guard" + "github.com/enescakir/emoji" +) + +type Service interface { + Run() error + String() string +} + +type ServiceRunner struct { + service Service +} + +func Command(service Service) *ServiceRunner { + return &ServiceRunner{ + service, + } +} + +//Run service once and panics if service is broken +func (r *ServiceRunner) MustRun() { + logger.Info().Msgf("%s command %s started",emoji.Tractor, r.service) + err := r.service.Run() + guard.FailOnError(err, "command %s failed", r.service) +} diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..7934cd8 --- /dev/null +++ b/config.yaml @@ -0,0 +1,93 @@ +# Copyright 2021 ABSA Group Limited +# +# Licensed 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. +# +# Generated by GoLic, for more details see: https://github.com/kuritka/golic +golic: + licenses: + apache2: | + Copyright {{copyright}} + + Licensed 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. + + Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic + copyright: | + Copyright © {{copyright}} + + All rights reserved + + Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic + rules: + .go: + prefix: "/*" + suffix: "*/" + .java: + prefix: "/*" + suffix: "*/" + .scala: + prefix: "/*" + suffix: "*/" + .cs: + prefix: "/*" + suffix: "*/" + .html: + prefix: "" + .js: + prefix: "/*" + suffix: "*/" + .css: + prefix: "/*" + suffix: "*/" + .yaml: + prefix: "#" + .yml: + prefix: "#" + Dockerfile: + prefix: "#" + Makefile: + prefix: "#" + .gitignore: + prefix: "#" + .licignore: + prefix: "#" + .tf: + prefix: "#" + .sh: + prefix: "#" + under: + - "!/bin/sh" + - "!/bin/bash" + - "!/bin/ksh" + - "!/bin/awk" + - "!/bin/expect" + - "!/bin/csh" + - "!/usr/bin/perl" + - "!/usr/bin/python" + - "!/usr/bin/ruby" + - "!/usr/bin/php" + .xml: + prefix: "" + under: + - " 0 +} \ No newline at end of file diff --git a/impl/inject/inject.go b/impl/inject/inject.go new file mode 100644 index 0000000..1659146 --- /dev/null +++ b/impl/inject/inject.go @@ -0,0 +1,208 @@ +/* +Copyright 2021 ABSA Group Limited + +Licensed 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. + +Generated by GoLic, for more details see: https://github.com/kuritka/golic +*/ +package inject + +import ( + "context" + "fmt" + "gopkg.in/yaml.v3" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/AbsaOSS/golic/utils/log" + + "github.com/denormal/go-gitignore" + "github.com/enescakir/emoji" + "github.com/logrusorgru/aurora" +) + +type Inject struct { + opts Options + ctx context.Context + ignore gitignore.GitIgnore + cfg *Config +} + +var logger = log.Log + +func New(ctx context.Context, options Options) *Inject { + return &Inject{ + ctx: ctx, + opts: options, + } +} + +func (i *Inject) Run() (err error) { + logger.Info().Msgf("%s reading %s",emoji.OpenBook, i.opts.LicIgnore) + i.ignore, err = gitignore.NewFromFile(i.opts.LicIgnore) + if err != nil { + return err + } + logger.Info().Msgf("%s reading %s",emoji.OpenBook, i.opts.ConfigURL) + if i.cfg, err = i.readConfig(); err != nil { + return + } + i.traverse() + return +} + +func (i *Inject) String() string { + return aurora.BrightCyan("inject").String() +} + +func read(f string) (s string, err error) { + content, err := ioutil.ReadFile(f) + if err != nil { + return + } + // Convert []byte to string and print to screen + return string(content), nil +} + +func (i *Inject) traverse() { + p := func(path string, i gitignore.GitIgnore, o Options, config *Config) (err error) { + if !i.Ignore(path) { + var skip bool + symbol := "" + if err,skip = inject(path,o, config); skip { + symbol = "-> skip" + } + emoji.Printf(" %s %s %s \n",emoji.Minus, aurora.BrightYellow(path), aurora.BrightMagenta(symbol)) + } + return + } + + err := filepath.Walk("./", + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + return p(path, i.ignore, i.opts, i.cfg) + } + return nil + }) + if err != nil { + logger.Err(err).Msg("") + } +} + +func inject(path string, o Options, config *Config) (err error, skip bool) { + source,err := read(path) + if err != nil { + return err,false + } + rule := getRule(path) + license,err := getCommentedLicense(config, o, rule) + if err != nil { + return err, false + } + if config.LicenseStartsAfterHeader(rule) { + // gets first line of source and the rest of source code + l1, lx := splitSource(source) + // file is not empty and file contains header + if headerContains(l1, config.Golic.Rules[rule].Under) { + license = fmt.Sprintf("%s\n%s", l1, license) + if strings.HasPrefix(source, license) { + return nil, true + } + source = lx + } + } + if strings.HasPrefix(source, license) { + return nil, true + } + if !o.Dry { + data := []byte(fmt.Sprintf("%s%s", license, source)) + err = ioutil.WriteFile(path,data, os.ModeExclusive) + } + return +} + +func headerContains(header string, values []string) bool{ + for _,v := range values { + if strings.Contains(header, v) { + return true + } + } + return false +} + +func getCommentedLicense(config *Config, o Options, rule string) (string, error) { + var ok bool + var template string + if template, ok = config.Golic.Licenses[o.Template]; !ok { + return "",fmt.Errorf("no license found for %s, check configuration (%s)",o.Template,o.ConfigURL) + } + if _, ok = config.Golic.Rules[rule]; !ok { + return "",fmt.Errorf("no rule found for %s, check configuration (%s)", rule,o.ConfigURL) + } + template = strings.ReplaceAll(template,"{{copyright}}", o.Copyright) + if config.IsWrapped(rule) { + return fmt.Sprintf("%s\n%s%s\n", + config.Golic.Rules[rule].Prefix, + template, + config.Golic.Rules[rule].Suffix), + nil + } + // `\r\n` -> `\r\n #`, `\n` -> `\n #` + content := strings.ReplaceAll(template,"\n",fmt.Sprintf("\n%s ", config.Golic.Rules[rule].Prefix)) + content = strings.TrimSuffix(content, config.Golic.Rules[rule].Prefix+" ") + return config.Golic.Rules[rule].Prefix + " " + content,nil +} + +func splitSource(source string) (firstLine, rest string){ + lines := strings.Split(source,"\n") + if len(lines) > 0 { + firstLine = lines[0] + rest = strings.Join(lines[1:],"\n") + return + } + return "",source +} + +func getRule(path string) (rule string) { + rule = filepath.Ext(path) + if rule == "" { + rule = filepath.Base(path) + } + return +} + +func (i *Inject) readConfig() (c *Config, err error) { + var client http.Client + var resp *http.Response + var b []byte + c = new(Config) + resp, err = client.Get(i.opts.ConfigURL) + if err != nil { + return + } + if resp.StatusCode >= http.StatusBadRequest { + return nil, fmt.Errorf("%s: %s returns %d", http.MethodGet, i.opts.ConfigURL, resp.StatusCode) + } + defer resp.Body.Close() + if b, err = ioutil.ReadAll(resp.Body); err != nil { + return + } + err = yaml.Unmarshal(b, c) + return +} \ No newline at end of file diff --git a/impl/inject/opts.go b/impl/inject/opts.go new file mode 100644 index 0000000..e6585a9 --- /dev/null +++ b/impl/inject/opts.go @@ -0,0 +1,26 @@ +/* +Copyright 2021 ABSA Group Limited + +Licensed 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. + +Generated by GoLic, for more details see: https://github.com/kuritka/golic +*/ +package inject + +type Options struct { + LicIgnore string + Copyright string + Dry bool + ConfigURL string + Template string +} diff --git a/impl/remove/opts.go b/impl/remove/opts.go new file mode 100644 index 0000000..0100970 --- /dev/null +++ b/impl/remove/opts.go @@ -0,0 +1,25 @@ +/* +Copyright 2021 ABSA Group Limited + +Licensed 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. + +Generated by GoLic, for more details see: https://github.com/kuritka/golic +*/ +package remove + +type Options struct { + License string + Template string + Copyright string + Dry bool +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..7ec560e --- /dev/null +++ b/main.go @@ -0,0 +1,24 @@ +/* +Copyright 2021 ABSA Group Limited + +Licensed 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. + +Generated by GoLic, for more details see: https://github.com/kuritka/golic +*/ +package main + +import "github.com/AbsaOSS/golic/cmd" + +func main() { + cmd.Execute() +} diff --git a/utils/guard/guard.go b/utils/guard/guard.go new file mode 100644 index 0000000..f4fab96 --- /dev/null +++ b/utils/guard/guard.go @@ -0,0 +1,31 @@ +/* +Copyright 2021 ABSA Group Limited + +Licensed 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. + +Generated by GoLic, for more details see: https://github.com/kuritka/golic +*/ +//Guards throws errors or panics when error occur +package guard + +import ( + "github.com/AbsaOSS/golic/utils/log" +) + +var logger = log.Log + +func FailOnError(err error, message string, v ...interface{}) { + if err != nil { + logger.Fatal().Err(err).Msgf(message, v...) + } +} diff --git a/utils/log/log.go b/utils/log/log.go new file mode 100644 index 0000000..ca44789 --- /dev/null +++ b/utils/log/log.go @@ -0,0 +1,41 @@ +/* +Copyright 2021 ABSA Group Limited + +Licensed 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. + +Generated by GoLic, for more details see: https://github.com/kuritka/golic +*/ +// Package log wraps zerolog logger and provides standard log functionality +package log + +import ( + "os" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/pkgerrors" +) + +// Log is the global logger. +var Log *zerolog.Logger + +//init initializes the logger +func init() { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack + l := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "15:04:05", NoColor: false}). + With(). + Timestamp(). + Logger() + Log = &l + //Log.Info().Msg("logger configured") +}