K8s/Test: Allow setting license path in test helper (#99786)

This commit is contained in:
Ryan McKinley 2025-01-30 09:59:00 +03:00 committed by GitHub
parent 4b0f8d8363
commit 3c0383f0d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 147 additions and 132 deletions

95
pkg/tests/apis/client.go Normal file
View File

@ -0,0 +1,95 @@
package apis
import (
"context"
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/types"
"k8s.io/client-go/dynamic"
)
// TypedClient is the struct that implements a typed interface for resource operations
type TypedClient[T any, L any] struct {
Client dynamic.ResourceInterface
}
func (c *TypedClient[T, L]) Create(ctx context.Context, resource *T, opts metav1.CreateOptions) (*T, error) {
unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(resource)
if err != nil {
return nil, err
}
u := &unstructured.Unstructured{Object: unstructuredObj}
result, err := c.Client.Create(ctx, u, opts)
if err != nil {
return nil, err
}
createdObj := new(T)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(result.Object, createdObj)
if err != nil {
return nil, err
}
return createdObj, nil
}
func (c *TypedClient[T, L]) Update(ctx context.Context, resource *T, opts metav1.UpdateOptions) (*T, error) {
unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(resource)
if err != nil {
return nil, err
}
u := &unstructured.Unstructured{Object: unstructuredObj}
result, err := c.Client.Update(ctx, u, opts)
if err != nil {
return nil, err
}
updatedObj := new(T)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(result.Object, updatedObj)
if err != nil {
return nil, err
}
return updatedObj, nil
}
func (c *TypedClient[T, L]) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
return c.Client.Delete(ctx, name, opts)
}
func (c *TypedClient[T, L]) Get(ctx context.Context, name string, opts metav1.GetOptions) (*T, error) {
result, err := c.Client.Get(ctx, name, opts)
if err != nil {
return nil, err
}
retrievedObj := new(T)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(result.Object, retrievedObj)
if err != nil {
return nil, err
}
return retrievedObj, nil
}
func (c *TypedClient[T, L]) List(ctx context.Context, opts metav1.ListOptions) (*L, error) {
result, err := c.Client.List(ctx, opts)
if err != nil {
return nil, err
}
listObj := new(L)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(result.UnstructuredContent(), listObj)
if err != nil {
return nil, err
}
return listObj, nil
}
func (c *TypedClient[T, L]) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*T, error) {
result, err := c.Client.Patch(ctx, name, pt, data, opts, subresources...)
if err != nil {
return nil, err
}
patchedObj := new(T)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(result.Object, patchedObj)
if err != nil {
return nil, err
}
return patchedObj, nil
}

View File

@ -8,10 +8,12 @@ import (
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -19,7 +21,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
"k8s.io/apimachinery/pkg/types"
yamlutil "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
@ -661,86 +662,49 @@ func (c *K8sTestHelper) CreateTeam(name, email string, orgID int64) team.Team {
return team
}
// TypedClient is the struct that implements a typed interface for resource operations
type TypedClient[T any, L any] struct {
Client dynamic.ResourceInterface
}
// Compare the OpenAPI schema from one api against a cached snapshot
func VerifyOpenAPISnapshots(t *testing.T, dir string, gv schema.GroupVersion, h *K8sTestHelper) {
if gv.Group == "" {
return // skip invalid groups
}
path := fmt.Sprintf("/openapi/v3/apis/%s/%s", gv.Group, gv.Version)
t.Run(path, func(t *testing.T) {
rsp := DoRequest(h, RequestParams{
Method: http.MethodGet,
Path: path,
User: h.Org1.Admin,
}, &AnyResource{})
func (c *TypedClient[T, L]) Create(ctx context.Context, resource *T, opts metav1.CreateOptions) (*T, error) {
unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(resource)
if err != nil {
return nil, err
}
u := &unstructured.Unstructured{Object: unstructuredObj}
result, err := c.Client.Create(ctx, u, opts)
if err != nil {
return nil, err
}
createdObj := new(T)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(result.Object, createdObj)
if err != nil {
return nil, err
}
return createdObj, nil
}
require.NotNil(t, rsp.Response)
require.Equal(t, 200, rsp.Response.StatusCode, path)
func (c *TypedClient[T, L]) Update(ctx context.Context, resource *T, opts metav1.UpdateOptions) (*T, error) {
unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(resource)
if err != nil {
return nil, err
}
u := &unstructured.Unstructured{Object: unstructuredObj}
result, err := c.Client.Update(ctx, u, opts)
if err != nil {
return nil, err
}
updatedObj := new(T)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(result.Object, updatedObj)
if err != nil {
return nil, err
}
return updatedObj, nil
}
var prettyJSON bytes.Buffer
err := json.Indent(&prettyJSON, rsp.Body, "", " ")
require.NoError(t, err)
pretty := prettyJSON.String()
func (c *TypedClient[T, L]) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
return c.Client.Delete(ctx, name, opts)
}
write := false
fpath := filepath.Join(dir, fmt.Sprintf("%s-%s.json", gv.Group, gv.Version))
func (c *TypedClient[T, L]) Get(ctx context.Context, name string, opts metav1.GetOptions) (*T, error) {
result, err := c.Client.Get(ctx, name, opts)
if err != nil {
return nil, err
}
retrievedObj := new(T)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(result.Object, retrievedObj)
if err != nil {
return nil, err
}
return retrievedObj, nil
}
// nolint:gosec
// We can ignore the gosec G304 warning since this is a test and the function is only called with explicit paths
body, err := os.ReadFile(fpath)
if err == nil {
if !assert.JSONEq(t, string(body), pretty) {
t.Logf("openapi spec has changed: %s", path)
t.Fail()
write = true
}
} else {
t.Errorf("missing openapi spec for: %s", path)
write = true
}
func (c *TypedClient[T, L]) List(ctx context.Context, opts metav1.ListOptions) (*L, error) {
result, err := c.Client.List(ctx, opts)
if err != nil {
return nil, err
}
listObj := new(L)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(result.UnstructuredContent(), listObj)
if err != nil {
return nil, err
}
return listObj, nil
}
func (c *TypedClient[T, L]) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*T, error) {
result, err := c.Client.Patch(ctx, name, pt, data, opts, subresources...)
if err != nil {
return nil, err
}
patchedObj := new(T)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(result.Object, patchedObj)
if err != nil {
return nil, err
}
return patchedObj, nil
if write {
e2 := os.WriteFile(fpath, []byte(pretty), 0644)
if e2 != nil {
t.Errorf("error writing file: %s", e2.Error())
}
}
})
}

View File

@ -1,22 +1,16 @@
package apis
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/version"
apimachineryversion "k8s.io/apimachinery/pkg/version"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/tests/testinfra"
"github.com/grafana/grafana/pkg/tests/testsuite"
@ -36,6 +30,7 @@ func TestIntegrationOpenAPIs(t *testing.T) {
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2, // Will be default on by G12
featuremgmt.FlagQueryService, // Query Library
featuremgmt.FlagProvisioning,
},
})
@ -77,50 +72,3 @@ func TestIntegrationOpenAPIs(t *testing.T) {
VerifyOpenAPISnapshots(t, dir, gv, h)
}
}
// This function should be moved to oss (it is now a duplicate)
func VerifyOpenAPISnapshots(t *testing.T, dir string, gv schema.GroupVersion, h *K8sTestHelper) {
if gv.Group == "" {
return // skip invalid groups
}
path := fmt.Sprintf("/openapi/v3/apis/%s/%s", gv.Group, gv.Version)
t.Run(path, func(t *testing.T) {
rsp := DoRequest(h, RequestParams{
Method: http.MethodGet,
Path: path,
User: h.Org1.Admin,
}, &AnyResource{})
require.NotNil(t, rsp.Response)
require.Equal(t, 200, rsp.Response.StatusCode, path)
var prettyJSON bytes.Buffer
err := json.Indent(&prettyJSON, rsp.Body, "", " ")
require.NoError(t, err)
pretty := prettyJSON.String()
write := false
fpath := filepath.Join(dir, fmt.Sprintf("%s-%s.json", gv.Group, gv.Version))
// nolint:gosec
// We can ignore the gosec G304 warning since this is a test and the function is only called with explicit paths
body, err := os.ReadFile(fpath)
if err == nil {
if !assert.JSONEq(t, string(body), pretty) {
t.Logf("openapi spec has changed: %s", path)
t.Fail()
write = true
}
} else {
t.Errorf("missing openapi spec for: %s", path)
write = true
}
if write {
e2 := os.WriteFile(fpath, []byte(pretty), 0644)
if e2 != nil {
t.Errorf("error writing file: %s", e2.Error())
}
}
})
}

View File

@ -295,6 +295,13 @@ func CreateGrafDir(t *testing.T, opts GrafanaOpts) (string, string) {
_, err = alertingSect.NewKey("max_attempts", "3")
require.NoError(t, err)
if opts.LicensePath != "" {
section, err := cfg.NewSection("enterprise")
require.NoError(t, err)
_, err = section.NewKey("license_path", opts.LicensePath)
require.NoError(t, err)
}
rbacSect, err := cfg.NewSection("rbac")
require.NoError(t, err)
_, err = rbacSect.NewKey("permission_cache", "false")
@ -529,6 +536,7 @@ type GrafanaOpts struct {
GrafanaComAPIURL string
UnifiedStorageConfig map[string]setting.UnifiedStorageConfig
GrafanaComSSOAPIToken string
LicensePath string
// When "unified-grpc" is selected it will also start the grpc server
APIServerStorageType options.StorageType