@@ -22,17 +22,20 @@ import (
2222 "fmt"
2323 "net/url"
2424 "os"
25- "os/exec"
2625 "path"
2726 goruntime "runtime"
2827 "sync"
2928 "time"
3029
3130 . "github.com/onsi/ginkgo/v2"
3231 . "github.com/onsi/gomega"
33- pkgerrors "github.com/pkg/errors"
3432 corev1 "k8s.io/api/core/v1"
33+ apierrors "k8s.io/apimachinery/pkg/api/errors"
34+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
35+ "k8s.io/apimachinery/pkg/labels"
3536 "k8s.io/apimachinery/pkg/runtime"
37+ "k8s.io/apimachinery/pkg/types"
38+ kerrors "k8s.io/apimachinery/pkg/util/errors"
3639 "k8s.io/apimachinery/pkg/util/wait"
3740 "k8s.io/client-go/kubernetes"
3841 "k8s.io/client-go/rest"
@@ -44,9 +47,9 @@ import (
4447
4548 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
4649 expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
47- testexec "sigs.k8s.io/cluster-api/test/framework/exec"
4850 "sigs.k8s.io/cluster-api/test/framework/internal/log"
4951 "sigs.k8s.io/cluster-api/test/infrastructure/container"
52+ "sigs.k8s.io/cluster-api/util/yaml"
5053)
5154
5255const (
@@ -89,8 +92,8 @@ type ClusterProxy interface {
8992 // GetLogCollector returns the machine log collector for the Kubernetes cluster.
9093 GetLogCollector () ClusterLogCollector
9194
92- // Apply to apply YAML to the Kubernetes cluster, `kubectl apply`.
93- Apply (ctx context.Context , resources []byte , args ... string ) error
95+ // CreateOrUpdate creates or updates objects using the clusterProxy client
96+ CreateOrUpdate (ctx context.Context , resources []byte , options ... CreateOrUpdateOption ) error
9497
9598 // GetWorkloadCluster returns a proxy to a workload cluster defined in the Kubernetes cluster.
9699 GetWorkloadCluster (ctx context.Context , namespace , name string , options ... Option ) ClusterProxy
@@ -103,6 +106,21 @@ type ClusterProxy interface {
103106 Dispose (context.Context )
104107}
105108
109+ // createOrUpdateConfig contains options for use with CreateOrUpdate.
110+ type createOrUpdateConfig struct {
111+ labelSelector labels.Selector
112+ }
113+
114+ // CreateOrUpdateOption is a configuration option supplied to CreateOrUpdate.
115+ type CreateOrUpdateOption func (* createOrUpdateConfig )
116+
117+ // WithLabelSelector allows definition of the LabelSelector to be used in CreateOrUpdate.
118+ func WithLabelSelector (labelSelector labels.Selector ) CreateOrUpdateOption {
119+ return func (c * createOrUpdateConfig ) {
120+ c .labelSelector = labelSelector
121+ }
122+ }
123+
106124// ClusterLogCollector defines an object that can collect logs from a machine.
107125type ClusterLogCollector interface {
108126 // CollectMachineLog collects log from a machine.
@@ -247,20 +265,52 @@ func (p *clusterProxy) GetCache(ctx context.Context) cache.Cache {
247265 return p .cache
248266}
249267
250- // Apply wraps `kubectl apply ...` and prints the output so we can see what gets applied to the cluster.
251- func (p * clusterProxy ) Apply (ctx context.Context , resources []byte , args ... string ) error {
252- Expect (ctx ).NotTo (BeNil (), "ctx is required for Apply" )
253- Expect (resources ).NotTo (BeNil (), "resources is required for Apply" )
254-
255- if err := testexec .KubectlApply (ctx , p .kubeconfigPath , resources , args ... ); err != nil {
256- var exitErr * exec.ExitError
257- if errors .As (err , & exitErr ) {
258- return pkgerrors .New (fmt .Sprintf ("%s: stderr: %s" , err .Error (), exitErr .Stderr ))
259- }
268+ // CreateOrUpdate creates or updates objects using the clusterProxy client.
269+ func (p * clusterProxy ) CreateOrUpdate (ctx context.Context , resources []byte , opts ... CreateOrUpdateOption ) error {
270+ Expect (ctx ).NotTo (BeNil (), "ctx is required for CreateOrUpdate" )
271+ Expect (resources ).NotTo (BeNil (), "resources is required for CreateOrUpdate" )
272+ labelSelector := labels .Everything ()
273+ config := & createOrUpdateConfig {}
274+ for _ , opt := range opts {
275+ opt (config )
276+ }
277+ if config .labelSelector != nil {
278+ labelSelector = config .labelSelector
279+ }
280+ objs , err := yaml .ToUnstructured (resources )
281+ if err != nil {
260282 return err
261283 }
262284
263- return nil
285+ existingObject := & unstructured.Unstructured {}
286+ var retErrs []error
287+ for _ , o := range objs {
288+ objectKey := types.NamespacedName {
289+ Name : o .GetName (),
290+ Namespace : o .GetNamespace (),
291+ }
292+ existingObject .SetAPIVersion (o .GetAPIVersion ())
293+ existingObject .SetKind (o .GetKind ())
294+ labels := labels .Set (o .GetLabels ())
295+ if labelSelector .Matches (labels ) {
296+ if err := p .GetClient ().Get (ctx , objectKey , existingObject ); err != nil {
297+ // Expected error -- if the object does not exist, create it
298+ if apierrors .IsNotFound (err ) {
299+ if err := p .GetClient ().Create (ctx , & o ); err != nil {
300+ retErrs = append (retErrs , err )
301+ }
302+ } else {
303+ retErrs = append (retErrs , err )
304+ }
305+ } else {
306+ o .SetResourceVersion (existingObject .GetResourceVersion ())
307+ if err := p .GetClient ().Update (ctx , & o ); err != nil {
308+ retErrs = append (retErrs , err )
309+ }
310+ }
311+ }
312+ }
313+ return kerrors .NewAggregate (retErrs )
264314}
265315
266316func (p * clusterProxy ) GetRESTConfig () * rest.Config {
0 commit comments