Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Restrict Kubechecks to a Single Namespace for App Watcher #235

Merged
merged 14 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/build-image/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ runs:
- name: Build and push the Docker image
shell: bash
run: >-
./earthly
./earthly.sh
--push
+docker-multiarch
${{ inputs.tag_latest != 'false' && format('--LATEST_IMAGE_NAME=ghcr.io/{0}:latest', github.repository) || '' }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/on_pull-request_docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
with: { version: "${{ env.EARTHLY_TOOL_VERSION }}" }

- name: rebuild the docs
run: ./earthly +rebuild-docs
run: ./earthly.sh +rebuild-docs

- name: verify that the checked in file has not changed
run: ./hacks/exit-on-changed-files.sh "Please run './earthly +rebuild-docs' and commit the results to this PR"
2 changes: 1 addition & 1 deletion .github/workflows/on_pull-request_helm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ jobs:
- uses: earthly/actions-setup@v1
with: { version: "v${{ env.EARTHLY_TOOL_VERSION }}" }

- run: ./earthly +ci-helm
- run: ./earthly.sh +ci-helm
2 changes: 1 addition & 1 deletion .github/workflows/on_pull_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ on:
- synchronize

env:
FS_TAG: 0.0.0-pr${{ github.event.pull_request.number }}
FS_TAG: 0.0.0-pr${{ github.event.pull_request.number }}-${{ github.sha }}
Greyeye marked this conversation as resolved.
Show resolved Hide resolved

jobs:
build:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/on_pull_request_go.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ jobs:
- uses: earthly/actions-setup@v1
with: { version: "v${{ env.EARTHLY }}" }

- run: ./earthly +ci-golang
- run: ./earthly.sh +ci-golang
2 changes: 1 addition & 1 deletion .github/workflows/on_push_to_main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:

- name: Build and push the helm charts
run: |
./earthly \
./earthly.sh \
--push \
+release-helm \
--repo_owner ${{ github.repository_owner }} \
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ localdev/terraform/gitlab/project.url
*.DS_Store
/kubechecks
localdev/terraform/github/project.url
.secret
.arg
1 change: 0 additions & 1 deletion .env.example → .secret.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
GITLAB_TOKEN=xyz
KUBECHECKS_LOG_LEVEL=debug
OPENAI_API_TOKEN=xyz
GITHUB_TOKEN=xyz
KUBECHECKS_WEBHOOK_SECRET=xyz
51 changes: 49 additions & 2 deletions Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ if cfg.get('enable_repo', True):
test_go(
'go-test', '.',
recursive=True,
timeout='30s',
timeout='60s',
extra_args=['-v'],
labels=["kubechecks"],
deps=[
Expand All @@ -138,12 +138,59 @@ test_go(
],
)


# get the git commit ref
def get_git_head():
result = local('git rev-parse --short HEAD')
return result

# read .tool-versions file and return a dictionary of tools and their versions
def parse_tool_versions(fn):
if not os.path.exists(fn):
warn("tool versions file not found: '%s'" % fn)
return dict()

f = read_file(fn)

lines = str(f).splitlines()

tools = dict()

for linenumber in range(len(lines)):
line = lines[linenumber]
parts = line.split("#", 1)
if len(parts) == 2:
line = parts[0]
line = line.strip()
if line == "":
continue
parts = line.split(' ', 1)
tools[parts[0].strip()] = parts[1].strip()
tools['githead'] = str(get_git_head())
Greyeye marked this conversation as resolved.
Show resolved Hide resolved
return tools

tool_versions = parse_tool_versions(".tool-versions")

earthly_build(
context='.',
target="+docker-debug",
ref='kubechecks',
image_arg='IMAGE_NAME',
ignore='./dist',
extra_args=[
'--CHART_RELEASER_VERSION='+tool_versions.get('helm-cr'),
'--GOLANG_VERSION='+tool_versions.get('golang'),
'--GOLANGCI_LINT_VERSION='+tool_versions.get('golangci-lint'),
'--HELM_VERSION='+tool_versions.get('helm'),
'--KUBECONFORM_VERSION='+tool_versions.get('kubeconform'),
'--KUSTOMIZE_VERSION='+tool_versions.get('kustomize'),
'--STATICCHECK_VERSION='+tool_versions.get('staticcheck'),
'--GIT_COMMIT='+tool_versions.get('githead'),
'--GITLAB_TOKEN='+os.getenv('GITLAB_TOKEN') if 'gitlab' in cfg.get('vcs-type', 'gitlab') else os.getenv('GITHUB_TOKEN'),
'--KUBECHECKS_LOG_LEVEL='+os.getenv('KUBECHECKS_LOG_LEVEL'),
'--OPENAI_API_TOKEN='+os.getenv('OPENAI_API_TOKEN'),
'--KUBECHECKS_WEBHOOK_SECRET='+os.getenv('KUBECHECKS_WEBHOOK_SECRET'),
],
)

cmd_button('loc:go mod tidy',
Expand Down Expand Up @@ -216,4 +263,4 @@ load("localdev/test_appsets/Tiltfile", "install_test_appsets")
install_test_appsets(cfg)


force_argocd_cleanup_on_tilt_down()
force_argocd_cleanup_on_tilt_down()
4 changes: 3 additions & 1 deletion cmd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"fmt"

"github.com/pkg/errors"

"github.com/rs/zerolog/log"
"github.com/zapier/kubechecks/pkg/app_watcher"
"github.com/zapier/kubechecks/pkg/appdir"
"github.com/zapier/kubechecks/pkg/argo_client"
Expand Down Expand Up @@ -60,6 +60,8 @@ func newContainer(ctx context.Context, cfg config.ServerConfig, watchApps bool)

go ctr.ApplicationWatcher.Run(ctx, 1)
}
} else {
log.Debug().Msgf("not monitoring applications, MonitorAllApplications: %+v", cfg.MonitorAllApplications)
Greyeye marked this conversation as resolved.
Show resolved Hide resolved
}

return ctr, nil
Expand Down
7 changes: 6 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,13 @@ func init() {
withDefault("gitlab"))
stringFlag(flags, "vcs-token", "VCS API token.")
stringFlag(flags, "argocd-api-token", "ArgoCD API token.")
stringFlag(flags, "argocd-api-server-addr", "ArgoCD API Server Address.", newStringOpts().withDefault("argocd-server"))
stringFlag(flags, "argocd-api-server-addr", "ArgoCD API Server Address.",
newStringOpts().
withDefault("argocd-server"))
boolFlag(flags, "argocd-api-insecure", "Enable to use insecure connections to the ArgoCD API server.")
stringFlag(flags, "argocd-api-namespace", "ArgoCD namespace where the application watcher will read Custom Resource Definitions (CRD).",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, technically, the CRD is the schema definition, I think we want to say something like "Application and ApplicationSet resources"

newStringOpts().
withDefault("argocd"))
stringFlag(flags, "kubernetes-config", "Path to your kubernetes config file, used to monitor applications.")

stringFlag(flags, "otel-collector-port", "The OpenTelemetry collector port.")
Expand Down
6 changes: 3 additions & 3 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ It creates:

To get started do the following:

* Copy the `.env.example` and set required values.
* Copy the `.secret.example` and set required values.

```console
cp .env.example .env
cp .secret.example .secret
```

* From the root directory of this repo:
Expand Down Expand Up @@ -110,7 +110,7 @@ If you're using minikube with Tilt we recommend following this [guide](https://g

### Code Changes

We use Earthly to simplify our CI/CD process with `kubechecks`. There's a thin wrapper around earthly that passes some common arguments in the root of the repo called `./earthly` that should be used instead of calling earthly directly. This also simplifies testing changes locally before pushing them up to ensure your PR will pass all required checks. The best command to run is `./earthly +test` this will pull all the required dependencies (including any new ones that you have added). It will then run [go vet](https://pkg.go.dev/cmd/vet), and if those pass it will run `go test` with race detection enabled. You can also always run these commands directly `go test -race ./...` will run all tests in the repo with race detection enabled. Please ensure that `./earthly +test` is passing before opening a PR.
We use Earthly to simplify our CI/CD process with `kubechecks`. There's a thin wrapper around earthly that passes some common arguments in the root of the repo called `./earthly.sh` that should be used instead of calling earthly directly. This also simplifies testing changes locally before pushing them up to ensure your PR will pass all required checks. The best command to run is `./earthly.sh +test` this will pull all the required dependencies (including any new ones that you have added). It will then run [go vet](https://pkg.go.dev/cmd/vet), and if those pass it will run `go test` with race detection enabled. You can also always run these commands directly `go test -race ./...` will run all tests in the repo with race detection enabled. Please ensure that `./earthly.sh +test` is passing before opening a PR.

### Documentation Changes

Expand Down
1 change: 1 addition & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ The full list of supported environment variables is described below:
|Env Var|Description|Default Value|
|-----------|-------------|------|
|`KUBECHECKS_ARGOCD_API_INSECURE`|Enable to use insecure connections to the ArgoCD API server.|`false`|
|`KUBECHECKS_ARGOCD_API_NAMESPACE`|ArgoCD namespace where the application watcher will read Custom Resource Definitions (CRD).|`argocd`|
|`KUBECHECKS_ARGOCD_API_SERVER_ADDR`|ArgoCD API Server Address.|`argocd-server`|
|`KUBECHECKS_ARGOCD_API_TOKEN`|ArgoCD API token.||
|`KUBECHECKS_ENABLE_CONFTEST`|Set to true to enable conftest policy checking of manifests.|`false`|
Expand Down
5 changes: 3 additions & 2 deletions earthly → earthly.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ to_echo() {

read_tool_versions_write_to_env() {
local -r tool_versions_file="$1"

cat $tool_versions_file
# loop over each line of the .tool-versions file
while read -r line; do
# split the line into a bash array using the default space delimeter
Expand Down Expand Up @@ -39,4 +39,5 @@ earthly $* \
--KUBECONFORM_VERSION=${kubeconform_tool_version} \
--KUSTOMIZE_VERSION=${kustomize_tool_version} \
--STATICCHECK_VERSION=${staticcheck_tool_version} \
--GIT_COMMIT=$(git rev-parse --short HEAD)
--GIT_COMMIT=$(git rev-parse --short HEAD) \
--KUBECHECKS_LOG_LEVEL=debug
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ require (
github.com/prometheus/client_golang v1.19.0
github.com/rikatz/kubepug v1.4.0
github.com/rs/zerolog v1.32.0
github.com/sashabaranov/go-openai v1.20.4
github.com/sashabaranov/go-openai v1.26.2
github.com/shurcooL/githubv4 v0.0.0-20231126234147-1cffa1f02456
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,8 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
github.com/sashabaranov/go-openai v1.20.4 h1:095xQ/fAtRa0+Rj21sezVJABgKfGPNbyx/sAN/hJUmg=
github.com/sashabaranov/go-openai v1.20.4/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/sashabaranov/go-openai v1.26.2 h1:cVlQa3gn3eYqNXRW03pPlpy6zLG52EU4g0FrWXc0EFI=
github.com/sashabaranov/go-openai v1.26.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
Expand Down
4 changes: 2 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ unit_test_race:
go test -race ./...

rebuild_docs:
./earthly +rebuild-docs
./earthly.sh +rebuild-docs

ci-golang:
./earthly +ci-golang
./earthly.sh +ci-golang
4 changes: 2 additions & 2 deletions localdev/kubechecks/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ configMap:
KUBECHECKS_ENABLE_WEBHOOK_CONTROLLER: "false"
KUBECHECKS_ARGOCD_API_INSECURE: "true"
KUBECHECKS_ARGOCD_API_PATH_PREFIX : '/argocd'
KUBECHECKS_ARGOCD_NAMESPACE: 'kubechecks'
KUBECHECKS_ARGOCD_API_NAMESPACE: 'kubechecks'
KUBECHECKS_WEBHOOK_URL_PREFIX: 'kubechecks'
KUBECHECKS_NAMESPACE: 'kubechecks'
KUBECHECKS_FALLBACK_K8S_VERSION: "1.25.0"
Expand All @@ -20,7 +20,7 @@ configMap:
# KUBECHECKS_LABEL_FILTER: "test" # On your PR/MR, prefix this with "kubechecks:"
# KUBECHECKS_SCHEMAS_LOCATION: https://github.com/zapier/kubecheck-schemas.git
KUBECHECKS_TIDY_OUTDATED_COMMENTS_MODE: "delete"
KUBECHECKS_ENABLE_CONFTEST: "true"
KUBECHECKS_ENABLE_CONFTEST: "false"


deployment:
Expand Down
2 changes: 1 addition & 1 deletion pkg/aisummary/diff_summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func (c *OpenAiClient) SummarizeDiff(ctx context.Context, appName, diff string)
ctx, span := tracer.Start(ctx, "SummarizeDiff")
defer span.End()

model := openai.GPT4Turbo0125
model := openai.GPT4o
djeebus marked this conversation as resolved.
Show resolved Hide resolved
if len(diff) < 3500 {
model = openai.GPT3Dot5Turbo
}
Expand Down
19 changes: 13 additions & 6 deletions pkg/app_watcher/app_watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func NewApplicationWatcher(vcsToArgoMap appdir.VcsToArgoMap, cfg config.ServerCo
vcsToArgoMap: vcsToArgoMap,
}

appInformer, appLister := ctrl.newApplicationInformerAndLister(time.Second * 30)
appInformer, appLister := ctrl.newApplicationInformerAndLister(time.Second*30, cfg)

ctrl.appInformer = appInformer
ctrl.appLister = appLister
Expand Down Expand Up @@ -118,12 +118,19 @@ func (ctrl *ApplicationWatcher) onApplicationDeleted(obj interface{}) {
}

/*
This Go function, named newApplicationInformerAndLister, is part of the ApplicationWatcher struct. It sets up a Kubernetes SharedIndexInformer and a Lister for Argo CD Applications.
A SharedIndexInformer is used to watch changes to a specific type of Kubernetes resource in an efficient manner. It significantly reduces the load on the Kubernetes API server by sharing and caching watches between all controllers that need to observe the object.
Listers use the data from the informer's cache to provide a read-optimized view of the cache which reduces the load on the API Server and hides some complexity.
newApplicationInformerAndLister, is part of the ApplicationWatcher struct. It sets up a Kubernetes SharedIndexInformer
and a Lister for Argo CD Applications.

A SharedIndexInformer is used to watch changes to a specific type of Kubernetes resource in an efficient manner.
It significantly reduces the load on the Kubernetes API server by sharing and caching watches between all controllers
that need to observe the object.

newApplicationInformerAndLister use the data from the informer's cache to provide a read-optimized view of the cache which reduces
the load on the API Server and hides some complexity.
*/
func (ctrl *ApplicationWatcher) newApplicationInformerAndLister(refreshTimeout time.Duration) (cache.SharedIndexInformer, applisters.ApplicationLister) {
informer := informers.NewApplicationInformer(ctrl.applicationClientset, "", refreshTimeout,
func (ctrl *ApplicationWatcher) newApplicationInformerAndLister(refreshTimeout time.Duration, cfg config.ServerConfig) (cache.SharedIndexInformer, applisters.ApplicationLister) {
log.Debug().Msgf("Creating Application informer with namespace: %s", cfg.ArgoCDNamespace)
informer := informers.NewApplicationInformer(ctrl.applicationClientset, cfg.ArgoCDNamespace, refreshTimeout,
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
)

Expand Down
16 changes: 11 additions & 5 deletions pkg/app_watcher/app_watcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@ import (
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
appclientsetfake "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake"
"github.com/stretchr/testify/assert"
"github.com/zapier/kubechecks/pkg/config"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

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

func initTestObjects() *ApplicationWatcher {
func initTestObjects(t *testing.T) *ApplicationWatcher {
cfg, err := config.New()
if err != nil {
// Handle the error appropriately, e.g., log it or fail the test
t.Fatalf("Failed to initialize config: %v", err)
}
Greyeye marked this conversation as resolved.
Show resolved Hide resolved
// set up the fake Application client set and informer.
testApp1 := &v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{Name: "test-app-1", Namespace: "default"},
Expand All @@ -34,15 +40,15 @@ func initTestObjects() *ApplicationWatcher {
vcsToArgoMap: appdir.NewVcsToArgoMap("vcs-username"),
}

appInformer, appLister := ctrl.newApplicationInformerAndLister(time.Second * 1)
appInformer, appLister := ctrl.newApplicationInformerAndLister(time.Second*1, cfg)
ctrl.appInformer = appInformer
ctrl.appLister = appLister

return ctrl
}

func TestApplicationAdded(t *testing.T) {
appWatcher := initTestObjects()
appWatcher := initTestObjects(t)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
Expand All @@ -68,7 +74,7 @@ func TestApplicationAdded(t *testing.T) {
}

func TestApplicationUpdated(t *testing.T) {
ctrl := initTestObjects()
ctrl := initTestObjects(t)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
Expand Down Expand Up @@ -101,7 +107,7 @@ func TestApplicationUpdated(t *testing.T) {
}

func TestApplicationDeleted(t *testing.T) {
ctrl := initTestObjects()
ctrl := initTestObjects(t)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type ServerConfig struct {
ArgoCDToken string `mapstructure:"argocd-api-token"`
ArgoCDPathPrefix string `mapstructure:"argocd-api-path-prefix"`
ArgoCDInsecure bool `mapstructure:"argocd-api-insecure"`
ArgoCDNamespace string `mapstructure:"argocd-api-namespace"`
KubernetesConfig string `mapstructure:"kubernetes-config"`

// otel
Expand Down Expand Up @@ -101,6 +102,7 @@ func NewWithViper(v *viper.Viper) (ServerConfig, error) {
log.Info().Msgf("Webhook URL Base: %s", cfg.WebhookUrlBase)
log.Info().Msgf("Webhook URL Prefix: %s", cfg.UrlPrefix)
log.Info().Msgf("VCS Type: %s", cfg.VcsType)
log.Info().Msgf("ArgoCD Namespace: %s", cfg.ArgoCDNamespace)

return cfg, nil
}
Loading
Loading