mirror of
https://github.com/1Password/onepassword-operator.git
synced 2025-10-22 07:28:06 +00:00
Pass CRDs on createion of kube instance
This commit is contained in:
@@ -13,14 +13,22 @@ import (
|
|||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
|
apix "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
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"
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/yaml"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||||
|
|
||||||
apiv1 "github.com/1Password/onepassword-operator/api/v1"
|
apiv1 "github.com/1Password/onepassword-operator/api/v1"
|
||||||
|
"github.com/1Password/onepassword-operator/pkg/testhelper/defaults"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestConfig struct {
|
type TestConfig struct {
|
||||||
@@ -32,11 +40,13 @@ type Config struct {
|
|||||||
Namespace string
|
Namespace string
|
||||||
ManifestsDir string
|
ManifestsDir string
|
||||||
TestConfig *TestConfig
|
TestConfig *TestConfig
|
||||||
|
CRDs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Kube struct {
|
type Kube struct {
|
||||||
Config *Config
|
Config *Config
|
||||||
Client client.Client
|
Client client.Client
|
||||||
|
Mapper meta.RESTMapper
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewKubeClient(config *Config) *Kube {
|
func NewKubeClient(config *Config) *Kube {
|
||||||
@@ -49,12 +59,26 @@ func NewKubeClient(config *Config) *Kube {
|
|||||||
restConfig, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
|
restConfig, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Install CRDs first (so discovery sees them)
|
||||||
|
installCRDs(context.Background(), restConfig, config.CRDs)
|
||||||
|
|
||||||
|
// Build an http.Client from restConfig
|
||||||
|
httpClient, err := rest.HTTPClientFor(restConfig)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Create a Dynamic RESTMapper that uses restConfig
|
||||||
|
rm, err := apiutil.NewDynamicRESTMapper(restConfig, httpClient)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
scheme := runtime.NewScheme()
|
scheme := runtime.NewScheme()
|
||||||
utilruntime.Must(corev1.AddToScheme(scheme))
|
utilruntime.Must(corev1.AddToScheme(scheme))
|
||||||
utilruntime.Must(appsv1.AddToScheme(scheme))
|
utilruntime.Must(appsv1.AddToScheme(scheme))
|
||||||
utilruntime.Must(apiv1.AddToScheme(scheme))
|
utilruntime.Must(apiv1.AddToScheme(scheme)) // add OnePasswordItem to scheme
|
||||||
|
|
||||||
kubernetesClient, err := client.New(restConfig, client.Options{Scheme: scheme})
|
kubernetesClient, err := client.New(restConfig, client.Options{
|
||||||
|
Scheme: scheme,
|
||||||
|
Mapper: rm,
|
||||||
|
})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
// update the current context’s namespace in kubeconfig
|
// update the current context’s namespace in kubeconfig
|
||||||
@@ -75,6 +99,7 @@ func NewKubeClient(config *Config) *Kube {
|
|||||||
return &Kube{
|
return &Kube{
|
||||||
Config: config,
|
Config: config,
|
||||||
Client: kubernetesClient,
|
Client: kubernetesClient,
|
||||||
|
Mapper: rm,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,19 +138,79 @@ func (k *Kube) ApplyOnePasswordItem(ctx context.Context, fileName string) {
|
|||||||
data, err := os.ReadFile(k.Config.ManifestsDir + "/" + fileName)
|
data, err := os.ReadFile(k.Config.ManifestsDir + "/" + fileName)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
item := &apiv1.OnePasswordItem{}
|
// Decode YAML -> JSON -> unstructured.Unstructured
|
||||||
err = yaml.Unmarshal(data, item)
|
jsonBytes, err := yaml.ToJSON(data)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
if item.Namespace == "" {
|
var obj unstructured.Unstructured
|
||||||
item.Namespace = k.Config.Namespace
|
Expect(obj.UnmarshalJSON(jsonBytes)).To(Succeed())
|
||||||
|
|
||||||
|
// Default namespace for namespaced resources if not set in YAML
|
||||||
|
if obj.GetNamespace() == "" && k.Config.Namespace != "" {
|
||||||
|
gvk := obj.GroupVersionKind()
|
||||||
|
mapping, mapErr := k.Mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||||
|
if mapErr == nil && mapping.Scope.Name() == meta.RESTScopeNameNamespace {
|
||||||
|
obj.SetNamespace(k.Config.Namespace)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = k.Client.Get(c, client.ObjectKey{Name: item.Name, Namespace: k.Config.Namespace}, item)
|
// Server-Side Apply (create or update)
|
||||||
if errors.IsNotFound(err) {
|
patchOpts := []client.PatchOption{
|
||||||
err = k.Client.Create(c, item)
|
client.FieldOwner("onepassword-e2e"),
|
||||||
} else {
|
client.ForceOwnership, // to force-take conflicting fields
|
||||||
err = k.Client.Update(c, item)
|
|
||||||
}
|
}
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(k.Client.Patch(c, &obj, client.Apply, patchOpts...)).To(Succeed())
|
||||||
|
}
|
||||||
|
|
||||||
|
func installCRDs(ctx context.Context, restConfig *rest.Config, crdFiles []string) {
|
||||||
|
apixClient, err := apix.NewForConfig(restConfig)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
for _, f := range crdFiles {
|
||||||
|
By("Installing CRD " + f)
|
||||||
|
b, err := os.ReadFile(filepath.Clean(f))
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
var crd apiextv1.CustomResourceDefinition
|
||||||
|
err = yaml.Unmarshal(b, &crd)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Create or Update
|
||||||
|
_, err = apixClient.ApiextensionsV1().CustomResourceDefinitions().Create(ctx, &crd, metav1.CreateOptions{})
|
||||||
|
if apierrors.IsAlreadyExists(err) {
|
||||||
|
existing, getErr := apixClient.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, crd.Name, metav1.GetOptions{})
|
||||||
|
Expect(getErr).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
crd.ResourceVersion = existing.ResourceVersion
|
||||||
|
_, err = apixClient.ApiextensionsV1().CustomResourceDefinitions().Update(ctx, &crd, metav1.UpdateOptions{})
|
||||||
|
}
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
waitCRDEstablished(ctx, apixClient, crd.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitCRDEstablished Wait until the CRD reaches Established=True, retrying until the suite timeout.
|
||||||
|
func waitCRDEstablished(ctx context.Context, apixClient *apix.Clientset, name string) {
|
||||||
|
By("Waiting for CRD " + name + " to be Established")
|
||||||
|
|
||||||
|
Eventually(func(g Gomega) {
|
||||||
|
// Short per-attempt timeout so a single Get can't hang the whole Eventually loop.
|
||||||
|
attemptCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
crd, err := apixClient.ApiextensionsV1().
|
||||||
|
CustomResourceDefinitions().
|
||||||
|
Get(attemptCtx, name, metav1.GetOptions{})
|
||||||
|
g.Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
established := false
|
||||||
|
for _, c := range crd.Status.Conditions {
|
||||||
|
if c.Type == apiextv1.Established && c.Status == apiextv1.ConditionTrue {
|
||||||
|
established = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.Expect(established).To(BeTrue(), "CRD %q is not Established yet", name)
|
||||||
|
}, defaults.E2ETimeout, defaults.E2EInterval).Should(Succeed())
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/1Password/onepassword-operator/pkg/testhelper/kube"
|
"github.com/1Password/onepassword-operator/pkg/testhelper/kube"
|
||||||
"github.com/1Password/onepassword-operator/pkg/testhelper/op"
|
"github.com/1Password/onepassword-operator/pkg/testhelper/op"
|
||||||
"github.com/1Password/onepassword-operator/pkg/testhelper/operator"
|
"github.com/1Password/onepassword-operator/pkg/testhelper/operator"
|
||||||
|
"github.com/1Password/onepassword-operator/pkg/testhelper/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -30,6 +31,9 @@ var _ = Describe("Onepassword Operator e2e", Ordered, func() {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
BeforeAll(func() {
|
BeforeAll(func() {
|
||||||
|
rootDir, err := system.GetProjectRoot()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
kubeClient = kube.NewKubeClient(&kube.Config{
|
kubeClient = kube.NewKubeClient(&kube.Config{
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
ManifestsDir: filepath.Join("manifests"),
|
ManifestsDir: filepath.Join("manifests"),
|
||||||
@@ -37,6 +41,9 @@ var _ = Describe("Onepassword Operator e2e", Ordered, func() {
|
|||||||
Timeout: defaults.E2ETimeout,
|
Timeout: defaults.E2ETimeout,
|
||||||
Interval: defaults.E2EInterval,
|
Interval: defaults.E2EInterval,
|
||||||
},
|
},
|
||||||
|
CRDs: []string{
|
||||||
|
filepath.Join(rootDir, "config", "crd", "bases", "onepassword.com_onepassworditems.yaml"),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
operator.BuildOperatorImage()
|
operator.BuildOperatorImage()
|
||||||
|
Reference in New Issue
Block a user