Skip to content

Commit

Permalink
create up to 4 schemas locations
Browse files Browse the repository at this point in the history
also go fmt and validate on PR
  • Loading branch information
djeebus committed Oct 10, 2023
1 parent 6de1593 commit e53eea5
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 101 deletions.
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[*.go]
ij_any_blank_lines_after_imports = 1
ij_go_add_parentheses_for_single_import = true
ij_go_GROUP_CURRENT_PROJECT_IMPORTS = true
ij_go_group_stdlib_imports = true
ij_go_import_sorting = gofmt
ij_go_move_all_imports_in_one_declaration = true
ij_go_move_all_stdlib_imports_in_one_group = true
ij_go_remove_redundant_import_aliases = true
ij_go_use_back_quotes_for_imports = false
14 changes: 1 addition & 13 deletions .github/workflows/on_pull-request_docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,4 @@ jobs:
--GOLANG_VERSION="${{ env.GOLANG_TOOL_VERSION }}"
- name: verify that the checked in file has not changed
run: |
#!/usr/bin/env bash
exitCode=0
# Log the actual diff for debugging purposes
git diff --name-only | cat
if ! git diff --exit-code --quiet; then
echo "Please run 'earthly +rebuild-docs' and commit the results to this PR"
exitCode=1
fi
exit $exitCode
run: ./hacks/exit-on-changed-files.sh "Please run 'earthly +rebuild-docs' and commit the results to this PR"
10 changes: 10 additions & 0 deletions Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ test:
BUILD +ci-helm

ci-golang:
BUILD +fmt-golang
BUILD +lint-golang
BUILD +validate-golang
BUILD +test-golang
Expand Down Expand Up @@ -144,6 +145,15 @@ docker-debug:

SAVE IMAGE --push $CI_REGISTRY_IMAGE

fmt-golang:
FROM +go-deps

WORKDIR /src
COPY . /src

RUN go fmt \
&& ./hacks/exit-on-changed-files.sh

lint-golang:
ARG STATICCHECK_VERSION="2023.1.3"

Expand Down
14 changes: 7 additions & 7 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ func init() {
flags := RootCmd.PersistentFlags()
stringFlag(flags, "log-level", "Set the log output level.",
newStringOpts().
withChoices(
zerolog.LevelErrorValue,
zerolog.LevelWarnValue,
zerolog.LevelInfoValue,
zerolog.LevelDebugValue,
zerolog.LevelTraceValue,
).
withChoices(
zerolog.LevelErrorValue,
zerolog.LevelWarnValue,
zerolog.LevelInfoValue,
zerolog.LevelDebugValue,
zerolog.LevelTraceValue,
).
withDefault("info").
withShortHand("l"),
)
Expand Down
15 changes: 15 additions & 0 deletions hacks/exit-on-changed-files.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash

set -e

exitCode=0
message=$1

# Log the actual diff for debugging purposes
git diff --name-only | cat
if ! git diff --exit-code --quiet; then
echo "$message"
exitCode=1
fi

exit $exitCode
6 changes: 3 additions & 3 deletions pkg/affected_apps/best_effort.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ func isKustomizeApp(file string) bool {

func isKustomizeBaseComponentsChange(file string) bool {
return strings.Contains(file, "base/") ||
strings.Contains(file, "bases/") ||
strings.Contains(file, "components/") ||
strings.Contains(file, "resources/")
strings.Contains(file, "bases/") ||
strings.Contains(file, "components/") ||
strings.Contains(file, "resources/")
}

func overlaysDir(file string) string {
Expand Down
11 changes: 6 additions & 5 deletions pkg/events/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"

"github.com/zapier/kubechecks/pkg"
"github.com/zapier/kubechecks/pkg/affected_apps"
"github.com/zapier/kubechecks/pkg/argo_client"
Expand All @@ -22,10 +27,6 @@ import (
"github.com/zapier/kubechecks/pkg/validate"
"github.com/zapier/kubechecks/pkg/vcs_clients"
"github.com/zapier/kubechecks/telemetry"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
)

type CheckEvent struct {
Expand Down Expand Up @@ -346,7 +347,7 @@ func (ce *CheckEvent) processApp(ctx context.Context, app, dir string) error {
}
}()

s, err := validate.ArgoCdAppValidate(grpCtx, app, k8sVersion, formattedManifests)
s, err := validate.ArgoCdAppValidate(grpCtx, app, k8sVersion, ce.TempWorkingDir, formattedManifests)
if err != nil {
telemetry.SetError(span, err, taskDescription)
ce.vcsNote.AddToAppMessage(grpCtx, app, fmt.Sprintf(errorCommentFormat, taskDescription, err))
Expand Down
1 change: 0 additions & 1 deletion pkg/gitlab_client/pipeline.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package gitlab_client

import (

"github.com/rs/zerolog/log"
"github.com/xanzy/go-gitlab"
)
Expand Down
70 changes: 70 additions & 0 deletions pkg/validate/schemas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package validate

import (
"context"
"os"
"os/exec"
"sync"

"github.com/rs/zerolog/log"

"github.com/zapier/kubechecks/pkg/repo"
)

type ReposDirectory struct {
paths map[string]string

mutex sync.Mutex
}

func newReposDirectory() *ReposDirectory {
rd := &ReposDirectory{
paths: make(map[string]string),
}

return rd
}

func (rd *ReposDirectory) Register(ctx context.Context, cloneUrl string) string {
var (
ok bool
repoDir string
)

rd.mutex.Lock()
defer rd.mutex.Unlock()

repoDir, ok = rd.paths[cloneUrl]
if ok {
rd.fetchLatest()
return repoDir
}

return rd.clone(ctx, cloneUrl)
}

func (rd *ReposDirectory) fetchLatest() {
cmd := exec.Command("git", "pull")
err := cmd.Run()
if err != nil {
log.Err(err).Msg("failed to pull latest")
}
}

func (rd *ReposDirectory) clone(ctx context.Context, cloneUrl string) string {
repoDir, err := os.MkdirTemp("/tmp", "schemas")
if err != nil {
log.Err(err).Msg("failed to make temp dir")
return ""
}

r := repo.Repo{CloneURL: cloneUrl}
err = r.CloneRepoLocal(ctx, repoDir)
if err != nil {
log.Err(err).Msg("failed to clone schemas repository")
return ""
}

rd.paths[cloneUrl] = repoDir
return repoDir
}
104 changes: 40 additions & 64 deletions pkg/validate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,16 @@ import (
"os"
"path/filepath"
"strings"
"sync"

"github.com/robfig/cron/v3"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
"github.com/yannh/kubeconform/pkg/validator"
"go.opentelemetry.io/otel"

"github.com/zapier/kubechecks/pkg"
"github.com/zapier/kubechecks/pkg/repo"
"github.com/zapier/kubechecks/telemetry"
)

var getSchemasOnce sync.Once // used to ensure we don't reauth this
var refreshSchemasOnce sync.Once
var reposCache = newReposDirectory()

const kubeconformCommentFormat = `
<details><summary><b>Show kubeconform report:</b> %s</summary>
Expand All @@ -32,70 +27,43 @@ const kubeconformCommentFormat = `
</details>
`
const inRepoSchemaLocation = "./schemas"

var localSchemasLocation string

func getSchemaLocations() []string {
getSchemasOnce.Do(func() {
ctx := context.Background()
_, span := otel.Tracer("Kubechecks").Start(ctx, "GetSchemaLocations")
schemasLocation := viper.GetString("schemas-location")
func getSchemaLocations(ctx context.Context, tempRepoPath string) []string {
locations := []string{
// schemas included in kubechecks
"default",

var oldLocalSchemasLocation string
// Store the oldSchemasLocation for clean up afterwards
if localSchemasLocation != inRepoSchemaLocation {
oldLocalSchemasLocation = localSchemasLocation
}
// schemas collected globally
"https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/{{ .NormalizedKubernetesVersion }}/{{ .ResourceKind }}{{ .KindSuffix }}.json",
}

localSchemasLocation = inRepoSchemaLocation
// if this is a git repo, we want to clone it locally
if strings.HasPrefix(schemasLocation, "https://") || strings.HasPrefix(schemasLocation, "http://") || strings.HasPrefix(schemasLocation, "git@") {

tmpSchemasLocalDir, err := os.MkdirTemp("/tmp", "schemas")
if err != nil {
log.Err(err).Msg("failed to make temporary directory for downloading schemas")
telemetry.SetError(span, err, "failed to make temporary directory for downloading schemas")
return
}

r := repo.Repo{CloneURL: schemasLocation}
err = r.CloneRepoLocal(ctx, tmpSchemasLocalDir)
if err != nil {
telemetry.SetError(span, err, fmt.Sprintf("failed to clone schemas repository %s", schemasLocation))
log.Err(err).Msgf("failed to clone schemas repository %s", schemasLocation)
return
}

log.Debug().Str("schemas-repo", schemasLocation).Msgf("Cloned schemas Repo %s to /tmp/schemas", schemasLocation)
localSchemasLocation = tmpSchemasLocalDir

err = os.RemoveAll(oldLocalSchemasLocation)
if err != nil {
telemetry.SetError(span, err, "failed to clean up old schemas directory")
log.Err(err).Msg("failed to clean up old schemas directory")
}

// This is a little function to allow getSchemaLocations to refresh daily by resetting the sync.Once mutex
refreshSchemasOnce.Do(func() {
c := cron.New()
c.AddFunc("@daily", func() {
log.Info().Msg("resetting schemas lock to allow refresh")
getSchemasOnce = *new(sync.Once)
})
c.Start()
})
// schemas configured globally
schemasLocation := viper.GetString("schemas-location")
log.Debug().Str("schemas-location", schemasLocation).Msg("viper")
if strings.HasPrefix(schemasLocation, "https://") || strings.HasPrefix(schemasLocation, "http://") || strings.HasPrefix(schemasLocation, "git@") {
log.Debug().Str("location", schemasLocation).Msg("registering remote schema repository")
repoPath := reposCache.Register(ctx, schemasLocation)
if repoPath != "" {
locations = append(locations, repoPath)
}
})
} else if schemasLocation != "" {
log.Debug().Str("location", schemasLocation).Msg("registering in-repo schema repository")
locations = append(locations, schemasLocation)
}

return []string{
localSchemasLocation + `/{{ .NormalizedKubernetesVersion }}/{{ .ResourceKind }}{{ .KindSuffix }}.json`,
"default",
"https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/{{ .NormalizedKubernetesVersion }}/{{ .ResourceKind }}{{ .KindSuffix }}.json",
// bring in schemas that might be in the cloned repository
schemaPath := filepath.Join(tempRepoPath, "schemas")
if stat, err := os.Stat(schemaPath); err == nil && stat.IsDir() {
log.Debug().Str("path", schemaPath).Msg("found in-repo ./schemas path")
locations = append(locations, fmt.Sprintf("%s/{{ .NormalizedKubernetesVersion }}/{{ .ResourceKind }}{{ .KindSuffix }}.json", schemaPath))
} else {
log.Debug().Err(err).Msg("failed to find in-repo ./schemas")
}

return locations
}

func ArgoCdAppValidate(ctx context.Context, appName, targetKubernetesVersion string, appManifests []string) (string, error) {
func ArgoCdAppValidate(ctx context.Context, appName, targetKubernetesVersion, tempRepoPath string, appManifests []string) (string, error) {
_, span := otel.Tracer("Kubechecks").Start(ctx, "ArgoCdAppValidate")
defer span.End()

Expand All @@ -110,11 +78,19 @@ func ArgoCdAppValidate(ctx context.Context, appName, targetKubernetesVersion str
KubernetesVersion: targetKubernetesVersion,
Strict: true,
IgnoreMissingSchemas: false,
Debug: log.Debug().Enabled(),
}

var outputString []string
var (
outputString []string
schemaLocations = getSchemaLocations(ctx, tempRepoPath)
)

log.Debug().Msgf("cache location: %s", vOpts.Cache)
log.Debug().Msgf("target kubernetes version: %s", targetKubernetesVersion)
log.Debug().Msgf("schema locations: %s", strings.Join(schemaLocations, ", "))

v, err := validator.New(getSchemaLocations(), vOpts)
v, err := validator.New(schemaLocations, vOpts)
if err != nil {
log.Error().Err(err).Msg("could not create kubeconform validator")
return "", fmt.Errorf("could not create kubeconform validator: %v", err)
Expand Down
Loading

0 comments on commit e53eea5

Please sign in to comment.