From 5b16fd79c8fe35654648dfa810e5814f6eafdd95 Mon Sep 17 00:00:00 2001 From: Carolina Delwing Rosa Date: Tue, 29 Oct 2024 10:08:03 -0400 Subject: [PATCH] docs: cluster scanner first commit --- scripts/cluster-scanner/README.md | 57 +++++++++++++++ scripts/cluster-scanner/go.mod | 38 ++++++++++ scripts/cluster-scanner/go.sum | 70 +++++++++++++++++++ .../cluster-scanner/internal/format_age.go | 32 +++++++++ .../internal/search_clusters.go | 16 +++++ .../internal/search_old_clusters.go | 30 ++++++++ scripts/cluster-scanner/main.go | 58 +++++++++++++++ 7 files changed, 301 insertions(+) create mode 100644 scripts/cluster-scanner/README.md create mode 100644 scripts/cluster-scanner/go.mod create mode 100644 scripts/cluster-scanner/go.sum create mode 100644 scripts/cluster-scanner/internal/format_age.go create mode 100644 scripts/cluster-scanner/internal/search_clusters.go create mode 100644 scripts/cluster-scanner/internal/search_old_clusters.go create mode 100644 scripts/cluster-scanner/main.go diff --git a/scripts/cluster-scanner/README.md b/scripts/cluster-scanner/README.md new file mode 100644 index 0000000..dbebdc5 --- /dev/null +++ b/scripts/cluster-scanner/README.md @@ -0,0 +1,57 @@ +# Cluster Scanner + +The **Cluster Scanner** tool uses the Palette Go SDK to scan your Palette environment and identify clusters that have been active for more than 24 hours. + +## Prerequisites + +- Go version 1.22.5 or later +- Git +- The `palette-samples` repository available locally +- A Palette acount +- A Palette API key + +## Usage + +1. Open a terminal window and export your Palette URL. Replace `` with your Palette URL, for example, `console.spectrocloud.com`. + + ```shell + export PALETTE_HOST= + ``` + +2. Export your Palette API key. Replace `` with your Palette API key. + + ```shell + export PALETTE_API_KEY= + ``` + +3. To scan a specific project, export the project's UID. Replace `` with the Palette project UID. If no project is provided, the tool assumes a tenant scope and scans clusters across all projects. + + ```shell + export PALETTE_PROJECT_UID= + ``` + +4. Navigate to the `cluster-scanner` folder. + + ```shell + cd cluster-scanner + ``` + +5. Issue the command below to download the required Palette SDK modules. + + ```shell + go get + ``` + +6. Execute the `cluster-scanner` application. + + ```shell + go run . + ``` + + The application will print the clusters that have been active in your Palette environment for more than 24 hours. + + ```text hideClipboard + time=2024-10-28T21:21:47.516-04:00 level=INFO msg="Setting scope to tenant." + time=2024-10-28T21:21:47.516-04:00 level=INFO msg="Searching for clusters..." + time=2024-10-28T21:21:48.297-04:00 level=INFO msg="The aws cluster named aws-test has been running for 2 weeks 6 days 2 hours. Are you sure you need this cluster?" + ``` diff --git a/scripts/cluster-scanner/go.mod b/scripts/cluster-scanner/go.mod new file mode 100644 index 0000000..2f12676 --- /dev/null +++ b/scripts/cluster-scanner/go.mod @@ -0,0 +1,38 @@ +module github.com/spectrocloud/palette-samples/cluster-scanner + +go 1.22.5 + +toolchain go1.22.8 + +require ( + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/go-errors/errors v1.5.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/analysis v0.23.0 // indirect + github.com/go-openapi/errors v0.22.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/loads v0.22.0 // indirect + github.com/go-openapi/runtime v0.28.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/strfmt v0.23.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/validate v0.24.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spectrocloud/palette-sdk-go v0.0.0-20240930211255-e224a905d31a // indirect + go.mongodb.org/mongo-driver v1.16.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.22.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/scripts/cluster-scanner/go.sum b/scripts/cluster-scanner/go.sum new file mode 100644 index 0000000..9911030 --- /dev/null +++ b/scripts/cluster-scanner/go.sum @@ -0,0 +1,70 @@ +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= +github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= +github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= +github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= +github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= +github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= +github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= +github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= +github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spectrocloud/palette-sdk-go v0.0.0-20240930211255-e224a905d31a h1:u1itx2mJzS9VCjVRr3cXqSnJpBxELSay5pdgFHIhz8w= +github.com/spectrocloud/palette-sdk-go v0.0.0-20240930211255-e224a905d31a/go.mod h1:dSlNvDS0qwUWTbrYI6P8x981mcbbRHFrBg67v5zl81U= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4= +go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/scripts/cluster-scanner/internal/format_age.go b/scripts/cluster-scanner/internal/format_age.go new file mode 100644 index 0000000..6866c1c --- /dev/null +++ b/scripts/cluster-scanner/internal/format_age.go @@ -0,0 +1,32 @@ +package internal + +import ( + "fmt" + "time" +) + +// Format the cluster age using weeks, days, and hours +func FormatAge (clusterAge time.Duration) (int, int, int) { + const hoursPerWeek = 168 + weeks := int(clusterAge.Hours() / hoursPerWeek) + remainingHours := int(clusterAge.Hours() - float64((weeks * hoursPerWeek))) + days := int(remainingHours / 24) + hours := int(remainingHours % 24) + return weeks, days, hours +} + +// Return the cluster age as a formatted string +func PrintFormattedAge (clusterAge time.Duration) (string) { + weeks, days, hours := FormatAge(clusterAge) + formattedString := "" + if weeks > 0 { + formattedString += fmt.Sprint(weeks) + " weeks " + } + if days > 0 { + formattedString += fmt.Sprint(days) + " days " + } + if hours > 0 { + formattedString += fmt.Sprint(hours) + " hours" + } + return formattedString +} \ No newline at end of file diff --git a/scripts/cluster-scanner/internal/search_clusters.go b/scripts/cluster-scanner/internal/search_clusters.go new file mode 100644 index 0000000..b917c69 --- /dev/null +++ b/scripts/cluster-scanner/internal/search_clusters.go @@ -0,0 +1,16 @@ +package internal + +import ( + "log/slog" + + "github.com/spectrocloud/palette-sdk-go/api/models" + "github.com/spectrocloud/palette-sdk-go/client" +) + +func SearchClusters (paletteClient *client.V1Client, logger *slog.Logger) ([]*models.V1SpectroClusterSummary, error) { + // Search for clusters + logger.Info("Searching for clusters...") + clusters, err := paletteClient.SearchClusterSummaries(&models.V1SearchFilterSpec{}, []*models.V1SearchFilterSortSpec{}) + + return clusters, err +} \ No newline at end of file diff --git a/scripts/cluster-scanner/internal/search_old_clusters.go b/scripts/cluster-scanner/internal/search_old_clusters.go new file mode 100644 index 0000000..0e5e3b0 --- /dev/null +++ b/scripts/cluster-scanner/internal/search_old_clusters.go @@ -0,0 +1,30 @@ +package internal + +import ( + "fmt" + "log/slog" + "time" + + "github.com/spectrocloud/palette-sdk-go/api/models" +) + +func SearchOldClusters (clusters []*models.V1SpectroClusterSummary, logger *slog.Logger) bool { + // Variable to keep track of any found clusters older than 24h + foundOldCluster := false + + // List the clusters that are running for more than 24h + for _, cluster := range clusters { + creationTime := cluster.Metadata.CreationTimestamp + timeValue := time.Time(creationTime) + timeNow := time.Now() + clusterAge := timeNow.Sub(timeValue) + + if clusterAge.Hours() >= 24 { + foundOldCluster = true + message := fmt.Sprintf("The %s cluster named %s has been running for %s. Are you sure you need this cluster?", cluster.SpecSummary.CloudConfig.CloudType, cluster.Metadata.Name, PrintFormattedAge(clusterAge)) + logger.Info(message) + } + } + + return foundOldCluster +} \ No newline at end of file diff --git a/scripts/cluster-scanner/main.go b/scripts/cluster-scanner/main.go new file mode 100644 index 0000000..c0716a8 --- /dev/null +++ b/scripts/cluster-scanner/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "log/slog" + "os" + + "github.com/spectrocloud/palette-samples/cluster-scanner/internal" + "github.com/spectrocloud/palette-sdk-go/client" +) + +func main() { + + // Read environment variables + host := os.Getenv("PALETTE_HOST") + apiKey := os.Getenv("PALETTE_API_KEY") + projectUid := os.Getenv("PALETTE_PROJECT_UID") + + logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) + + + if host == "" || apiKey == "" { + logger.Error("You must specify the PALETTE_HOST and PALETTE_API_KEY environment variables.") + os.Exit(1) + } + + // Initialize a Palette client + paletteClient := client.New( + client.WithPaletteURI(host), + client.WithAPIKey(apiKey), + ) + + if projectUid != "" { + client.WithScopeProject(projectUid)(paletteClient) + logger.Info("Setting scope to project.") + } else { + client.WithScopeTenant()(paletteClient) + logger.Info("Setting scope to tenant.") + } + + // Search for clusters + clusters, err := internal.SearchClusters(paletteClient, logger) + if err != nil { + logger.Error("Failed to search cluster summaries", "error", err) + os.Exit(2) + } + + // Check active clusters + if len(clusters) == 0 { + logger.Warn("There are no clusters running.") + return + } + + foundOldCluster := internal.SearchOldClusters(clusters, logger) + + if !foundOldCluster { + logger.Info("There are no clusters running for more than 24 hours.") + } +} \ No newline at end of file