diff --git a/go.mod b/go.mod index 8122701..9d7b746 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.3.7 // indirect + github.com/coreos/go-oidc/v3 v3.11.0 // indirect github.com/cyphar/filepath-securejoin v0.3.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect @@ -47,11 +48,13 @@ require ( github.com/evanphx/json-patch v5.9.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fatih/camelcase v1.0.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect github.com/go-git/go-git/v5 v5.13.2 // indirect + github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect @@ -76,6 +79,8 @@ require ( github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect diff --git a/go.sum b/go.sum index dacffbe..20ee96f 100644 --- a/go.sum +++ b/go.sum @@ -696,6 +696,8 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= +github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -742,7 +744,11 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwC github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= @@ -775,6 +781,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= +github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= @@ -947,6 +955,12 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -1012,8 +1026,12 @@ github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuz github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= diff --git a/pkg/argocd/app_query.go b/pkg/argocd/app_query.go new file mode 100644 index 0000000..7869373 --- /dev/null +++ b/pkg/argocd/app_query.go @@ -0,0 +1,164 @@ +package argocd + +import ( + "context" + "encoding/json" + "fmt" + "os" + + "github.com/argoproj/argo-cd/v2/pkg/apiclient" + "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" + "github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster" + "github.com/argoproj/argo-cd/v2/pkg/apiclient/project" + "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "gopkg.in/yaml.v2" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +type appClient struct { + client apiclient.Client +} + +type ClientOptions struct { + ServerAddr string + Insecure bool + Plaintext bool + Certfile string + GRPCWeb bool + GRPCWebRootPath string + AuthToken string +} + +func NewAPIClient(opts *ClientOptions) (appClient, error) { + envAuthToken := os.Getenv("ARGOCD_TOKEN") + if envAuthToken != "" && opts.AuthToken == "" { + opts.AuthToken = envAuthToken + } + + rOpts := &apiclient.ClientOptions{ + ServerAddr: opts.ServerAddr, + PlainText: opts.Plaintext, + Insecure: opts.Insecure, + CertFile: opts.Certfile, + GRPCWeb: opts.GRPCWeb, + GRPCWebRootPath: opts.GRPCWebRootPath, + AuthToken: opts.AuthToken, + } + client, err := apiclient.NewClient(rOpts) + if err != nil { + return appClient{}, fmt.Errorf("failed to create ArgoCD client: %w", err) + } + return appClient{client}, nil +} + +func (a *appClient) GetApplicationChildManifests(ctx context.Context, app *v1alpha1.Application, proj *v1alpha1.AppProject) ([]*unstructured.Unstructured, error) { + query := &application.ApplicationManifestQuery{ + Name: &app.Name, + AppNamespace: &app.Namespace, + } + + fmt.Printf("Querying manifests for app: %s, namespace: %s\n", app.Name, app.Namespace) + conn, appSetClient, err := a.client.NewApplicationClient() + if err != nil { + return nil, fmt.Errorf("failed to create Application client: %w", err) + } + defer conn.Close() + response, err := appSetClient.GetManifests(ctx, query) + if err != nil { + return nil, fmt.Errorf("failed to get manifests: %w", err) + } + // print pretty the manifests for debugging + for _, manifest := range response.Manifests { + fmt.Printf("Manifest for app %s:\n%s\n", app.Name, manifest) + } + + for i, manifest := range response.Manifests { + var parsed interface{} + err := yaml.Unmarshal([]byte(manifest), &parsed) + if err != nil { + fmt.Printf("Error parsing manifest %d: %v\n", i+1, err) + continue + } + + formatted, err := yaml.Marshal(parsed) + if err != nil { + fmt.Printf("Error formatting manifest %d: %v\n", i+1, err) + continue + } + fmt.Printf("\n--- Manifest %d for app '%s' ---\n%s\n", i+1, app.Name, string(formatted)) + } + return unmarshalManifests(response.Manifests) +} + +func (a *appClient) GetApplication(ctx context.Context, appName, appNamespace string) (*v1alpha1.Application, error) { + conn, appSetClient, err := a.client.NewApplicationClient() + if err != nil { + return nil, fmt.Errorf("failed to create Application client: %w", err) + } + defer conn.Close() + query := &application.ApplicationQuery{ + Name: &appName, + AppNamespace: &appNamespace, + } + + return appSetClient.Get(ctx, query) +} + +func (a *appClient) ListApplications(ctx context.Context, controllerNamespace string) ([]v1alpha1.Application, error) { + var applications []v1alpha1.Application + conn, appSetClient, err := a.client.NewApplicationClient() + if err != nil { + return nil, fmt.Errorf("failed to create Application client: %w", err) + } + defer conn.Close() + appList, err := appSetClient.List(ctx, &application.ApplicationQuery{}) + if err != nil { + return nil, fmt.Errorf("failed to list applications: %w", err) + } + for _, app := range appList.Items { + if app.Status.ControllerNamespace == controllerNamespace { + applications = append(applications, app) + } + } + return applications, nil +} + +func (a *appClient) GetAppProject(ctx context.Context, name string) (*v1alpha1.AppProject, error) { + conn, projectClient, err := a.client.NewProjectClient() + if err != nil { + return nil, fmt.Errorf("failed to create Project client: %w", err) + } + defer conn.Close() + + return projectClient.Get(ctx, &project.ProjectQuery{Name: name}) +} + +func unmarshalManifests(manifests []string) ([]*unstructured.Unstructured, error) { + targetObjs := make([]*unstructured.Unstructured, 0) + for _, manifest := range manifests { + if manifest == "" || manifest == "null" { + return nil, nil + } + var obj unstructured.Unstructured + err := json.Unmarshal([]byte(manifest), &obj) + if err != nil { + return nil, err + } + targetObjs = append(targetObjs, &obj) + } + return targetObjs, nil +} + +func (a *appClient) GetApplicationDestinationServer(ctx context.Context, destinationServer, destinationName string) (*v1alpha1.Cluster, error) { + conn, clusterClient, err := a.client.NewClusterClient() + if err != nil { + return nil, fmt.Errorf("failed to create Application client: %w", err) + } + defer conn.Close() + + return clusterClient.Get(ctx, &cluster.ClusterQuery{ + Name: destinationName, + Server: destinationServer, + }) + +} diff --git a/pkg/argocd/repo.go b/pkg/argocd/repo.go index 0c6edca..c017d1d 100644 --- a/pkg/argocd/repo.go +++ b/pkg/argocd/repo.go @@ -186,18 +186,6 @@ func getApplicationChildManifests(ctx context.Context, application *appsv1alpha1 return targetObjs, cluster.RESTConfig(), nil } -func unmarshalManifests(manifests []string) ([]*unstructured.Unstructured, error) { - targetObjs := make([]*unstructured.Unstructured, 0) - for _, manifest := range manifests { - obj, err := appsv1alpha1.UnmarshalToUnstructured(manifest) - if err != nil { - return nil, err - } - targetObjs = append(targetObjs, obj) - } - return targetObjs, nil -} - // getClusterAPIDetails retrieves the server version and API resources from the Kubernetes cluster func getClusterAPIDetails(config *rest.Config, kubectl kube.Kubectl) (*clusterAPIDetails, error) { // Retrieve the server version diff --git a/pkg/env/env.go b/pkg/env/env.go index 1afa401..7914679 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -2,6 +2,7 @@ package env import ( "os" + "strings" ) // Package env provides some utility functions to interact with the environment @@ -16,3 +17,14 @@ func GetStringVal(envVar string, defaultValue string) string { return defaultValue } } + +func GetBoolVal(envVar string, defaultValue bool) bool { + if val := os.Getenv(envVar); val != "" { + if strings.ToLower(val) == "true" { + return true + } else if strings.ToLower(val) == "false" { + return false + } + } + return defaultValue +}