├── .github └── workflows │ └── inspect.yaml ├── .gitignore ├── .golangci ├── .golic.yaml ├── .licignore ├── CODEOWNERS ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── inject.go ├── remove.go ├── root.go ├── runner.go └── version.go ├── config.yaml ├── go.mod ├── go.sum ├── impl └── update │ ├── config.go │ ├── match.go │ ├── opts.go │ └── update.go ├── main.go └── utils ├── guard └── guard.go └── log └── log.go /.github/workflows/inspect.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Absa Group Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic 16 | name: Inspect [linters, tests] 17 | 18 | on: 19 | [workflow_dispatch, push] 20 | jobs: 21 | go-inspect: 22 | name: Inspect packages 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | # see: https://golangci-lint.run/usage/configuration/#config-file 27 | - name: golangci-lint 28 | uses: golangci/golangci-lint-action@v2.5.2 29 | with: 30 | version: v1.42.1 31 | - name: golic 32 | run: | 33 | go install github.com/AbsaOSS/golic@v0.5.0 34 | golic inject -c "2022 Absa Group Limited" -t apache2 35 | - name: go test 36 | run: go test ./... 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | 4 | # But not these files... 5 | !CODEOWNERS 6 | !LICENSE 7 | !README.md 8 | !/.gitignore 9 | !.licignore 10 | !.golangci 11 | !*.yaml 12 | !Makefile 13 | !*.go 14 | !go.mod 15 | !go.sum 16 | 17 | # ...even if they are in subdirectories 18 | !*/ 19 | 20 | -------------------------------------------------------------------------------- /.golangci: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - bodyclose 5 | - deadcode 6 | - errcheck 7 | - gocritic 8 | - gocyclo 9 | - goimports 10 | - revive 11 | - gosec 12 | - gosimple 13 | - govet 14 | - ineffassign 15 | - misspell 16 | - nakedret 17 | - lll 18 | - noctx 19 | - rowserrcheck 20 | - staticcheck 21 | - structcheck 22 | - stylecheck 23 | - typecheck 24 | - unconvert 25 | - unparam 26 | - unused 27 | - varcheck 28 | - depguard 29 | - dogsled 30 | - exportloopref 31 | - goconst 32 | - goprintffuncname 33 | - nolintlint 34 | - dupl 35 | - gochecknoinits 36 | 37 | # don't enable: 38 | # - golint # deprecated 39 | # - whitespace 40 | # - funlen 41 | # - exhaustive 42 | # - gomnd 43 | # - gofmt 44 | 45 | run: 46 | deadline: 3m 47 | linters-settings: 48 | lll: 49 | line-length: 150 50 | -------------------------------------------------------------------------------- /.golic.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Absa Group Limited 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic 16 | golic: 17 | licenses: 18 | apache2: | 19 | Copyright {{copyright}} 20 | 21 | Licensed under the Apache License, Version 2.0 (the "License"); 22 | you may not use this file except in compliance with the License. 23 | You may obtain a copy of the License at 24 | 25 | http://www.apache.org/licenses/LICENSE-2.0 26 | 27 | Unless required by applicable law or agreed to in writing, software 28 | distributed under the License is distributed on an "AS IS" BASIS, 29 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30 | See the License for the specific language governing permissions and 31 | limitations under the License. 32 | 33 | Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic 34 | copyright: | 35 | Copyright © {{copyright}} 36 | 37 | All rights reserved 38 | 39 | Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic 40 | rules: 41 | .go: 42 | prefix: "\n/*" 43 | suffix: "*/" 44 | under: 45 | - "package *" 46 | .java: 47 | prefix: "/*" 48 | suffix: "*/" 49 | .scala: 50 | prefix: "/*" 51 | suffix: "*/" 52 | .cs: 53 | prefix: "/*" 54 | suffix: "*/" 55 | .html: 56 | prefix: "" 58 | .js: 59 | prefix: "/*" 60 | suffix: "*/" 61 | .css: 62 | prefix: "/*" 63 | suffix: "*/" 64 | .yaml: 65 | prefix: "# " 66 | .yml: 67 | prefix: "# " 68 | Dockerfile*: 69 | prefix: "# " 70 | Makefile: 71 | prefix: "# " 72 | .gitignore: 73 | prefix: "# " 74 | .licignore: 75 | prefix: "# " 76 | .tf: 77 | prefix: "# " 78 | .sh: 79 | prefix: "# " 80 | under: 81 | - "#!/usr/bin/*" 82 | - "#!/bin/*" 83 | .xml: 84 | prefix: "" 86 | under: 87 | - " 0 { 42 | if runePattern[0] == '*' { 43 | isMatchingMatrix[0][1] = true 44 | } 45 | } 46 | 47 | for j := 2; j <= lenPattern; j++ { 48 | if runePattern[j-1] == '*' { 49 | isMatchingMatrix[0][j] = isMatchingMatrix[0][j-1] 50 | } 51 | 52 | } 53 | 54 | for i := 1; i <= lenInput; i++ { 55 | for j := 1; j <= lenPattern; j++ { 56 | 57 | if runePattern[j-1] == '*' { 58 | isMatchingMatrix[i][j] = isMatchingMatrix[i-1][j] || isMatchingMatrix[i][j-1] 59 | } 60 | 61 | if runePattern[j-1] == '?' || runeInput[i-1] == runePattern[j-1] { 62 | isMatchingMatrix[i][j] = isMatchingMatrix[i-1][j-1] 63 | } 64 | } 65 | } 66 | 67 | return isMatchingMatrix[lenInput][lenPattern] 68 | } 69 | 70 | -------------------------------------------------------------------------------- /impl/update/opts.go: -------------------------------------------------------------------------------- 1 | package update 2 | 3 | /* 4 | Copyright 2022 Absa Group Limited 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic 19 | */ 20 | 21 | type LicenseCommandType int 22 | 23 | const ( 24 | LicenseInject LicenseCommandType = 0 25 | LicenseRemove LicenseCommandType = 1 26 | ) 27 | 28 | type Options struct { 29 | LicIgnore string 30 | Copyright string 31 | Dry bool 32 | ConfigPath string 33 | Template string 34 | ModifiedExitStatus bool 35 | MasterConfig string 36 | Type LicenseCommandType 37 | } 38 | -------------------------------------------------------------------------------- /impl/update/update.go: -------------------------------------------------------------------------------- 1 | package update 2 | 3 | /* 4 | Copyright 2022 Absa Group Limited 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic 19 | */ 20 | 21 | import ( 22 | "context" 23 | "fmt" 24 | "io/ioutil" 25 | "os" 26 | "path/filepath" 27 | "strings" 28 | 29 | "gopkg.in/yaml.v3" 30 | 31 | "github.com/AbsaOSS/golic/utils/log" 32 | 33 | "github.com/denormal/go-gitignore" 34 | "github.com/enescakir/emoji" 35 | "github.com/logrusorgru/aurora" 36 | ) 37 | 38 | type Update struct { 39 | opts Options 40 | ctx context.Context 41 | ignore gitignore.GitIgnore 42 | cfg *Config 43 | modified int 44 | } 45 | 46 | var logger = log.Log 47 | 48 | func New(ctx context.Context, options Options) *Update { 49 | return &Update{ 50 | ctx: ctx, 51 | opts: options, 52 | } 53 | } 54 | 55 | func (u *Update) Run() (err error) { 56 | logger.Info().Msgf("%s reading %s", emoji.OpenBook, u.opts.LicIgnore) 57 | u.ignore, err = gitignore.NewFromFile(u.opts.LicIgnore) 58 | if err != nil { 59 | return err 60 | } 61 | logger.Info().Msgf("%s reading %s; use --verbose to see details", emoji.OpenBook, aurora.BrightCyan("master config")) 62 | if u.cfg, err = u.readCommonConfig(); err != nil { 63 | return 64 | } 65 | if _, err = os.Stat(u.opts.ConfigPath); !os.IsNotExist(err) { 66 | logger.Info().Msgf("%s reading %s", emoji.OpenBook, aurora.BrightCyan(u.opts.ConfigPath)) 67 | logger.Info().Msgf("%s overriding %s with %s", 68 | emoji.ConstructionWorker, aurora.BrightCyan("master config"), aurora.BrightCyan(u.opts.ConfigPath)) 69 | if u.cfg, err = u.readLocalConfig(); err != nil { 70 | return 71 | } 72 | } else { 73 | logger.Info().Msgf("%s skipping local %s", emoji.FileFolder, aurora.BrightCyan(u.opts.ConfigPath)) 74 | err = nil 75 | } 76 | u.traverse() 77 | return 78 | } 79 | 80 | func (u *Update) String() string { 81 | switch u.opts.Type { 82 | case LicenseInject: 83 | return aurora.BrightCyan("inject").String() 84 | case LicenseRemove: 85 | return aurora.BrightCyan("remove").String() 86 | } 87 | return aurora.BrightRed("ERROR, unrecognised command").String() 88 | } 89 | 90 | func (u *Update) ExitCode() int { 91 | if u.opts.ModifiedExitStatus && u.modified != 0 { 92 | return 1 93 | } 94 | return 0 95 | } 96 | 97 | func read(f string) (s string, err error) { 98 | content, err := ioutil.ReadFile(f) 99 | if err != nil { 100 | return 101 | } 102 | // Convert []byte to string and print to screen 103 | return string(content), nil 104 | } 105 | 106 | func (u *Update) traverse() { 107 | skipped := 0 108 | visited := 0 109 | p := func(path string, i gitignore.GitIgnore, o Options, config *Config) (err error) { 110 | if !i.Ignore(path) { 111 | var skip bool 112 | symbol := "" 113 | cp := aurora.BrightYellow(path) 114 | visited++ 115 | if err, skip = update(path, o, config); skip { 116 | symbol = "-> skip" 117 | cp = aurora.Magenta(path) 118 | skipped++ 119 | } 120 | _, _ = emoji.Printf(" %s %s %s \n", emoji.Minus, cp, aurora.BrightMagenta(symbol)) 121 | } 122 | return 123 | } 124 | 125 | err := filepath.Walk("./", 126 | func(path string, info os.FileInfo, err error) error { 127 | if err != nil { 128 | return err 129 | } 130 | if !info.IsDir() { 131 | return p(path, u.ignore, u.opts, u.cfg) 132 | } 133 | return nil 134 | }) 135 | if err != nil { 136 | logger.Err(err).Msg("") 137 | } 138 | u.modified = visited - skipped 139 | summary(skipped, visited) 140 | } 141 | 142 | func summary(skipped, visited int) { 143 | if skipped == visited { 144 | fmt.Printf("\n %s %v/%v %s\n\n", emoji.Ice, aurora.BrightCyan(visited-skipped), aurora.BrightWhite(visited), aurora.BrightCyan("changed")) 145 | return 146 | } 147 | fmt.Printf("\n %s %v/%v %s\n\n", emoji.Fire, aurora.BrightYellow(visited-skipped), aurora.BrightWhite(visited), aurora.BrightYellow("changed")) 148 | } 149 | 150 | func update(path string, o Options, config *Config) (err error, skip bool) { 151 | switch o.Type { 152 | case LicenseInject: 153 | return inject(path, o, config) 154 | case LicenseRemove: 155 | return remove(path, o, config) 156 | } 157 | return fmt.Errorf("invalid license type"), true 158 | } 159 | 160 | func inject(path string, o Options, config *Config) (err error, skip bool) { 161 | source, err := read(path) 162 | if err != nil { 163 | return err, false 164 | } 165 | rule := getRule(config, path) 166 | license, err := getCommentedLicense(config, o, rule) 167 | if err != nil { 168 | return err, false 169 | } 170 | // license is injected, continue 171 | if strings.Contains(source, license) { 172 | return nil, true 173 | } 174 | // split file to header and footer and extend with license 175 | header, footer := splitSource(source,config.Golic.Rules[rule].Under) 176 | if header != "" { 177 | header = header + "\n" 178 | } 179 | source = fmt.Sprintf("%s%s%s", header, license, footer) 180 | 181 | if !o.Dry { 182 | data := []byte(source) 183 | err = ioutil.WriteFile(path, data, os.ModeExclusive) 184 | } 185 | return 186 | } 187 | 188 | func remove(path string, o Options, config *Config) (err error, skip bool) { 189 | source, err := read(path) 190 | if err != nil { 191 | return err, false 192 | } 193 | rule := getRule(config, path) 194 | license, err := getCommentedLicense(config, o, rule) 195 | if err != nil { 196 | return err, false 197 | } 198 | if strings.Contains(source, license) { 199 | return RemoveFromFile(path, o, source, license, err), false 200 | } 201 | return nil, true 202 | } 203 | 204 | func RemoveFromFile(path string, o Options, source string, license string, err error) error { 205 | if !o.Dry { 206 | source = strings.Replace(source, license, "", 1) 207 | err = ioutil.WriteFile(path, []byte(source), os.ModeExclusive) 208 | } 209 | return err 210 | } 211 | 212 | func matchRule(config *Config, fileName string) (rule string, ok bool) { 213 | if _, ok = config.Golic.Rules[fileName]; ok { 214 | return fileName, ok 215 | } 216 | // if rule is pattern like Dockerfile* 217 | for k := range config.Golic.Rules { 218 | matched, _ := filepath.Match(k, fileName) 219 | if matched { 220 | return k, true 221 | } 222 | } 223 | return 224 | } 225 | 226 | func getCommentedLicense(config *Config, o Options, file string) (string, error) { 227 | var ok bool 228 | var template string 229 | var rule string 230 | if template, ok = config.Golic.Licenses[o.Template]; !ok { 231 | return "", fmt.Errorf("no license found for %s, check configuration (.golic.yaml)", o.Template) 232 | } 233 | //if _, ok = config.Golic.Rules[rule]; !ok { 234 | if rule, ok = matchRule(config, file); !ok { 235 | return "", fmt.Errorf("no rule found for %s, check configuration (.golic.yaml)", rule) 236 | } 237 | template = strings.ReplaceAll(template, "{{copyright}}", o.Copyright) 238 | if config.IsWrapped(rule) { 239 | return fmt.Sprintf("%s\n%s%s\n", 240 | config.Golic.Rules[rule].Prefix, 241 | template, 242 | config.Golic.Rules[rule].Suffix), 243 | nil 244 | } 245 | // `\r\n` -> `\r\n #`, `\n` -> `\n #` 246 | content := strings.ReplaceAll(template, "\n", fmt.Sprintf("\n%s", config.Golic.Rules[rule].Prefix)) 247 | content = strings.TrimSuffix(content, config.Golic.Rules[rule].Prefix) 248 | content = config.Golic.Rules[rule].Prefix + content 249 | // "# \n" -> "#\n" // "# \r\n" -> "#\r\n"; some environments automatically remove spaces in empty lines. This makes problems in license PR's 250 | cleanedPrefix := strings.TrimSuffix(config.Golic.Rules[rule].Prefix, " ") 251 | content = strings.ReplaceAll(content, fmt.Sprintf("%s \n", cleanedPrefix), fmt.Sprintf("%s\n", cleanedPrefix)) 252 | content = strings.ReplaceAll(content, fmt.Sprintf("%s \r\n", cleanedPrefix), fmt.Sprintf("%s\r\n", cleanedPrefix)) 253 | return content, nil 254 | } 255 | 256 | func splitSource(source string, rules []string) (header, footer string) { 257 | lines := strings.Split(source, "\n") 258 | if len(rules) == 0 { 259 | return "", source 260 | } 261 | for _, r := range rules { 262 | header, footer = findHeaderAndFooter(lines, r) 263 | if header != "" { 264 | return 265 | } 266 | } 267 | return 268 | } 269 | 270 | func findHeaderAndFooter(lines []string, match string) (header, footer string){ 271 | for i, l := range lines { 272 | if isMatch(l, match) { 273 | header = strings.Join(lines[0:i+1], "\n") 274 | footer = strings.Join(lines[i+1:], "\n") 275 | return 276 | } 277 | } 278 | return "", strings.Join(lines, "\n") 279 | } 280 | 281 | func getRule(config *Config, path string) (rule string) { 282 | fileName := filepath.Base(path) 283 | for k := range config.Golic.Rules { 284 | matched, _ := filepath.Match(k, fileName) 285 | if matched { 286 | return k 287 | } 288 | } 289 | rule = filepath.Ext(path) 290 | if rule == "" { 291 | rule = filepath.Base(path) 292 | } 293 | return 294 | } 295 | 296 | func (u *Update) readLocalConfig() (*Config, error) { 297 | var c = &Config{} 298 | var rc = *u.cfg 299 | yamlFile, err := ioutil.ReadFile(u.opts.ConfigPath) 300 | if err != nil { 301 | return nil, nil 302 | } 303 | err = yaml.Unmarshal(yamlFile, c) 304 | if err != nil { 305 | return nil, nil 306 | } 307 | for k, v := range c.Golic.Licenses { 308 | rc.Golic.Licenses[k] = v 309 | } 310 | for k, v := range c.Golic.Rules { 311 | rc.Golic.Rules[k] = v 312 | } 313 | return &rc, nil 314 | } 315 | 316 | func (u *Update) readCommonConfig() (c *Config, err error) { 317 | c = &Config{} 318 | err = yaml.Unmarshal([]byte(u.opts.MasterConfig), c) 319 | return 320 | } 321 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | Copyright 2022 Absa Group Limited 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic 19 | */ 20 | 21 | import ( 22 | _ "embed" 23 | "fmt" 24 | 25 | "github.com/AbsaOSS/golic/cmd" 26 | ) 27 | 28 | //go:embed .golic.yaml 29 | var golic string 30 | 31 | func main() { 32 | fmt.Println() 33 | cmd.Execute(golic) 34 | } 35 | -------------------------------------------------------------------------------- /utils/guard/guard.go: -------------------------------------------------------------------------------- 1 | // Package guard throws errors or panics when error occur 2 | package guard 3 | 4 | /* 5 | Copyright 2022 Absa Group Limited 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | 19 | Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic 20 | */ 21 | 22 | import ( 23 | "github.com/AbsaOSS/golic/utils/log" 24 | ) 25 | 26 | var logger = log.Log 27 | 28 | func FailOnError(err error, message string, v ...interface{}) { 29 | if err != nil { 30 | logger.Fatal().Err(err).Msgf(message, v...) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /utils/log/log.go: -------------------------------------------------------------------------------- 1 | // Package log wraps zerolog logger and provides standard log functionality 2 | package log 3 | 4 | /* 5 | Copyright 2022 Absa Group Limited 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | 19 | Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic 20 | */ 21 | 22 | import ( 23 | "os" 24 | 25 | "github.com/rs/zerolog" 26 | "github.com/rs/zerolog/pkgerrors" 27 | ) 28 | 29 | // Log is the global logger. 30 | var Log *zerolog.Logger 31 | 32 | //init initializes the logger 33 | func init() { 34 | zerolog.SetGlobalLevel(zerolog.DebugLevel) 35 | zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack 36 | l := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "15:04:05", NoColor: false}). 37 | With(). 38 | Timestamp(). 39 | Logger() 40 | Log = &l 41 | //Log.Info().Msg("logger configured") 42 | } 43 | --------------------------------------------------------------------------------