mirror of
https://github.com/k8sgpt-ai/k8sgpt.git
synced 2026-03-19 19:42:38 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00c99dc934 | ||
|
|
a821814125 | ||
|
|
50d5d78c06 | ||
|
|
5b4224951e | ||
|
|
290a4be210 |
@@ -1 +1 @@
|
||||
{".":"0.4.22"}
|
||||
{".":"0.4.23"}
|
||||
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## [0.4.23](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.22...v0.4.23) (2025-08-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ClusterCatalog and ClusterExtension analyzers ([#1555](https://github.com/k8sgpt-ai/k8sgpt/issues/1555)) ([a821814](https://github.com/k8sgpt-ai/k8sgpt/commit/a821814125e25c062ff2faebf9df1b880414c22c))
|
||||
* oci genai chat models ([#1337](https://github.com/k8sgpt-ai/k8sgpt/issues/1337)) ([290a4be](https://github.com/k8sgpt-ai/k8sgpt/commit/290a4be210fbb508214070c31218138781d96142))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1537](https://github.com/k8sgpt-ai/k8sgpt/issues/1537)) ([50d5d78](https://github.com/k8sgpt-ai/k8sgpt/commit/50d5d78c06e42d75a2448989528e5e6be12ea825))
|
||||
* **deps:** update module helm.sh/helm/v3 to v3.17.4 [security] ([#1541](https://github.com/k8sgpt-ai/k8sgpt/issues/1541)) ([5b42249](https://github.com/k8sgpt-ai/k8sgpt/commit/5b4224951e7348e9d78292dadc9b9786957117f1))
|
||||
|
||||
## [0.4.22](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.21...v0.4.22) (2025-07-18)
|
||||
|
||||
|
||||
|
||||
14
README.md
14
README.md
@@ -62,7 +62,7 @@ brew install k8sgpt
|
||||
<!---x-release-please-start-version-->
|
||||
|
||||
```
|
||||
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.22/k8sgpt_386.rpm
|
||||
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.23/k8sgpt_386.rpm
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
|
||||
@@ -70,7 +70,7 @@ brew install k8sgpt
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.22/k8sgpt_amd64.rpm
|
||||
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.23/k8sgpt_amd64.rpm
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
</details>
|
||||
@@ -83,7 +83,7 @@ brew install k8sgpt
|
||||
<!---x-release-please-start-version-->
|
||||
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.22/k8sgpt_386.deb
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.23/k8sgpt_386.deb
|
||||
sudo dpkg -i k8sgpt_386.deb
|
||||
```
|
||||
|
||||
@@ -94,7 +94,7 @@ sudo dpkg -i k8sgpt_386.deb
|
||||
<!---x-release-please-start-version-->
|
||||
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.22/k8sgpt_amd64.deb
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.23/k8sgpt_amd64.deb
|
||||
sudo dpkg -i k8sgpt_amd64.deb
|
||||
```
|
||||
|
||||
@@ -109,7 +109,7 @@ sudo dpkg -i k8sgpt_amd64.deb
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.22/k8sgpt_386.apk
|
||||
wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.23/k8sgpt_386.apk
|
||||
apk add --allow-untrusted k8sgpt_386.apk
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -118,7 +118,7 @@ sudo dpkg -i k8sgpt_amd64.deb
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.22/k8sgpt_amd64.apk
|
||||
wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.23/k8sgpt_amd64.apk
|
||||
apk add --allow-untrusted k8sgpt_amd64.apk
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -270,6 +270,8 @@ you will be able to write your own analyzers.
|
||||
- [x] logAnalyzer
|
||||
- [x] storageAnalyzer
|
||||
- [x] securityAnalyzer
|
||||
- [x] ClusterCatalog
|
||||
- [x] ClusterExtension
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
2
go.mod
2
go.mod
@@ -14,7 +14,7 @@ require (
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/term v0.30.0
|
||||
helm.sh/helm/v3 v3.17.3
|
||||
helm.sh/helm/v3 v3.17.4
|
||||
k8s.io/api v0.32.2
|
||||
k8s.io/apimachinery v0.32.2
|
||||
k8s.io/client-go v0.32.2
|
||||
|
||||
6
go.sum
6
go.sum
@@ -965,6 +965,8 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
||||
github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
@@ -2258,8 +2260,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||
helm.sh/helm/v3 v3.17.3 h1:3n5rW3D0ArjFl0p4/oWO8IbY/HKaNNwJtOQFdH2AZHg=
|
||||
helm.sh/helm/v3 v3.17.3/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8=
|
||||
helm.sh/helm/v3 v3.17.4 h1:GK+vgn9gKCyoH44+f3B5zpA78iH3AK4ywIInDEmmn/g=
|
||||
helm.sh/helm/v3 v3.17.4/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -16,21 +16,32 @@ package ai
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/oracle/oci-go-sdk/v65/common"
|
||||
"github.com/oracle/oci-go-sdk/v65/generativeai"
|
||||
"github.com/oracle/oci-go-sdk/v65/generativeaiinference"
|
||||
"strings"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const ociClientName = "oci"
|
||||
|
||||
type ociModelVendor string
|
||||
|
||||
const (
|
||||
vendorCohere = "cohere"
|
||||
vendorMeta = "meta"
|
||||
)
|
||||
|
||||
type OCIGenAIClient struct {
|
||||
nopCloser
|
||||
|
||||
client *generativeaiinference.GenerativeAiInferenceClient
|
||||
model string
|
||||
model *generativeai.Model
|
||||
modelID string
|
||||
compartmentId string
|
||||
temperature float32
|
||||
topP float32
|
||||
topK int32
|
||||
maxTokens int
|
||||
}
|
||||
|
||||
@@ -40,9 +51,10 @@ func (c *OCIGenAIClient) GetName() string {
|
||||
|
||||
func (c *OCIGenAIClient) Configure(config IAIConfig) error {
|
||||
config.GetEndpointName()
|
||||
c.model = config.GetModel()
|
||||
c.modelID = config.GetModel()
|
||||
c.temperature = config.GetTemperature()
|
||||
c.topP = config.GetTopP()
|
||||
c.topK = config.GetTopK()
|
||||
c.maxTokens = config.GetMaxTokens()
|
||||
c.compartmentId = config.GetCompartmentId()
|
||||
provider := common.DefaultConfigProvider()
|
||||
@@ -51,47 +63,123 @@ func (c *OCIGenAIClient) Configure(config IAIConfig) error {
|
||||
return err
|
||||
}
|
||||
c.client = &client
|
||||
model, err := c.getModel(provider)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.model = model
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *OCIGenAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
|
||||
generateTextRequest := c.newGenerateTextRequest(prompt)
|
||||
generateTextResponse, err := c.client.GenerateText(ctx, generateTextRequest)
|
||||
request := c.newChatRequest(prompt)
|
||||
response, err := c.client.Chat(ctx, request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return extractGeneratedText(generateTextResponse.InferenceResponse)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return extractGeneratedText(response.ChatResponse)
|
||||
}
|
||||
|
||||
func (c *OCIGenAIClient) newGenerateTextRequest(prompt string) generativeaiinference.GenerateTextRequest {
|
||||
temperatureF64 := float64(c.temperature)
|
||||
topPF64 := float64(c.topP)
|
||||
return generativeaiinference.GenerateTextRequest{
|
||||
GenerateTextDetails: generativeaiinference.GenerateTextDetails{
|
||||
func (c *OCIGenAIClient) newChatRequest(prompt string) generativeaiinference.ChatRequest {
|
||||
return generativeaiinference.ChatRequest{
|
||||
ChatDetails: generativeaiinference.ChatDetails{
|
||||
CompartmentId: &c.compartmentId,
|
||||
ServingMode: generativeaiinference.OnDemandServingMode{
|
||||
ModelId: &c.model,
|
||||
},
|
||||
InferenceRequest: generativeaiinference.CohereLlmInferenceRequest{
|
||||
Prompt: &prompt,
|
||||
MaxTokens: &c.maxTokens,
|
||||
Temperature: &temperatureF64,
|
||||
TopP: &topPF64,
|
||||
},
|
||||
ServingMode: c.getServingMode(),
|
||||
ChatRequest: c.getChatModelRequest(prompt),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func extractGeneratedText(llmInferenceResponse generativeaiinference.LlmInferenceResponse) (string, error) {
|
||||
response, ok := llmInferenceResponse.(generativeaiinference.CohereLlmInferenceResponse)
|
||||
if !ok {
|
||||
return "", errors.New("failed to extract generated text from backed response")
|
||||
func (c *OCIGenAIClient) getChatModelRequest(prompt string) generativeaiinference.BaseChatRequest {
|
||||
temperatureF64 := float64(c.temperature)
|
||||
topPF64 := float64(c.topP)
|
||||
topK := int(c.topK)
|
||||
|
||||
switch c.getVendor() {
|
||||
case vendorMeta:
|
||||
messages := []generativeaiinference.Message{
|
||||
generativeaiinference.UserMessage{
|
||||
Content: []generativeaiinference.ChatContent{
|
||||
generativeaiinference.TextContent{
|
||||
Text: &prompt,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
// 0 is invalid for Meta vendor type, instead use -1 to disable topK sampling.
|
||||
if topK == 0 {
|
||||
topK = -1
|
||||
}
|
||||
return generativeaiinference.GenericChatRequest{
|
||||
Messages: messages,
|
||||
TopK: &topK,
|
||||
TopP: &topPF64,
|
||||
Temperature: &temperatureF64,
|
||||
MaxTokens: &c.maxTokens,
|
||||
}
|
||||
default: // Default to cohere
|
||||
return generativeaiinference.CohereChatRequest{
|
||||
Message: &prompt,
|
||||
MaxTokens: &c.maxTokens,
|
||||
Temperature: &temperatureF64,
|
||||
TopK: &topK,
|
||||
TopP: &topPF64,
|
||||
}
|
||||
|
||||
}
|
||||
sb := strings.Builder{}
|
||||
for _, text := range response.GeneratedTexts {
|
||||
if text.Text != nil {
|
||||
sb.WriteString(*text.Text)
|
||||
}
|
||||
|
||||
func extractGeneratedText(llmInferenceResponse generativeaiinference.BaseChatResponse) (string, error) {
|
||||
switch response := llmInferenceResponse.(type) {
|
||||
case generativeaiinference.GenericChatResponse:
|
||||
if len(response.Choices) > 0 && len(response.Choices[0].Message.GetContent()) > 0 {
|
||||
if content, ok := response.Choices[0].Message.GetContent()[0].(generativeaiinference.TextContent); ok {
|
||||
return *content.Text, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("no text found in oci response")
|
||||
case generativeaiinference.CohereChatResponse:
|
||||
return *response.Text, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unknown oci response type: %s", reflect.TypeOf(llmInferenceResponse).Name())
|
||||
}
|
||||
}
|
||||
|
||||
func (c *OCIGenAIClient) getServingMode() generativeaiinference.ServingMode {
|
||||
if c.isBaseModel() {
|
||||
return generativeaiinference.OnDemandServingMode{
|
||||
ModelId: &c.modelID,
|
||||
}
|
||||
}
|
||||
return sb.String(), nil
|
||||
return generativeaiinference.DedicatedServingMode{
|
||||
EndpointId: &c.modelID,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *OCIGenAIClient) getModel(provider common.ConfigurationProvider) (*generativeai.Model, error) {
|
||||
client, err := generativeai.NewGenerativeAiClientWithConfigurationProvider(provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response, err := client.GetModel(context.Background(), generativeai.GetModelRequest{
|
||||
ModelId: &c.modelID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response.Model, nil
|
||||
}
|
||||
|
||||
func (c *OCIGenAIClient) isBaseModel() bool {
|
||||
return c.model != nil && c.model.Type == generativeai.ModelTypeBase
|
||||
}
|
||||
|
||||
func (c *OCIGenAIClient) getVendor() ociModelVendor {
|
||||
if c.model == nil || c.model.Vendor == nil {
|
||||
return ""
|
||||
}
|
||||
return ociModelVendor(*c.model.Vendor)
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ var additionalAnalyzerMap = map[string]common.IAnalyzer{
|
||||
"HTTPRoute": HTTPRouteAnalyzer{},
|
||||
"Storage": StorageAnalyzer{},
|
||||
"Security": SecurityAnalyzer{},
|
||||
"ClusterCatalog": ClusterCatalogAnalyzer{},
|
||||
"ClusterExtension": ClusterExtensionAnalyzer{},
|
||||
}
|
||||
|
||||
func ListFilters() ([]string, []string, []string) {
|
||||
|
||||
161
pkg/analyzer/clustercatalog.go
Normal file
161
pkg/analyzer/clustercatalog.go
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
Copyright 2023 The K8sGPT Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
type ClusterCatalogAnalyzer struct{}
|
||||
|
||||
func (ClusterCatalogAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
kind := "ClusterCatalog"
|
||||
|
||||
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
var clusterCatalogGVR = schema.GroupVersionResource{
|
||||
Group: "olm.operatorframework.io",
|
||||
Version: "v1",
|
||||
Resource: "clustercatalogs",
|
||||
}
|
||||
if a.Client == nil {
|
||||
return nil, fmt.Errorf("client is nil in ClusterCatalogAnalyzer")
|
||||
}
|
||||
if a.Client.GetDynamicClient() == nil {
|
||||
return nil, fmt.Errorf("dynamic client is nil in ClusterCatalogAnalyzer")
|
||||
}
|
||||
|
||||
list, err := a.Client.GetDynamicClient().Resource(clusterCatalogGVR).Namespace("").List(a.Context, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, item := range list.Items {
|
||||
var failures []common.Failure
|
||||
catalog, err := ConvertToClusterCatalog(&item)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("ClusterCatalog: %s | Source: %s\n", catalog.Name, catalog.Spec.Source.Image.Ref)
|
||||
failures, err = ValidateClusterCatalog(failures, catalog)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[catalog.Name] = common.PreAnalysis{
|
||||
Catalog: *catalog,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
AnalyzerErrorsMetric.WithLabelValues(kind, catalog.Name, "").Set(float64(len(failures)))
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: kind,
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, found := util.GetParent(a.Client, value.Node.ObjectMeta)
|
||||
if found {
|
||||
currentAnalysis.ParentObject = parent
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, err
|
||||
}
|
||||
|
||||
func ConvertToClusterCatalog(u *unstructured.Unstructured) (*common.ClusterCatalog, error) {
|
||||
var cc common.ClusterCatalog
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &cc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert to ClusterCatalog: %w", err)
|
||||
}
|
||||
return &cc, nil
|
||||
}
|
||||
|
||||
func addCatalogConditionFailure(failures []common.Failure, catalogName string, catalogCondition metav1.Condition) []common.Failure {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("OLMv1 ClusterCatalog: %s has condition of type %s, reason %s: %s", catalogName, catalogCondition.Type, catalogCondition.Reason, catalogCondition.Message),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: catalogName,
|
||||
Masked: util.MaskString(catalogName),
|
||||
},
|
||||
},
|
||||
})
|
||||
return failures
|
||||
}
|
||||
|
||||
func addCatalogFailure(failures []common.Failure, catalogName string, err error) []common.Failure {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("%s has error: %s", catalogName, err.Error()),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: catalogName,
|
||||
Masked: util.MaskString(catalogName),
|
||||
},
|
||||
},
|
||||
})
|
||||
return failures
|
||||
}
|
||||
|
||||
func ValidateClusterCatalog(failures []common.Failure, catalog *common.ClusterCatalog) ([]common.Failure, error) {
|
||||
if !isValidImageRef(catalog.Spec.Source.Image.Ref) {
|
||||
failures = addCatalogFailure(failures, catalog.Name, fmt.Errorf("invalid image ref format in spec.source.image.ref: %s", catalog.Spec.Source.Image.Ref))
|
||||
}
|
||||
|
||||
// Check status.resolvedSource.image.ref ends with @sha256:...
|
||||
if catalog.Status.ResolvedSource != nil {
|
||||
if catalog.Status.ResolvedSource.Image.Ref == "" {
|
||||
failures = addCatalogFailure(failures, catalog.Name, fmt.Errorf("missing status.resolvedSource.image.ref"))
|
||||
}
|
||||
if !regexp.MustCompile(`@sha256:[a-f0-9]{64}$`).MatchString(catalog.Status.ResolvedSource.Image.Ref) {
|
||||
failures = addCatalogFailure(failures, catalog.Name, fmt.Errorf("status.resolvedSource.image.ref must end with @sha256:<digest>"))
|
||||
}
|
||||
}
|
||||
|
||||
for _, condition := range catalog.Status.Conditions {
|
||||
if condition.Status != "True" && condition.Type == "Serving" {
|
||||
failures = addCatalogConditionFailure(failures, catalog.Name, condition)
|
||||
}
|
||||
if condition.Type == "Progressing" && condition.Reason != "Succeeded" {
|
||||
failures = addCatalogConditionFailure(failures, catalog.Name, condition)
|
||||
}
|
||||
}
|
||||
|
||||
return failures, nil
|
||||
}
|
||||
|
||||
// isValidImageRef does a simple regex check to validate image refs
|
||||
func isValidImageRef(ref string) bool {
|
||||
pattern := `^([a-zA-Z0-9\-\.]+(?::[0-9]+)?/)?([a-z0-9]+(?:[._\-\/][a-z0-9]+)*)(:[\w][\w.-]{0,127})?(?:@sha256:[a-f0-9]{64})?$`
|
||||
return regexp.MustCompile(pattern).MatchString(ref)
|
||||
}
|
||||
182
pkg/analyzer/clustercatalog_test.go
Normal file
182
pkg/analyzer/clustercatalog_test.go
Normal file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
Copyright 2023 The K8sGPT Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
dynamicfake "k8s.io/client-go/dynamic/fake"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestClusterCatalogAnalyzer(t *testing.T) {
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: "olm.operatorframework.io",
|
||||
Version: "v1",
|
||||
Resource: "clustercatalogs",
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
|
||||
dynamicClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(
|
||||
scheme,
|
||||
map[schema.GroupVersionResource]string{
|
||||
gvr: "ClusterCatalogList",
|
||||
},
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "olm.operatorframework.io/v1",
|
||||
"kind": "ClusterCatalog",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "Valid ClusterCatalog",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"availabilityMode": "Available",
|
||||
"source": map[string]interface{}{
|
||||
"type": "Image",
|
||||
"image": map[string]interface{}{
|
||||
"ref": "registry.redhat.io/redhat/community-operator-index:v4.19",
|
||||
"pollIntervalMinutes": float64(10),
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Progressing",
|
||||
"status": "True",
|
||||
"reason": "Succeeded",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"type": "Serving",
|
||||
"status": "True",
|
||||
"reason": "Available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "olm.operatorframework.io/v1",
|
||||
"kind": "ClusterCatalog",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "Invalid availabilityMode",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"availabilityMode": "test",
|
||||
"source": map[string]interface{}{
|
||||
"type": "Image",
|
||||
"image": map[string]interface{}{
|
||||
"ref": "registry.redhat.io/redhat/community-operator-index:v4.19",
|
||||
"pollIntervalMinutes": float64(10),
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Progressing",
|
||||
"status": "True",
|
||||
"reason": "Retrying",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "olm.operatorframework.io/v1",
|
||||
"kind": "ClusterCatalog",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "Invalid pollIntervalMinutes",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"availabilityMode": "Available",
|
||||
"source": map[string]interface{}{
|
||||
"type": "Image",
|
||||
"image": map[string]interface{}{
|
||||
"ref": "registry.redhat.io/redhat/community-operator-index:v4.19",
|
||||
"pollIntervalMinutes": float64(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Progressing",
|
||||
"status": "True",
|
||||
"reason": "Retrying",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "olm.operatorframework.io/v1",
|
||||
"kind": "ClusterCatalog",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "Invalid image reference",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"availabilityMode": "Available",
|
||||
"source": map[string]interface{}{
|
||||
"type": "Image",
|
||||
"image": map[string]interface{}{
|
||||
"ref": "quay.io/test/community-operator-index:v4.19",
|
||||
"pollIntervalMinutes": float64(10),
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Progressing",
|
||||
"status": "True",
|
||||
"reason": "Retrying",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(),
|
||||
DynamicClient: dynamicClient,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "test",
|
||||
}
|
||||
|
||||
ccAnalyzer := ClusterCatalogAnalyzer{}
|
||||
results, err := ccAnalyzer.Analyze(config)
|
||||
for _, res := range results {
|
||||
fmt.Printf("Result: %s | Failures: %d\n", res.Name, len(res.Error))
|
||||
for _, err := range res.Error {
|
||||
fmt.Printf(" - %s\n", err)
|
||||
}
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(results))
|
||||
}
|
||||
148
pkg/analyzer/clusterextension.go
Normal file
148
pkg/analyzer/clusterextension.go
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
Copyright 2023 The K8sGPT Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
type ClusterExtensionAnalyzer struct{}
|
||||
|
||||
func (ClusterExtensionAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
kind := "ClusterExtension"
|
||||
|
||||
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
var clusterExtensionGVR = schema.GroupVersionResource{
|
||||
Group: "olm.operatorframework.io",
|
||||
Version: "v1",
|
||||
Resource: "clusterextensions",
|
||||
}
|
||||
if a.Client == nil {
|
||||
return nil, fmt.Errorf("client is nil in ClusterExtensionAnalyzer")
|
||||
}
|
||||
if a.Client.GetDynamicClient() == nil {
|
||||
return nil, fmt.Errorf("dynamic client is nil in ClusterExtensionAnalyzer")
|
||||
}
|
||||
|
||||
list, err := a.Client.GetDynamicClient().Resource(clusterExtensionGVR).Namespace("").List(a.Context, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, item := range list.Items {
|
||||
var failures []common.Failure
|
||||
extension, err := ConvertToClusterExtension(&item)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("ClusterExtension: %s | Source: %s\n", extension.Name, extension.Spec.Source.Catalog.PackageName)
|
||||
failures, err = ValidateClusterExtension(failures, extension)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[extension.Name] = common.PreAnalysis{
|
||||
Extension: *extension,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
AnalyzerErrorsMetric.WithLabelValues(kind, extension.Name, "").Set(float64(len(failures)))
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: kind,
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, found := util.GetParent(a.Client, value.Node.ObjectMeta)
|
||||
if found {
|
||||
currentAnalysis.ParentObject = parent
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, err
|
||||
}
|
||||
|
||||
func ConvertToClusterExtension(u *unstructured.Unstructured) (*common.ClusterExtension, error) {
|
||||
var ce common.ClusterExtension
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &ce)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert to ClusterExtension: %w", err)
|
||||
}
|
||||
return &ce, nil
|
||||
}
|
||||
|
||||
func addExtensionConditionFailure(failures []common.Failure, extensionName string, extensionCondition metav1.Condition) []common.Failure {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("OLMv1 ClusterExtension: %s has condition of type %s, reason %s: %s", extensionName, extensionCondition.Type, extensionCondition.Reason, extensionCondition.Message),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: extensionName,
|
||||
Masked: util.MaskString(extensionName),
|
||||
},
|
||||
},
|
||||
})
|
||||
return failures
|
||||
}
|
||||
|
||||
func addExtensionFailure(failures []common.Failure, extensionName string, err error) []common.Failure {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("%s has error: %s", extensionName, err.Error()),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: extensionName,
|
||||
Masked: util.MaskString(extensionName),
|
||||
},
|
||||
},
|
||||
})
|
||||
return failures
|
||||
}
|
||||
|
||||
func ValidateClusterExtension(failures []common.Failure, extension *common.ClusterExtension) ([]common.Failure, error) {
|
||||
if extension.Spec.Source.Catalog != nil && extension.Spec.Source.Catalog.UpgradeConstraintPolicy != "CatalogProvided" && extension.Spec.Source.Catalog.UpgradeConstraintPolicy != "SelfCertified" {
|
||||
failures = addExtensionFailure(failures, extension.Name, fmt.Errorf("invalid or missing extension.Spec.Source.Catalog.UpgradeConstraintPolicy (expecting 'SelfCertified' or 'CatalogProvided')"))
|
||||
}
|
||||
|
||||
if extension.Spec.Source.SourceType != "Catalog" {
|
||||
failures = addExtensionFailure(failures, extension.Name, fmt.Errorf("invalid or missing spec.source.sourceType (expecting 'Catalog')"))
|
||||
}
|
||||
|
||||
for _, condition := range extension.Status.Conditions {
|
||||
if condition.Status != "True" && condition.Type == "Installed" {
|
||||
failures = addExtensionConditionFailure(failures, extension.Name, condition)
|
||||
}
|
||||
if condition.Type == "Progressing" && condition.Reason != "Succeeded" {
|
||||
failures = addExtensionConditionFailure(failures, extension.Name, condition)
|
||||
}
|
||||
}
|
||||
|
||||
return failures, nil
|
||||
}
|
||||
179
pkg/analyzer/clusterextension_test.go
Normal file
179
pkg/analyzer/clusterextension_test.go
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
Copyright 2023 The K8sGPT Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
dynamicfake "k8s.io/client-go/dynamic/fake"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestClusterExtensionAnalyzer(t *testing.T) {
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: "olm.operatorframework.io",
|
||||
Version: "v1",
|
||||
Resource: "clusterextensions",
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
|
||||
dynamicClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(
|
||||
scheme,
|
||||
map[schema.GroupVersionResource]string{
|
||||
gvr: "ClusterExtensionList",
|
||||
},
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "olm.operatorframework.io/v1",
|
||||
"kind": "ClusterExtension",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "Valid SelfCertified ClusterExtension",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"source": map[string]interface{}{
|
||||
"sourceType": "Catalog",
|
||||
"catalog": map[string]interface{}{
|
||||
"upgradeConstraintPolicy": "SelfCertified",
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Installed",
|
||||
"status": "True",
|
||||
"reason": "Succeeded",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "olm.operatorframework.io/v1",
|
||||
"kind": "ClusterExtension",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "Valid CatalogProvided ClusterExtension",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"source": map[string]interface{}{
|
||||
"sourceType": "Catalog",
|
||||
"catalog": map[string]interface{}{
|
||||
"upgradeConstraintPolicy": "CatalogProvided",
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Installed",
|
||||
"status": "True",
|
||||
"reason": "Succeeded",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "olm.operatorframework.io/v1",
|
||||
"kind": "ClusterExtension",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "Invalid UpgradeConstraintPolicy",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"source": map[string]interface{}{
|
||||
"sourceType": "Catalog",
|
||||
"catalog": map[string]interface{}{
|
||||
"upgradeConstraintPolicy": "InvalidPolicy",
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Progressing",
|
||||
"status": "True",
|
||||
"reason": "Retrying",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"type": "Installed",
|
||||
"status": "False",
|
||||
"reason": "Failed",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "olm.operatorframework.io/v1",
|
||||
"kind": "ClusterExtension",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "Invalid SourceType",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"source": map[string]interface{}{
|
||||
"sourceType": "Git",
|
||||
"catalog": map[string]interface{}{
|
||||
"upgradeConstraintPolicy": "CatalogProvided",
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Progressing",
|
||||
"status": "True",
|
||||
"reason": "Retrying",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"type": "Installed",
|
||||
"status": "False",
|
||||
"reason": "Failed",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(),
|
||||
DynamicClient: dynamicClient,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "test",
|
||||
}
|
||||
|
||||
ceAnalyzer := ClusterExtensionAnalyzer{}
|
||||
results, err := ceAnalyzer.Analyze(config)
|
||||
for _, res := range results {
|
||||
fmt.Printf("Result: %s | Failures: %d\n", res.Name, len(res.Error))
|
||||
for _, err := range res.Error {
|
||||
fmt.Printf(" - %s\n", err)
|
||||
}
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(results))
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
networkv1 "k8s.io/api/networking/v1"
|
||||
policyv1 "k8s.io/api/policy/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
gtwapi "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
|
||||
@@ -68,6 +69,8 @@ type PreAnalysis struct {
|
||||
ScaledObject keda.ScaledObject
|
||||
KyvernoPolicyReport kyverno.PolicyReport
|
||||
KyvernoClusterPolicyReport kyverno.ClusterPolicyReport
|
||||
Catalog ClusterCatalog
|
||||
Extension ClusterExtension
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
@@ -93,3 +96,117 @@ type Sensitive struct {
|
||||
Unmasked string
|
||||
Masked string
|
||||
}
|
||||
|
||||
type (
|
||||
SourceType string
|
||||
AvailabilityMode string
|
||||
UpgradeConstraintPolicy string
|
||||
CRDUpgradeSafetyEnforcement string
|
||||
)
|
||||
|
||||
type ClusterCatalog struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata"`
|
||||
Spec ClusterCatalogSpec `json:"spec"`
|
||||
Status ClusterCatalogStatus `json:"status,omitempty"`
|
||||
}
|
||||
type ClusterCatalogList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata"`
|
||||
Items []ClusterCatalog `json:"items"`
|
||||
}
|
||||
|
||||
type ClusterCatalogSpec struct {
|
||||
Source CatalogSource `json:"source"`
|
||||
|
||||
Priority int32 `json:"priority"`
|
||||
|
||||
AvailabilityMode AvailabilityMode `json:"availabilityMode,omitempty"`
|
||||
}
|
||||
|
||||
type ClusterCatalogStatus struct {
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
|
||||
|
||||
ResolvedSource *ResolvedCatalogSource `json:"resolvedSource,omitempty"`
|
||||
|
||||
URLs *ClusterCatalogURLs `json:"urls,omitempty"`
|
||||
LastUnpacked *metav1.Time `json:"lastUnpacked,omitempty"`
|
||||
}
|
||||
|
||||
type ClusterCatalogURLs struct {
|
||||
Base string `json:"base"`
|
||||
}
|
||||
type CatalogSource struct {
|
||||
Type SourceType `json:"type"`
|
||||
Image *ImageSource `json:"image,omitempty"`
|
||||
}
|
||||
type ResolvedCatalogSource struct {
|
||||
Type SourceType `json:"type"`
|
||||
Image *ResolvedImageSource `json:"image"`
|
||||
}
|
||||
type ResolvedImageSource struct {
|
||||
Ref string `json:"ref"`
|
||||
}
|
||||
|
||||
type ImageSource struct {
|
||||
Ref string `json:"ref"`
|
||||
PollIntervalMinutes *int `json:"pollIntervalMinutes,omitempty"`
|
||||
}
|
||||
|
||||
type ClusterExtension struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
Spec ClusterExtensionSpec `json:"spec,omitempty"`
|
||||
Status ClusterExtensionStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
type ClusterExtensionSpec struct {
|
||||
Namespace string `json:"namespace"`
|
||||
ServiceAccount ServiceAccountReference `json:"serviceAccount"`
|
||||
Source SourceConfig `json:"source"`
|
||||
Install *ClusterExtensionInstallConfig `json:"install,omitempty"`
|
||||
}
|
||||
|
||||
type ClusterExtensionInstallConfig struct {
|
||||
Preflight *PreflightConfig `json:"preflight,omitempty"`
|
||||
}
|
||||
|
||||
type PreflightConfig struct {
|
||||
CRDUpgradeSafety *CRDUpgradeSafetyPreflightConfig `json:"crdUpgradeSafety"`
|
||||
}
|
||||
|
||||
type CRDUpgradeSafetyPreflightConfig struct {
|
||||
Enforcement CRDUpgradeSafetyEnforcement `json:"enforcement"`
|
||||
}
|
||||
|
||||
type ServiceAccountReference struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type SourceConfig struct {
|
||||
SourceType string `json:"sourceType"`
|
||||
Catalog *CatalogFilter `json:"catalog,omitempty"`
|
||||
}
|
||||
|
||||
type CatalogFilter struct {
|
||||
PackageName string `json:"packageName"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Channels []string `json:"channels,omitempty"`
|
||||
Selector *metav1.LabelSelector `json:"selector,omitempty"`
|
||||
UpgradeConstraintPolicy UpgradeConstraintPolicy `json:"upgradeConstraintPolicy,omitempty"`
|
||||
}
|
||||
|
||||
type ClusterExtensionStatus struct {
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
|
||||
|
||||
Install *ClusterExtensionInstallStatus `json:"install,omitempty"`
|
||||
}
|
||||
|
||||
type ClusterExtensionInstallStatus struct {
|
||||
Bundle BundleMetadata `json:"bundle"`
|
||||
}
|
||||
|
||||
type BundleMetadata struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ limitations under the License.
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
"k8s.io/client-go/rest"
|
||||
@@ -33,6 +34,10 @@ func (c *Client) GetCtrlClient() ctrl.Client {
|
||||
return c.CtrlClient
|
||||
}
|
||||
|
||||
func (c *Client) GetDynamicClient() dynamic.Interface {
|
||||
return c.DynamicClient
|
||||
}
|
||||
|
||||
func NewClient(kubecontext string, kubeconfig string) (*Client, error) {
|
||||
var config *rest.Config
|
||||
config, err := rest.InClusterConfig()
|
||||
@@ -69,10 +74,16 @@ func NewClient(kubecontext string, kubeconfig string) (*Client, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dynamicClient, err := dynamic.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{
|
||||
Client: clientSet,
|
||||
CtrlClient: ctrlClient,
|
||||
Config: config,
|
||||
ServerVersion: serverVersion,
|
||||
DynamicClient: dynamicClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
openapi_v2 "github.com/google/gnostic/openapiv2"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
ctrl "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -14,6 +15,7 @@ type Client struct {
|
||||
CtrlClient ctrl.Client
|
||||
Config *rest.Config
|
||||
ServerVersion *version.Info
|
||||
DynamicClient dynamic.Interface
|
||||
}
|
||||
|
||||
type K8sApiReference struct {
|
||||
|
||||
Reference in New Issue
Block a user