Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
choffmeister committed Nov 11, 2021
0 parents commit 1806dd5
Show file tree
Hide file tree
Showing 21 changed files with 1,727 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.git

.dockerignore
.editorconfig
.gitignore
Dockerfile
LICENSE
Makefile
README.md

build/
coverage.out
17 changes: 17 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.go]
indent_style = tab
indent_size = 4

[Makefile]
indent_style = tab
indent_size = 4
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build/
coverage.out
.git-ops-update.yaml
.git-ops-update.*.yaml
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM golang:1.14-alpine AS builder

WORKDIR /build
COPY ./ /build/
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o git-ops-update ./src

FROM alpine
COPY --from=builder /build/git-ops-update /bin/git-ops-update
WORKDIR /workdir
ENTRYPOINT ["/bin/git-ops-update"]
27 changes: 27 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright (c) 2021 Christian Hoffmeister. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Christian Hoffmeister nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40 changes: 40 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.PHONY: run test test-watch build push

# The binary to build (just the basename).
BIN := git-ops-update

# Where to push the docker image.
REGISTRY ?= choffmeister

IMAGE := $(REGISTRY)/$(BIN)

# This version-strategy uses git tags to set the version string
VERSION := $(shell git describe --tags --always --dirty)

run:
go run ./src

build:
mkdir -p build/
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o build/git-ops-update-linux-amd64 ./src
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o build/git-ops-update-darwin-amd64 ./src
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o build/git-ops-update-windows-amd64.exe ./src

test:
go test -v ./src

test-watch:
watch -n1 go test -v ./src

test-cover:
go test -coverprofile=coverage.out
go tool cover -func=coverage.out
go tool cover -html=coverage.out

container:
docker build -t $(IMAGE):$(VERSION) .

container-push: container
docker push $(IMAGE):$(VERSION)
docker tag $(IMAGE):$(VERSION) $(IMAGE):latest
docker push $(IMAGE):latest
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# git-ops-update

Replacement for `git describe --tags` that produces [semver](https://semver.org/) compatible versions that follow to semver sorting rules.

## Usage

TODO

### Binary

```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/choffmeister/git-ops-update/master/install.sh)"
cd ~/my-git-directory
~/bin/git-ops-update
```

### Docker

```bash
cd my-git-directory
docker pull choffmeister/git-ops-update:latest
docker run --rm -v $PWD:/workdir choffmeister/git-ops-update:latest
```
13 changes: 13 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module github.com/choffmeister/git-ops-update

go 1.14

require (
github.com/blang/semver/v4 v4.0.0
github.com/google/go-cmp v0.5.6
github.com/heroku/docker-registry-client v0.0.0-20211012143308-9463674c8930
github.com/stretchr/testify v1.7.0
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
k8s.io/apimachinery v0.22.3
)
335 changes: 335 additions & 0 deletions go.sum

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
set -eo pipefail

if [[ "$OSTYPE" == "linux-gnu"* ]]; then
VARIANT="linux-amd64"
mkdir -p "$HOME/bin"
TARGET="$HOME/bin/git-ops-update"
elif [[ "$OSTYPE" == "darwin"* ]]; then
VARIANT="darwin-amd64"
mkdir -p "$HOME/bin"
TARGET="$HOME/bin/git-ops-update"
else
echo "Unknown OS type $OSTYPE"
exit 1
fi
LATEST_VERSION="$(curl -s https://api.github.com/repos/choffmeister/git-ops-update/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3)}')"

curl -fsSL -o "$TARGET" "https://github.com/choffmeister/git-ops-update/releases/download/$LATEST_VERSION/git-ops-update-$VARIANT"
chmod +x "$TARGET"
46 changes: 46 additions & 0 deletions src/annotation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package main

import (
utiljson "encoding/json"
"strings"

"gopkg.in/yaml.v3"
)

func visitAnnotationsRecursion(parentNodes []*yaml.Node, node *yaml.Node, identifier string, fn func(keyNode *yaml.Node, valueNode *yaml.Node, parentNodes []*yaml.Node, annotation string) error) error {
if node.Kind == yaml.MappingNode {
for i := 0; i < len(node.Content); i += 2 {
keyNode := node.Content[i]
valueNode := node.Content[i+1]
if valueNode.Kind == yaml.ScalarNode {
annotationStr := strings.TrimPrefix(valueNode.LineComment, "#")
annotationJson := map[string]interface{}{}
utiljson.Unmarshal([]byte(annotationStr), &annotationJson)
annotation, ok := annotationJson["$"+identifier].(string)
if ok {
err := fn(keyNode, valueNode, []*yaml.Node{}, annotation)
if err != nil {
return err
}
}
} else {
err := visitAnnotationsRecursion(append(parentNodes, node), valueNode, identifier, fn)
if err != nil {
return err
}
}
}
} else {
for _, child := range node.Content {
err := visitAnnotationsRecursion(append(parentNodes, node), child, identifier, fn)
if err != nil {
return err
}
}
}
return nil
}

func VisitAnnotations(node *yaml.Node, identifier string, fn func(keyNode *yaml.Node, valueNode *yaml.Node, parentNodes []*yaml.Node, annotation string) error) error {
return visitAnnotationsRecursion([]*yaml.Node{}, node, identifier, fn)
}
38 changes: 38 additions & 0 deletions src/annotation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"testing"

"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)

func TestVisitAnnotations(t *testing.T) {
input := `apple: pie
foo:
bar:
apple: pie # {"$append":"-new"}
another: thing # {"$append":"-new2"}
`
doc := yaml.Node{}
err := yaml.Unmarshal([]byte(input), &doc)
if assert.NoError(t, err) {
err = VisitAnnotations(&doc, "append", func(keyNode *yaml.Node, valueNode *yaml.Node, parentNodes []*yaml.Node, annotation string) error {
valueNode.Value = valueNode.Value + annotation
return nil
})
if assert.NoError(t, err) {
actualOutputBytes, err := yaml.Marshal(&doc)
if assert.NoError(t, err) {
actualOutput := string(actualOutputBytes)
expectedOutput := `apple: pie
foo:
bar:
apple: pie-new # {"$append":"-new"}
another: thing-new2 # {"$append":"-new2"}
`
assert.Equal(t, expectedOutput, actualOutput)
}
}
}
}
97 changes: 97 additions & 0 deletions src/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package main

import (
utiljson "encoding/json"
"fmt"
"regexp"

"github.com/google/go-cmp/cmp"
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
)

// GitOpsUpdaterConfigFiles ...
type GitOpsUpdaterConfigFiles struct {
Includes []string `json:"includes"`
Excludes []string `json:"excludes"`
}

// GitOpsUpdaterConfig ...
type GitOpsUpdaterConfig struct {
Files GitOpsUpdaterConfigFiles `json:"files"`
RegistryConfigs []RegistryConfig `json:"registries"`
PolicyConfigs []PolicyConfig `json:"policies"`
}

// LoadGitOpsUpdaterConfig ...
func LoadGitOpsUpdaterConfig(yaml []byte) (*GitOpsUpdaterConfig, *map[string]Registry, *map[string]Policy, error) {
config := &GitOpsUpdaterConfig{}

json, err := utilyaml.ToJSON(yaml)
if err != nil {
return nil, nil, nil, err
}

if err = utiljson.Unmarshal(json, config); err != nil {
return nil, nil, nil, err
}

registries := map[string]Registry{}
for _, r := range config.RegistryConfigs {
if r.Docker != nil {
registries[r.Name] = DockerRegistry{
Name: r.Name,
Interval: r.Interval,
Config: r.Docker,
}
} else if r.Helm != nil {
registries[r.Name] = HelmRegistry{
Name: r.Name,
Interval: r.Interval,
Config: r.Helm,
}
} else {
return nil, nil, nil, fmt.Errorf("registry %s is invalid", r.Name)
}
}

policies := map[string]Policy{}
for _, p := range config.PolicyConfigs {
extracts := []Extract{}
for ei, e := range p.Extracts {
if e.Lexicographic != nil {
extracts = append(extracts, Extract{Value: e.Value, Strategy: LexicographicExtractStrategy{}})
} else if e.Numeric != nil {
extracts = append(extracts, Extract{Value: e.Value, Strategy: NumericExtractStrategy{}})
} else if e.Semver != nil {
extracts = append(extracts, Extract{Value: e.Value, Strategy: SemverExtractStrategy{}})
} else {
return nil, nil, nil, fmt.Errorf("policy %s strategy %d is invalid", p.Name, ei)
}
}
if len(extracts) == 0 {
return nil, nil, nil, fmt.Errorf("policy %s has no extracts", p.Name)
}
pattern, err := regexp.Compile(p.Pattern)
if err != nil {
return nil, nil, nil, fmt.Errorf("policy %s pattern %s is invalid", p.Name, p.Pattern)
}
if p.Pattern == "" {
pattern = nil
}
policies[p.Name] = Policy{
Name: p.Name,
Pattern: pattern,
Extracts: extracts,
}
}

return config, &registries, &policies, nil
}

// Equal ...
func (c1 GitOpsUpdaterConfig) Equal(c2 GitOpsUpdaterConfig) bool {
return true &&
cmp.Equal(c1.Files, c2.Files) &&
cmp.Equal(c1.RegistryConfigs, c2.RegistryConfigs) &&
cmp.Equal(c1.PolicyConfigs, c2.PolicyConfigs)
}
Loading

0 comments on commit 1806dd5

Please sign in to comment.