Skip to content

Commit

Permalink
handle multi source applications (#298)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt Morrison <[email protected]>
  • Loading branch information
djeebus and sl1pm4t authored Dec 16, 2024
1 parent 40efd62 commit 839119d
Show file tree
Hide file tree
Showing 49 changed files with 2,304 additions and 435 deletions.
3 changes: 3 additions & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
with-expecter: true
dir: "mocks/{{.PackageName}}/mocks"
packages:
github.com/zapier/kubechecks/pkg/vcs:
config:
all: true
github.com/zapier/kubechecks/pkg/vcs/github_client:
# place your package-specific config here
config:
Expand Down
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ helm-cr 1.6.1
helm-ct 3.11.0
kubeconform 0.6.7
kustomize 5.5.0
mockery 2.46.3
staticcheck 2024.1.1
tilt 0.33.2
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@

## Pull/Merge Request driven checks

When using ArgoCD, it can be difficult to tell just how your Pull/Merge Request (PR/MR) will impact your live deployment. `kubechecks` was designed to address this problem; every time a new PR/MR is created, `kubechecks` will automatically determine what's changed and how it will impact your `main`/default branchs state, informing you of the details directly on the PR/MR. As a bonus, it also lints and checks your Kubernetes manifests to let you know ahead of time if something is outdated, invalid, or otherwise not good practice.
When using ArgoCD, it can be difficult to tell just how your Pull/Merge Request (PR/MR) will impact your live deployment. `kubechecks` was designed to address this problem; every time a new PR/MR is created, `kubechecks` will automatically determine what's changed and how it will impact your `main`/default branch's state, informing you of the details directly on the PR/MR. As a bonus, it also lints and checks your Kubernetes manifests to let you know ahead of time if something is outdated, invalid, or otherwise not good practice.

![Demo](./docs/gif/kubechecks.gif)

### How it works

This tool provides a server function that processes webhooks from Gitlab/Github, clones the repository at the `HEAD` SHA of the PR/MR, and runs various check suites, commenting the output of each check in a single comment on your PR/MR. `kubechecks` talks directly to ArgoCD to get the live state of your deployments to ensure that you have the most accurate information about how your changes will affect your production code.
This tool provides a server function that processes webhooks from Gitlab/Github, clones the repository at the `HEAD` SHA of the PR/MR, and runs various check suites, commenting the output of each check in a single comment on your PR/MR. `kubechecks` talks directly to ArgoCD to get the live state of your deployments and talks directly to ArgoCD's repo server to generate the new resources to ensure that you have the most accurate information about how your changes will affect your production code.

### Architecture

Expand Down
2 changes: 1 addition & 1 deletion charts/kubechecks/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apiVersion: v2
name: kubechecks
description: A Helm chart for kubechecks
version: 0.4.6
version: 0.5.0
type: application
maintainers:
- name: zapier
15 changes: 15 additions & 0 deletions charts/kubechecks/templates/role.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: kubechecks
namespace: {{ .Values.argocd.namespace }}
rules:
- apiGroups:
- ""
resources:
- configmaps
- secrets
verbs:
- get
- list
- watch
13 changes: 13 additions & 0 deletions charts/kubechecks/templates/rolebinding.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: kubechecks
namespace: {{ .Values.argocd.namespace }}
roleRef:
kind: Role
name: kubechecks
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: {{ include "kubechecks.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
3 changes: 3 additions & 0 deletions charts/kubechecks/values.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Labels to apply to all resources created by this Helm chart
argocd:
namespace: argocd

commonLabels: {}

configMap:
Expand Down
123 changes: 0 additions & 123 deletions cmd/container.go

This file was deleted.

28 changes: 27 additions & 1 deletion cmd/controller_cmd.go → cmd/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/zapier/kubechecks/pkg/app_watcher"

"github.com/zapier/kubechecks/pkg"
"github.com/zapier/kubechecks/pkg/checks"
Expand Down Expand Up @@ -41,19 +42,39 @@ var ControllerCmd = &cobra.Command{
log.Fatal().Err(err).Msg("failed to parse configuration")
}

ctr, err := newContainer(ctx, cfg, true)
ctr, err := container.New(ctx, cfg)
if err != nil {
log.Fatal().Err(err).Msg("failed to create container")
}

// watch app modifications, if necessary
if cfg.MonitorAllApplications {
appWatcher, err := app_watcher.NewApplicationWatcher(ctr)
if err != nil {
log.Fatal().Err(err).Msg("failed to create watch applications")
}
go appWatcher.Run(ctx, 1)

appSetWatcher, err := app_watcher.NewApplicationSetWatcher(ctr)
if err != nil {
log.Fatal().Err(err).Msg("failed to create watch application sets")
}
go appSetWatcher.Run(ctx)
} else {
log.Info().Msgf("not monitoring applications, MonitorAllApplications: %+v", cfg.MonitorAllApplications)
}

log.Info().Msg("initializing git settings")
if err = initializeGit(ctr); err != nil {
log.Fatal().Err(err).Msg("failed to initialize git settings")
}

log.Info().Strs("locations", cfg.PoliciesLocation).Msg("processing policies locations")
if err = processLocations(ctx, ctr, cfg.PoliciesLocation); err != nil {
log.Fatal().Err(err).Msg("failed to process policy locations")
}

log.Info().Strs("locations", cfg.SchemasLocations).Msg("processing schemas locations")
if err = processLocations(ctx, ctr, cfg.SchemasLocations); err != nil {
log.Fatal().Err(err).Msg("failed to process schema locations")
}
Expand Down Expand Up @@ -137,6 +158,11 @@ func init() {
newStringOpts().withDefault("1.23.0"))
boolFlag(flags, "show-debug-info", "Set to true to print debug info to the footer of MR comments (KUBECHECKS_SHOW_DEBUG_INFO).")

stringFlag(flags, "argocd-repository-endpoint", `Location of the argocd repository service endpoint.`,
newStringOpts().withDefault("argocd-repo-server.argocd:8081"))
boolFlag(flags, "argocd-repository-insecure", `True if you need to skip validating the grpc tls certificate.`,
newBoolOpts().withDefault(true))
boolFlag(flags, "argocd-send-full-repository", `Set to true if you want to try to send the full repository to ArgoCD when generating manifests.`)
stringFlag(flags, "label-filter", `(Optional) If set, The label that must be set on an MR (as "kubechecks:<value>") for kubechecks to process the merge request webhook (KUBECHECKS_LABEL_FILTER).`)
stringFlag(flags, "openai-api-token", "OpenAI API Token.")
stringFlag(flags, "webhook-url-base", "The endpoint to listen on for incoming PR/MR event webhooks. For example, 'https://checker.mycompany.com'.")
Expand Down
2 changes: 2 additions & 0 deletions cmd/locations.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ func processLocations(ctx context.Context, ctr container.Container, locations []
}
}

log.Debug().Strs("locations", locations).Msg("locations after processing")

return nil
}

Expand Down
37 changes: 35 additions & 2 deletions cmd/process.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package cmd

import (
"os"
"path/filepath"

"github.com/argoproj/argo-cd/v2/common"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/zapier/kubechecks/pkg/container"

"github.com/zapier/kubechecks/pkg/config"
"github.com/zapier/kubechecks/pkg/server"
Expand All @@ -15,14 +20,42 @@ var processCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
ctx := cmd.Context()

tempPath, err := os.MkdirTemp("", "")
if err != nil {
log.Fatal().Err(err).Msg("fail to create ssh data dir")
}
defer func() {
os.RemoveAll(tempPath)
}()

// symlink local ssh known hosts to argocd ssh known hosts
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatal().Err(err).Msg("failed to get user home dir")
}
source := filepath.Join(homeDir, ".ssh", "known_hosts")
target := filepath.Join(tempPath, common.DefaultSSHKnownHostsName)

if err := os.Symlink(source, target); err != nil {
log.Fatal().Err(err).Msg("fail to symlink ssh_known_hosts file")
}

if err := os.Setenv("ARGOCD_SSH_DATA_PATH", tempPath); err != nil {
log.Fatal().Err(err).Msg("fail to set ARGOCD_SSH_DATA_PATH")
}

cfg, err := config.New()
if err != nil {
log.Fatal().Err(err).Msg("failed to generate config")
}

ctr, err := newContainer(ctx, cfg, false)
if len(args) != 1 {
log.Fatal().Msg("usage: kubechecks process PR_REF")
}

ctr, err := container.New(ctx, cfg)
if err != nil {
log.Fatal().Err(err).Msg("failed to create container")
log.Fatal().Err(err).Msg("failed to create clients")
}

log.Info().Msg("initializing git settings")
Expand Down
9 changes: 5 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func init() {
newStringOpts().
withChoices("hide", "delete").
withDefault("hide"))
stringSliceFlag(flags, "schemas-location", "Sets schema locations to be used for every check request. Can be common paths inside the repos being checked or git urls in either git or http(s) format.")
stringSliceFlag(flags, "schemas-location", "Sets schema locations to be used for every check request. Can be a common path on the host or git urls in either git or http(s) format.")
boolFlag(flags, "enable-conftest", "Set to true to enable conftest policy checking of manifests.")
stringSliceFlag(flags, "policies-location", "Sets rego policy locations to be used for every check request. Can be common path inside the repos being checked or git urls in either git or http(s) format.",
newStringSliceOpts().
Expand Down Expand Up @@ -124,9 +124,6 @@ func init() {
}

func setupLogOutput() {
output := zerolog.ConsoleWriter{Out: os.Stdout}
log.Logger = log.Output(output)

// Default level is info, unless debug flag is present
levelFlag := viper.GetString("log-level")
level, err := zerolog.ParseLevel(levelFlag)
Expand All @@ -135,6 +132,10 @@ func setupLogOutput() {
}

zerolog.SetGlobalLevel(level)

output := zerolog.ConsoleWriter{Out: os.Stdout}
log.Logger = log.Output(output)

log.Debug().Msg("Debug level logging enabled.")
log.Trace().Msg("Trace level logging enabled.")
log.Info().Msg("Initialized logger.")
Expand Down
5 changes: 4 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ The full list of supported environment variables is described below:
|`KUBECHECKS_ARGOCD_API_PLAINTEXT`|Enable to use plaintext connections without TLS.|`false`|
|`KUBECHECKS_ARGOCD_API_SERVER_ADDR`|ArgoCD API Server Address.|`argocd-server`|
|`KUBECHECKS_ARGOCD_API_TOKEN`|ArgoCD API token.||
|`KUBECHECKS_ARGOCD_REPOSITORY_ENDPOINT`|Location of the argocd repository service endpoint.|`argocd-repo-server.argocd:8081`|
|`KUBECHECKS_ARGOCD_REPOSITORY_INSECURE`|True if you need to skip validating the grpc tls certificate.|`true`|
|`KUBECHECKS_ARGOCD_SEND_FULL_REPOSITORY`|Set to true if you want to try to send the full repository to ArgoCD when generating manifests.|`false`|
|`KUBECHECKS_ENABLE_CONFTEST`|Set to true to enable conftest policy checking of manifests.|`false`|
|`KUBECHECKS_ENABLE_HOOKS_RENDERER`|Render hooks.|`true`|
|`KUBECHECKS_ENABLE_KUBECONFORM`|Enable kubeconform checks.|`true`|
Expand All @@ -66,7 +69,7 @@ The full list of supported environment variables is described below:
|`KUBECHECKS_POLICIES_LOCATION`|Sets rego policy locations to be used for every check request. Can be common path inside the repos being checked or git urls in either git or http(s) format.|`[./policies]`|
|`KUBECHECKS_REPLAN_COMMENT_MSG`|comment message which re-triggers kubechecks on PR.|`kubechecks again`|
|`KUBECHECKS_REPO_REFRESH_INTERVAL`|Interval between static repo refreshes (for schemas and policies).|`5m`|
|`KUBECHECKS_SCHEMAS_LOCATION`|Sets schema locations to be used for every check request. Can be common paths inside the repos being checked or git urls in either git or http(s) format.|`[]`|
|`KUBECHECKS_SCHEMAS_LOCATION`|Sets schema locations to be used for every check request. Can be a common path on the host or git urls in either git or http(s) format.|`[]`|
|`KUBECHECKS_SHOW_DEBUG_INFO`|Set to true to print debug info to the footer of MR comments.|`false`|
|`KUBECHECKS_TIDY_OUTDATED_COMMENTS_MODE`|Sets the mode to use when tidying outdated comments. One of hide, delete.|`hide`|
|`KUBECHECKS_VCS_BASE_URL`|VCS base url, useful if self hosting gitlab, enterprise github, etc.||
Expand Down
6 changes: 5 additions & 1 deletion mocks/affected_apps/mocks/mock_Matcher.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 839119d

Please sign in to comment.