- );
-};
+// RESTClient returns a RESTClient that is used to communicate
+// with API server by this client implementation.
+func (c *FakeJobV1alpha1) RESTClient() rest.Interface {
+ var ret *rest.RESTClient
+ return ret
+}
diff --git a/pkg/client/clientset/versioned/typed/job.min.io/v1alpha1/fake/fake_miniojob.go b/pkg/client/clientset/versioned/typed/job.min.io/v1alpha1/fake/fake_miniojob.go
new file mode 100644
index 00000000000..d92f5fe647a
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/job.min.io/v1alpha1/fake/fake_miniojob.go
@@ -0,0 +1,189 @@
+// This file is part of MinIO Operator
+// Copyright (c) 2023 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package fake
+
+import (
+ "context"
+ json "encoding/json"
+ "fmt"
+
+ v1alpha1 "github.com/minio/operator/pkg/apis/job.min.io/v1alpha1"
+ jobminiov1alpha1 "github.com/minio/operator/pkg/client/applyconfiguration/job.min.io/v1alpha1"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ labels "k8s.io/apimachinery/pkg/labels"
+ types "k8s.io/apimachinery/pkg/types"
+ watch "k8s.io/apimachinery/pkg/watch"
+ testing "k8s.io/client-go/testing"
+)
+
+// FakeMinIOJobs implements MinIOJobInterface
+type FakeMinIOJobs struct {
+ Fake *FakeJobV1alpha1
+ ns string
+}
+
+var miniojobsResource = v1alpha1.SchemeGroupVersion.WithResource("miniojobs")
+
+var miniojobsKind = v1alpha1.SchemeGroupVersion.WithKind("MinIOJob")
+
+// Get takes name of the minIOJob, and returns the corresponding minIOJob object, and an error if there is any.
+func (c *FakeMinIOJobs) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.MinIOJob, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewGetAction(miniojobsResource, c.ns, name), &v1alpha1.MinIOJob{})
+
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.MinIOJob), err
+}
+
+// List takes label and field selectors, and returns the list of MinIOJobs that match those selectors.
+func (c *FakeMinIOJobs) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.MinIOJobList, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewListAction(miniojobsResource, miniojobsKind, c.ns, opts), &v1alpha1.MinIOJobList{})
+
+ if obj == nil {
+ return nil, err
+ }
+
+ label, _, _ := testing.ExtractFromListOptions(opts)
+ if label == nil {
+ label = labels.Everything()
+ }
+ list := &v1alpha1.MinIOJobList{ListMeta: obj.(*v1alpha1.MinIOJobList).ListMeta}
+ for _, item := range obj.(*v1alpha1.MinIOJobList).Items {
+ if label.Matches(labels.Set(item.Labels)) {
+ list.Items = append(list.Items, item)
+ }
+ }
+ return list, err
+}
+
+// Watch returns a watch.Interface that watches the requested minIOJobs.
+func (c *FakeMinIOJobs) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
+ return c.Fake.
+ InvokesWatch(testing.NewWatchAction(miniojobsResource, c.ns, opts))
+
+}
+
+// Create takes the representation of a minIOJob and creates it. Returns the server's representation of the minIOJob, and an error, if there is any.
+func (c *FakeMinIOJobs) Create(ctx context.Context, minIOJob *v1alpha1.MinIOJob, opts v1.CreateOptions) (result *v1alpha1.MinIOJob, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewCreateAction(miniojobsResource, c.ns, minIOJob), &v1alpha1.MinIOJob{})
+
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.MinIOJob), err
+}
+
+// Update takes the representation of a minIOJob and updates it. Returns the server's representation of the minIOJob, and an error, if there is any.
+func (c *FakeMinIOJobs) Update(ctx context.Context, minIOJob *v1alpha1.MinIOJob, opts v1.UpdateOptions) (result *v1alpha1.MinIOJob, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewUpdateAction(miniojobsResource, c.ns, minIOJob), &v1alpha1.MinIOJob{})
+
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.MinIOJob), err
+}
+
+// UpdateStatus was generated because the type contains a Status member.
+// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+func (c *FakeMinIOJobs) UpdateStatus(ctx context.Context, minIOJob *v1alpha1.MinIOJob, opts v1.UpdateOptions) (*v1alpha1.MinIOJob, error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewUpdateSubresourceAction(miniojobsResource, "status", c.ns, minIOJob), &v1alpha1.MinIOJob{})
+
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.MinIOJob), err
+}
+
+// Delete takes name of the minIOJob and deletes it. Returns an error if one occurs.
+func (c *FakeMinIOJobs) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
+ _, err := c.Fake.
+ Invokes(testing.NewDeleteActionWithOptions(miniojobsResource, c.ns, name, opts), &v1alpha1.MinIOJob{})
+
+ return err
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *FakeMinIOJobs) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
+ action := testing.NewDeleteCollectionAction(miniojobsResource, c.ns, listOpts)
+
+ _, err := c.Fake.Invokes(action, &v1alpha1.MinIOJobList{})
+ return err
+}
+
+// Patch applies the patch and returns the patched minIOJob.
+func (c *FakeMinIOJobs) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.MinIOJob, err error) {
+ obj, err := c.Fake.
+ Invokes(testing.NewPatchSubresourceAction(miniojobsResource, c.ns, name, pt, data, subresources...), &v1alpha1.MinIOJob{})
+
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.MinIOJob), err
+}
+
+// Apply takes the given apply declarative configuration, applies it and returns the applied minIOJob.
+func (c *FakeMinIOJobs) Apply(ctx context.Context, minIOJob *jobminiov1alpha1.MinIOJobApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.MinIOJob, err error) {
+ if minIOJob == nil {
+ return nil, fmt.Errorf("minIOJob provided to Apply must not be nil")
+ }
+ data, err := json.Marshal(minIOJob)
+ if err != nil {
+ return nil, err
+ }
+ name := minIOJob.Name
+ if name == nil {
+ return nil, fmt.Errorf("minIOJob.Name must be provided to Apply")
+ }
+ obj, err := c.Fake.
+ Invokes(testing.NewPatchSubresourceAction(miniojobsResource, c.ns, *name, types.ApplyPatchType, data), &v1alpha1.MinIOJob{})
+
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.MinIOJob), err
+}
+
+// ApplyStatus was generated because the type contains a Status member.
+// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().
+func (c *FakeMinIOJobs) ApplyStatus(ctx context.Context, minIOJob *jobminiov1alpha1.MinIOJobApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.MinIOJob, err error) {
+ if minIOJob == nil {
+ return nil, fmt.Errorf("minIOJob provided to Apply must not be nil")
+ }
+ data, err := json.Marshal(minIOJob)
+ if err != nil {
+ return nil, err
+ }
+ name := minIOJob.Name
+ if name == nil {
+ return nil, fmt.Errorf("minIOJob.Name must be provided to Apply")
+ }
+ obj, err := c.Fake.
+ Invokes(testing.NewPatchSubresourceAction(miniojobsResource, c.ns, *name, types.ApplyPatchType, data, "status"), &v1alpha1.MinIOJob{})
+
+ if obj == nil {
+ return nil, err
+ }
+ return obj.(*v1alpha1.MinIOJob), err
+}
diff --git a/web-app/src/screens/Console/Common/FormHr.tsx b/pkg/client/clientset/versioned/typed/job.min.io/v1alpha1/generated_expansion.go
similarity index 78%
rename from web-app/src/screens/Console/Common/FormHr.tsx
rename to pkg/client/clientset/versioned/typed/job.min.io/v1alpha1/generated_expansion.go
index 2205ef2be91..cb17df7446d 100644
--- a/web-app/src/screens/Console/Common/FormHr.tsx
+++ b/pkg/client/clientset/versioned/typed/job.min.io/v1alpha1/generated_expansion.go
@@ -14,14 +14,8 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-import styled from "@emotion/styled";
+// Code generated by client-gen. DO NOT EDIT.
-const FormHr = styled("hr")`
- border-top: 0;
- border-left: 0;
- border-right: 0;
- border-color: #999999;
- background-color: transparent;
-`;
+package v1alpha1
-export default FormHr;
+type MinIOJobExpansion interface{}
diff --git a/pkg/client/clientset/versioned/typed/job.min.io/v1alpha1/job.min.io_client.go b/pkg/client/clientset/versioned/typed/job.min.io/v1alpha1/job.min.io_client.go
new file mode 100644
index 00000000000..d5c5486d31f
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/job.min.io/v1alpha1/job.min.io_client.go
@@ -0,0 +1,107 @@
+// This file is part of MinIO Operator
+// Copyright (c) 2023 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ "net/http"
+
+ v1alpha1 "github.com/minio/operator/pkg/apis/job.min.io/v1alpha1"
+ "github.com/minio/operator/pkg/client/clientset/versioned/scheme"
+ rest "k8s.io/client-go/rest"
+)
+
+type JobV1alpha1Interface interface {
+ RESTClient() rest.Interface
+ MinIOJobsGetter
+}
+
+// JobV1alpha1Client is used to interact with features provided by the job.min.io group.
+type JobV1alpha1Client struct {
+ restClient rest.Interface
+}
+
+func (c *JobV1alpha1Client) MinIOJobs(namespace string) MinIOJobInterface {
+ return newMinIOJobs(c, namespace)
+}
+
+// NewForConfig creates a new JobV1alpha1Client for the given config.
+// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
+// where httpClient was generated with rest.HTTPClientFor(c).
+func NewForConfig(c *rest.Config) (*JobV1alpha1Client, error) {
+ config := *c
+ if err := setConfigDefaults(&config); err != nil {
+ return nil, err
+ }
+ httpClient, err := rest.HTTPClientFor(&config)
+ if err != nil {
+ return nil, err
+ }
+ return NewForConfigAndClient(&config, httpClient)
+}
+
+// NewForConfigAndClient creates a new JobV1alpha1Client for the given config and http client.
+// Note the http client provided takes precedence over the configured transport values.
+func NewForConfigAndClient(c *rest.Config, h *http.Client) (*JobV1alpha1Client, error) {
+ config := *c
+ if err := setConfigDefaults(&config); err != nil {
+ return nil, err
+ }
+ client, err := rest.RESTClientForConfigAndClient(&config, h)
+ if err != nil {
+ return nil, err
+ }
+ return &JobV1alpha1Client{client}, nil
+}
+
+// NewForConfigOrDie creates a new JobV1alpha1Client for the given config and
+// panics if there is an error in the config.
+func NewForConfigOrDie(c *rest.Config) *JobV1alpha1Client {
+ client, err := NewForConfig(c)
+ if err != nil {
+ panic(err)
+ }
+ return client
+}
+
+// New creates a new JobV1alpha1Client for the given RESTClient.
+func New(c rest.Interface) *JobV1alpha1Client {
+ return &JobV1alpha1Client{c}
+}
+
+func setConfigDefaults(config *rest.Config) error {
+ gv := v1alpha1.SchemeGroupVersion
+ config.GroupVersion = &gv
+ config.APIPath = "/apis"
+ config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
+
+ if config.UserAgent == "" {
+ config.UserAgent = rest.DefaultKubernetesUserAgent()
+ }
+
+ return nil
+}
+
+// RESTClient returns a RESTClient that is used to communicate
+// with API server by this client implementation.
+func (c *JobV1alpha1Client) RESTClient() rest.Interface {
+ if c == nil {
+ return nil
+ }
+ return c.restClient
+}
diff --git a/pkg/client/clientset/versioned/typed/job.min.io/v1alpha1/miniojob.go b/pkg/client/clientset/versioned/typed/job.min.io/v1alpha1/miniojob.go
new file mode 100644
index 00000000000..24cd4e53fc9
--- /dev/null
+++ b/pkg/client/clientset/versioned/typed/job.min.io/v1alpha1/miniojob.go
@@ -0,0 +1,256 @@
+// This file is part of MinIO Operator
+// Copyright (c) 2023 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ "context"
+ json "encoding/json"
+ "fmt"
+ "time"
+
+ v1alpha1 "github.com/minio/operator/pkg/apis/job.min.io/v1alpha1"
+ jobminiov1alpha1 "github.com/minio/operator/pkg/client/applyconfiguration/job.min.io/v1alpha1"
+ scheme "github.com/minio/operator/pkg/client/clientset/versioned/scheme"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ types "k8s.io/apimachinery/pkg/types"
+ watch "k8s.io/apimachinery/pkg/watch"
+ rest "k8s.io/client-go/rest"
+)
+
+// MinIOJobsGetter has a method to return a MinIOJobInterface.
+// A group's client should implement this interface.
+type MinIOJobsGetter interface {
+ MinIOJobs(namespace string) MinIOJobInterface
+}
+
+// MinIOJobInterface has methods to work with MinIOJob resources.
+type MinIOJobInterface interface {
+ Create(ctx context.Context, minIOJob *v1alpha1.MinIOJob, opts v1.CreateOptions) (*v1alpha1.MinIOJob, error)
+ Update(ctx context.Context, minIOJob *v1alpha1.MinIOJob, opts v1.UpdateOptions) (*v1alpha1.MinIOJob, error)
+ UpdateStatus(ctx context.Context, minIOJob *v1alpha1.MinIOJob, opts v1.UpdateOptions) (*v1alpha1.MinIOJob, error)
+ Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
+ DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
+ Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.MinIOJob, error)
+ List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.MinIOJobList, error)
+ Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
+ Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.MinIOJob, err error)
+ Apply(ctx context.Context, minIOJob *jobminiov1alpha1.MinIOJobApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.MinIOJob, err error)
+ ApplyStatus(ctx context.Context, minIOJob *jobminiov1alpha1.MinIOJobApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.MinIOJob, err error)
+ MinIOJobExpansion
+}
+
+// minIOJobs implements MinIOJobInterface
+type minIOJobs struct {
+ client rest.Interface
+ ns string
+}
+
+// newMinIOJobs returns a MinIOJobs
+func newMinIOJobs(c *JobV1alpha1Client, namespace string) *minIOJobs {
+ return &minIOJobs{
+ client: c.RESTClient(),
+ ns: namespace,
+ }
+}
+
+// Get takes name of the minIOJob, and returns the corresponding minIOJob object, and an error if there is any.
+func (c *minIOJobs) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.MinIOJob, err error) {
+ result = &v1alpha1.MinIOJob{}
+ err = c.client.Get().
+ Namespace(c.ns).
+ Resource("miniojobs").
+ Name(name).
+ VersionedParams(&options, scheme.ParameterCodec).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// List takes label and field selectors, and returns the list of MinIOJobs that match those selectors.
+func (c *minIOJobs) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.MinIOJobList, err error) {
+ var timeout time.Duration
+ if opts.TimeoutSeconds != nil {
+ timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+ }
+ result = &v1alpha1.MinIOJobList{}
+ err = c.client.Get().
+ Namespace(c.ns).
+ Resource("miniojobs").
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Timeout(timeout).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// Watch returns a watch.Interface that watches the requested minIOJobs.
+func (c *minIOJobs) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
+ var timeout time.Duration
+ if opts.TimeoutSeconds != nil {
+ timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
+ }
+ opts.Watch = true
+ return c.client.Get().
+ Namespace(c.ns).
+ Resource("miniojobs").
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Timeout(timeout).
+ Watch(ctx)
+}
+
+// Create takes the representation of a minIOJob and creates it. Returns the server's representation of the minIOJob, and an error, if there is any.
+func (c *minIOJobs) Create(ctx context.Context, minIOJob *v1alpha1.MinIOJob, opts v1.CreateOptions) (result *v1alpha1.MinIOJob, err error) {
+ result = &v1alpha1.MinIOJob{}
+ err = c.client.Post().
+ Namespace(c.ns).
+ Resource("miniojobs").
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Body(minIOJob).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// Update takes the representation of a minIOJob and updates it. Returns the server's representation of the minIOJob, and an error, if there is any.
+func (c *minIOJobs) Update(ctx context.Context, minIOJob *v1alpha1.MinIOJob, opts v1.UpdateOptions) (result *v1alpha1.MinIOJob, err error) {
+ result = &v1alpha1.MinIOJob{}
+ err = c.client.Put().
+ Namespace(c.ns).
+ Resource("miniojobs").
+ Name(minIOJob.Name).
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Body(minIOJob).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// UpdateStatus was generated because the type contains a Status member.
+// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+func (c *minIOJobs) UpdateStatus(ctx context.Context, minIOJob *v1alpha1.MinIOJob, opts v1.UpdateOptions) (result *v1alpha1.MinIOJob, err error) {
+ result = &v1alpha1.MinIOJob{}
+ err = c.client.Put().
+ Namespace(c.ns).
+ Resource("miniojobs").
+ Name(minIOJob.Name).
+ SubResource("status").
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Body(minIOJob).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// Delete takes name of the minIOJob and deletes it. Returns an error if one occurs.
+func (c *minIOJobs) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
+ return c.client.Delete().
+ Namespace(c.ns).
+ Resource("miniojobs").
+ Name(name).
+ Body(&opts).
+ Do(ctx).
+ Error()
+}
+
+// DeleteCollection deletes a collection of objects.
+func (c *minIOJobs) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
+ var timeout time.Duration
+ if listOpts.TimeoutSeconds != nil {
+ timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
+ }
+ return c.client.Delete().
+ Namespace(c.ns).
+ Resource("miniojobs").
+ VersionedParams(&listOpts, scheme.ParameterCodec).
+ Timeout(timeout).
+ Body(&opts).
+ Do(ctx).
+ Error()
+}
+
+// Patch applies the patch and returns the patched minIOJob.
+func (c *minIOJobs) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.MinIOJob, err error) {
+ result = &v1alpha1.MinIOJob{}
+ err = c.client.Patch(pt).
+ Namespace(c.ns).
+ Resource("miniojobs").
+ Name(name).
+ SubResource(subresources...).
+ VersionedParams(&opts, scheme.ParameterCodec).
+ Body(data).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// Apply takes the given apply declarative configuration, applies it and returns the applied minIOJob.
+func (c *minIOJobs) Apply(ctx context.Context, minIOJob *jobminiov1alpha1.MinIOJobApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.MinIOJob, err error) {
+ if minIOJob == nil {
+ return nil, fmt.Errorf("minIOJob provided to Apply must not be nil")
+ }
+ patchOpts := opts.ToPatchOptions()
+ data, err := json.Marshal(minIOJob)
+ if err != nil {
+ return nil, err
+ }
+ name := minIOJob.Name
+ if name == nil {
+ return nil, fmt.Errorf("minIOJob.Name must be provided to Apply")
+ }
+ result = &v1alpha1.MinIOJob{}
+ err = c.client.Patch(types.ApplyPatchType).
+ Namespace(c.ns).
+ Resource("miniojobs").
+ Name(*name).
+ VersionedParams(&patchOpts, scheme.ParameterCodec).
+ Body(data).
+ Do(ctx).
+ Into(result)
+ return
+}
+
+// ApplyStatus was generated because the type contains a Status member.
+// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().
+func (c *minIOJobs) ApplyStatus(ctx context.Context, minIOJob *jobminiov1alpha1.MinIOJobApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.MinIOJob, err error) {
+ if minIOJob == nil {
+ return nil, fmt.Errorf("minIOJob provided to Apply must not be nil")
+ }
+ patchOpts := opts.ToPatchOptions()
+ data, err := json.Marshal(minIOJob)
+ if err != nil {
+ return nil, err
+ }
+
+ name := minIOJob.Name
+ if name == nil {
+ return nil, fmt.Errorf("minIOJob.Name must be provided to Apply")
+ }
+
+ result = &v1alpha1.MinIOJob{}
+ err = c.client.Patch(types.ApplyPatchType).
+ Namespace(c.ns).
+ Resource("miniojobs").
+ Name(*name).
+ SubResource("status").
+ VersionedParams(&patchOpts, scheme.ParameterCodec).
+ Body(data).
+ Do(ctx).
+ Into(result)
+ return
+}
diff --git a/pkg/client/clientset/versioned/typed/minio.min.io/v2/doc.go b/pkg/client/clientset/versioned/typed/minio.min.io/v2/doc.go
index 0dcc25c9d6c..3aba29f3aca 100644
--- a/pkg/client/clientset/versioned/typed/minio.min.io/v2/doc.go
+++ b/pkg/client/clientset/versioned/typed/minio.min.io/v2/doc.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/clientset/versioned/typed/minio.min.io/v2/fake/doc.go b/pkg/client/clientset/versioned/typed/minio.min.io/v2/fake/doc.go
index af8d23ae280..85a59559240 100644
--- a/pkg/client/clientset/versioned/typed/minio.min.io/v2/fake/doc.go
+++ b/pkg/client/clientset/versioned/typed/minio.min.io/v2/fake/doc.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/clientset/versioned/typed/minio.min.io/v2/fake/fake_minio.min.io_client.go b/pkg/client/clientset/versioned/typed/minio.min.io/v2/fake/fake_minio.min.io_client.go
index 8ab1b8018f0..ab7b435cb56 100644
--- a/pkg/client/clientset/versioned/typed/minio.min.io/v2/fake/fake_minio.min.io_client.go
+++ b/pkg/client/clientset/versioned/typed/minio.min.io/v2/fake/fake_minio.min.io_client.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/clientset/versioned/typed/minio.min.io/v2/fake/fake_tenant.go b/pkg/client/clientset/versioned/typed/minio.min.io/v2/fake/fake_tenant.go
index bf97ed26612..f326006187a 100644
--- a/pkg/client/clientset/versioned/typed/minio.min.io/v2/fake/fake_tenant.go
+++ b/pkg/client/clientset/versioned/typed/minio.min.io/v2/fake/fake_tenant.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/clientset/versioned/typed/minio.min.io/v2/generated_expansion.go b/pkg/client/clientset/versioned/typed/minio.min.io/v2/generated_expansion.go
index 3e69057bb38..172fdf814cf 100644
--- a/pkg/client/clientset/versioned/typed/minio.min.io/v2/generated_expansion.go
+++ b/pkg/client/clientset/versioned/typed/minio.min.io/v2/generated_expansion.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/clientset/versioned/typed/minio.min.io/v2/minio.min.io_client.go b/pkg/client/clientset/versioned/typed/minio.min.io/v2/minio.min.io_client.go
index d776d583b96..ed3adc2fb63 100644
--- a/pkg/client/clientset/versioned/typed/minio.min.io/v2/minio.min.io_client.go
+++ b/pkg/client/clientset/versioned/typed/minio.min.io/v2/minio.min.io_client.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/clientset/versioned/typed/minio.min.io/v2/tenant.go b/pkg/client/clientset/versioned/typed/minio.min.io/v2/tenant.go
index b26dd5906f4..c29edd84d55 100644
--- a/pkg/client/clientset/versioned/typed/minio.min.io/v2/tenant.go
+++ b/pkg/client/clientset/versioned/typed/minio.min.io/v2/tenant.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/doc.go b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/doc.go
index 8d796276602..3b4480b44f7 100644
--- a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/doc.go
+++ b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/doc.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/doc.go b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/doc.go
index af8d23ae280..85a59559240 100644
--- a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/doc.go
+++ b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/doc.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/fake_policybinding.go b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/fake_policybinding.go
index 8df61d2283f..8e1d8af1951 100644
--- a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/fake_policybinding.go
+++ b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/fake_policybinding.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/fake_sts.min.io_client.go b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/fake_sts.min.io_client.go
index ecbfb218923..6dfbf04e746 100644
--- a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/fake_sts.min.io_client.go
+++ b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/fake_sts.min.io_client.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/generated_expansion.go
index 8afaf86213e..7b1409ae353 100644
--- a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/generated_expansion.go
+++ b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/generated_expansion.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/policybinding.go b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/policybinding.go
index 2cd673123b5..f326bf16f31 100644
--- a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/policybinding.go
+++ b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/policybinding.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/sts.min.io_client.go b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/sts.min.io_client.go
index 1b3adb7905b..413dde10f54 100644
--- a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/sts.min.io_client.go
+++ b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/sts.min.io_client.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go
index 2263a2b8c6b..f099bdc7f9e 100644
--- a/pkg/client/informers/externalversions/factory.go
+++ b/pkg/client/informers/externalversions/factory.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -25,6 +25,7 @@ import (
versioned "github.com/minio/operator/pkg/client/clientset/versioned"
internalinterfaces "github.com/minio/operator/pkg/client/informers/externalversions/internalinterfaces"
+ jobminio "github.com/minio/operator/pkg/client/informers/externalversions/job.min.io"
miniominio "github.com/minio/operator/pkg/client/informers/externalversions/minio.min.io"
stsminio "github.com/minio/operator/pkg/client/informers/externalversions/sts.min.io"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -43,6 +44,7 @@ type sharedInformerFactory struct {
lock sync.Mutex
defaultResync time.Duration
customResync map[reflect.Type]time.Duration
+ transform cache.TransformFunc
informers map[reflect.Type]cache.SharedIndexInformer
// startedInformers is used for tracking which informers have been started.
@@ -81,6 +83,14 @@ func WithNamespace(namespace string) SharedInformerOption {
}
}
+// WithTransform sets a transform on all informers.
+func WithTransform(transform cache.TransformFunc) SharedInformerOption {
+ return func(factory *sharedInformerFactory) *sharedInformerFactory {
+ factory.transform = transform
+ return factory
+ }
+}
+
// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces.
func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory {
return NewSharedInformerFactoryWithOptions(client, defaultResync)
@@ -167,7 +177,7 @@ func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[ref
return res
}
-// InternalInformerFor returns the SharedIndexInformer for obj using an internal
+// InformerFor returns the SharedIndexInformer for obj using an internal
// client.
func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {
f.lock.Lock()
@@ -185,6 +195,7 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal
}
informer = newFunc(f.client, resyncPeriod)
+ informer.SetTransform(f.transform)
f.informers[informerType] = informer
return informer
@@ -240,14 +251,19 @@ type SharedInformerFactory interface {
// ForResource gives generic access to a shared informer of the matching type.
ForResource(resource schema.GroupVersionResource) (GenericInformer, error)
- // InternalInformerFor returns the SharedIndexInformer for obj using an internal
+ // InformerFor returns the SharedIndexInformer for obj using an internal
// client.
InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer
+ Job() jobminio.Interface
Minio() miniominio.Interface
Sts() stsminio.Interface
}
+func (f *sharedInformerFactory) Job() jobminio.Interface {
+ return jobminio.New(f, f.namespace, f.tweakListOptions)
+}
+
func (f *sharedInformerFactory) Minio() miniominio.Interface {
return miniominio.New(f, f.namespace, f.tweakListOptions)
}
diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go
index 1c93ddc9ea6..6ef90962506 100644
--- a/pkg/client/informers/externalversions/generic.go
+++ b/pkg/client/informers/externalversions/generic.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -21,8 +21,9 @@ package externalversions
import (
"fmt"
+ v1alpha1 "github.com/minio/operator/pkg/apis/job.min.io/v1alpha1"
v2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
- v1alpha1 "github.com/minio/operator/pkg/apis/sts.min.io/v1alpha1"
+ stsminiov1alpha1 "github.com/minio/operator/pkg/apis/sts.min.io/v1alpha1"
schema "k8s.io/apimachinery/pkg/runtime/schema"
cache "k8s.io/client-go/tools/cache"
)
@@ -53,12 +54,16 @@ func (f *genericInformer) Lister() cache.GenericLister {
// TODO extend this to unknown resources with a client pool
func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
switch resource {
- // Group=minio.min.io, Version=v2
+ // Group=job.min.io, Version=v1alpha1
+ case v1alpha1.SchemeGroupVersion.WithResource("miniojobs"):
+ return &genericInformer{resource: resource.GroupResource(), informer: f.Job().V1alpha1().MinIOJobs().Informer()}, nil
+
+ // Group=minio.min.io, Version=v2
case v2.SchemeGroupVersion.WithResource("tenants"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Minio().V2().Tenants().Informer()}, nil
// Group=sts.min.io, Version=v1alpha1
- case v1alpha1.SchemeGroupVersion.WithResource("policybindings"):
+ case stsminiov1alpha1.SchemeGroupVersion.WithResource("policybindings"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Sts().V1alpha1().PolicyBindings().Informer()}, nil
}
diff --git a/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go
index c66f9a8fdae..2486e2ceba0 100644
--- a/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go
+++ b/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/informers/externalversions/job.min.io/interface.go b/pkg/client/informers/externalversions/job.min.io/interface.go
new file mode 100644
index 00000000000..cc73f45577e
--- /dev/null
+++ b/pkg/client/informers/externalversions/job.min.io/interface.go
@@ -0,0 +1,46 @@
+// This file is part of MinIO Operator
+// Copyright (c) 2023 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package job
+
+import (
+ internalinterfaces "github.com/minio/operator/pkg/client/informers/externalversions/internalinterfaces"
+ v1alpha1 "github.com/minio/operator/pkg/client/informers/externalversions/job.min.io/v1alpha1"
+)
+
+// Interface provides access to each of this group's versions.
+type Interface interface {
+ // V1alpha1 provides access to shared informers for resources in V1alpha1.
+ V1alpha1() v1alpha1.Interface
+}
+
+type group struct {
+ factory internalinterfaces.SharedInformerFactory
+ namespace string
+ tweakListOptions internalinterfaces.TweakListOptionsFunc
+}
+
+// New returns a new Interface.
+func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
+ return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
+}
+
+// V1alpha1 returns a new v1alpha1.Interface.
+func (g *group) V1alpha1() v1alpha1.Interface {
+ return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)
+}
diff --git a/pkg/client/informers/externalversions/job.min.io/v1alpha1/interface.go b/pkg/client/informers/externalversions/job.min.io/v1alpha1/interface.go
new file mode 100644
index 00000000000..a42cf61dd7d
--- /dev/null
+++ b/pkg/client/informers/externalversions/job.min.io/v1alpha1/interface.go
@@ -0,0 +1,45 @@
+// This file is part of MinIO Operator
+// Copyright (c) 2023 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ internalinterfaces "github.com/minio/operator/pkg/client/informers/externalversions/internalinterfaces"
+)
+
+// Interface provides access to all the informers in this group version.
+type Interface interface {
+ // MinIOJobs returns a MinIOJobInformer.
+ MinIOJobs() MinIOJobInformer
+}
+
+type version struct {
+ factory internalinterfaces.SharedInformerFactory
+ namespace string
+ tweakListOptions internalinterfaces.TweakListOptionsFunc
+}
+
+// New returns a new Interface.
+func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
+ return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
+}
+
+// MinIOJobs returns a MinIOJobInformer.
+func (v *version) MinIOJobs() MinIOJobInformer {
+ return &minIOJobInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
+}
diff --git a/pkg/client/informers/externalversions/job.min.io/v1alpha1/miniojob.go b/pkg/client/informers/externalversions/job.min.io/v1alpha1/miniojob.go
new file mode 100644
index 00000000000..fcd6afb26dc
--- /dev/null
+++ b/pkg/client/informers/externalversions/job.min.io/v1alpha1/miniojob.go
@@ -0,0 +1,90 @@
+// This file is part of MinIO Operator
+// Copyright (c) 2023 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ "context"
+ time "time"
+
+ jobminiov1alpha1 "github.com/minio/operator/pkg/apis/job.min.io/v1alpha1"
+ versioned "github.com/minio/operator/pkg/client/clientset/versioned"
+ internalinterfaces "github.com/minio/operator/pkg/client/informers/externalversions/internalinterfaces"
+ v1alpha1 "github.com/minio/operator/pkg/client/listers/job.min.io/v1alpha1"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ runtime "k8s.io/apimachinery/pkg/runtime"
+ watch "k8s.io/apimachinery/pkg/watch"
+ cache "k8s.io/client-go/tools/cache"
+)
+
+// MinIOJobInformer provides access to a shared informer and lister for
+// MinIOJobs.
+type MinIOJobInformer interface {
+ Informer() cache.SharedIndexInformer
+ Lister() v1alpha1.MinIOJobLister
+}
+
+type minIOJobInformer struct {
+ factory internalinterfaces.SharedInformerFactory
+ tweakListOptions internalinterfaces.TweakListOptionsFunc
+ namespace string
+}
+
+// NewMinIOJobInformer constructs a new informer for MinIOJob type.
+// Always prefer using an informer factory to get a shared informer instead of getting an independent
+// one. This reduces memory footprint and number of connections to the server.
+func NewMinIOJobInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+ return NewFilteredMinIOJobInformer(client, namespace, resyncPeriod, indexers, nil)
+}
+
+// NewFilteredMinIOJobInformer constructs a new informer for MinIOJob type.
+// Always prefer using an informer factory to get a shared informer instead of getting an independent
+// one. This reduces memory footprint and number of connections to the server.
+func NewFilteredMinIOJobInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
+ return cache.NewSharedIndexInformer(
+ &cache.ListWatch{
+ ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.JobV1alpha1().MinIOJobs(namespace).List(context.TODO(), options)
+ },
+ WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.JobV1alpha1().MinIOJobs(namespace).Watch(context.TODO(), options)
+ },
+ },
+ &jobminiov1alpha1.MinIOJob{},
+ resyncPeriod,
+ indexers,
+ )
+}
+
+func (f *minIOJobInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
+ return NewFilteredMinIOJobInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
+}
+
+func (f *minIOJobInformer) Informer() cache.SharedIndexInformer {
+ return f.factory.InformerFor(&jobminiov1alpha1.MinIOJob{}, f.defaultInformer)
+}
+
+func (f *minIOJobInformer) Lister() v1alpha1.MinIOJobLister {
+ return v1alpha1.NewMinIOJobLister(f.Informer().GetIndexer())
+}
diff --git a/pkg/client/informers/externalversions/minio.min.io/interface.go b/pkg/client/informers/externalversions/minio.min.io/interface.go
index 0d3c5e09e7f..d4ab2c43146 100644
--- a/pkg/client/informers/externalversions/minio.min.io/interface.go
+++ b/pkg/client/informers/externalversions/minio.min.io/interface.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/informers/externalversions/minio.min.io/v2/interface.go b/pkg/client/informers/externalversions/minio.min.io/v2/interface.go
index b3ca3e44f35..95c51612cd9 100644
--- a/pkg/client/informers/externalversions/minio.min.io/v2/interface.go
+++ b/pkg/client/informers/externalversions/minio.min.io/v2/interface.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/informers/externalversions/minio.min.io/v2/tenant.go b/pkg/client/informers/externalversions/minio.min.io/v2/tenant.go
index 491c74b6f89..7294aedaf0d 100644
--- a/pkg/client/informers/externalversions/minio.min.io/v2/tenant.go
+++ b/pkg/client/informers/externalversions/minio.min.io/v2/tenant.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/informers/externalversions/sts.min.io/interface.go b/pkg/client/informers/externalversions/sts.min.io/interface.go
index ffa5a13b17f..b0c331dcafe 100644
--- a/pkg/client/informers/externalversions/sts.min.io/interface.go
+++ b/pkg/client/informers/externalversions/sts.min.io/interface.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/informers/externalversions/sts.min.io/v1alpha1/interface.go b/pkg/client/informers/externalversions/sts.min.io/v1alpha1/interface.go
index 9d5eba602fe..c74e9874ed5 100644
--- a/pkg/client/informers/externalversions/sts.min.io/v1alpha1/interface.go
+++ b/pkg/client/informers/externalversions/sts.min.io/v1alpha1/interface.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/informers/externalversions/sts.min.io/v1alpha1/policybinding.go b/pkg/client/informers/externalversions/sts.min.io/v1alpha1/policybinding.go
index feaa08ebe3f..98ac7245c28 100644
--- a/pkg/client/informers/externalversions/sts.min.io/v1alpha1/policybinding.go
+++ b/pkg/client/informers/externalversions/sts.min.io/v1alpha1/policybinding.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/web-app/src/screens/Console/Common/SectionH1.tsx b/pkg/client/listers/job.min.io/v1alpha1/expansion_generated.go
similarity index 65%
rename from web-app/src/screens/Console/Common/SectionH1.tsx
rename to pkg/client/listers/job.min.io/v1alpha1/expansion_generated.go
index eacf3f284eb..339fc4bb42d 100644
--- a/web-app/src/screens/Console/Common/SectionH1.tsx
+++ b/pkg/client/listers/job.min.io/v1alpha1/expansion_generated.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2022 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -14,18 +14,14 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-import React from "react";
+// Code generated by lister-gen. DO NOT EDIT.
-type Props = {
- children: string;
-};
+package v1alpha1
-const SectionH1: React.FC = ({ children }) => {
- return (
-
- {children}
-
- );
-};
+// MinIOJobListerExpansion allows custom methods to be added to
+// MinIOJobLister.
+type MinIOJobListerExpansion interface{}
-export default SectionH1;
+// MinIOJobNamespaceListerExpansion allows custom methods to be added to
+// MinIOJobNamespaceLister.
+type MinIOJobNamespaceListerExpansion interface{}
diff --git a/pkg/client/listers/job.min.io/v1alpha1/miniojob.go b/pkg/client/listers/job.min.io/v1alpha1/miniojob.go
new file mode 100644
index 00000000000..468dec7847a
--- /dev/null
+++ b/pkg/client/listers/job.min.io/v1alpha1/miniojob.go
@@ -0,0 +1,99 @@
+// This file is part of MinIO Operator
+// Copyright (c) 2023 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+// Code generated by lister-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ v1alpha1 "github.com/minio/operator/pkg/apis/job.min.io/v1alpha1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/labels"
+ "k8s.io/client-go/tools/cache"
+)
+
+// MinIOJobLister helps list MinIOJobs.
+// All objects returned here must be treated as read-only.
+type MinIOJobLister interface {
+ // List lists all MinIOJobs in the indexer.
+ // Objects returned here must be treated as read-only.
+ List(selector labels.Selector) (ret []*v1alpha1.MinIOJob, err error)
+ // MinIOJobs returns an object that can list and get MinIOJobs.
+ MinIOJobs(namespace string) MinIOJobNamespaceLister
+ MinIOJobListerExpansion
+}
+
+// minIOJobLister implements the MinIOJobLister interface.
+type minIOJobLister struct {
+ indexer cache.Indexer
+}
+
+// NewMinIOJobLister returns a new MinIOJobLister.
+func NewMinIOJobLister(indexer cache.Indexer) MinIOJobLister {
+ return &minIOJobLister{indexer: indexer}
+}
+
+// List lists all MinIOJobs in the indexer.
+func (s *minIOJobLister) List(selector labels.Selector) (ret []*v1alpha1.MinIOJob, err error) {
+ err = cache.ListAll(s.indexer, selector, func(m interface{}) {
+ ret = append(ret, m.(*v1alpha1.MinIOJob))
+ })
+ return ret, err
+}
+
+// MinIOJobs returns an object that can list and get MinIOJobs.
+func (s *minIOJobLister) MinIOJobs(namespace string) MinIOJobNamespaceLister {
+ return minIOJobNamespaceLister{indexer: s.indexer, namespace: namespace}
+}
+
+// MinIOJobNamespaceLister helps list and get MinIOJobs.
+// All objects returned here must be treated as read-only.
+type MinIOJobNamespaceLister interface {
+ // List lists all MinIOJobs in the indexer for a given namespace.
+ // Objects returned here must be treated as read-only.
+ List(selector labels.Selector) (ret []*v1alpha1.MinIOJob, err error)
+ // Get retrieves the MinIOJob from the indexer for a given namespace and name.
+ // Objects returned here must be treated as read-only.
+ Get(name string) (*v1alpha1.MinIOJob, error)
+ MinIOJobNamespaceListerExpansion
+}
+
+// minIOJobNamespaceLister implements the MinIOJobNamespaceLister
+// interface.
+type minIOJobNamespaceLister struct {
+ indexer cache.Indexer
+ namespace string
+}
+
+// List lists all MinIOJobs in the indexer for a given namespace.
+func (s minIOJobNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.MinIOJob, err error) {
+ err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
+ ret = append(ret, m.(*v1alpha1.MinIOJob))
+ })
+ return ret, err
+}
+
+// Get retrieves the MinIOJob from the indexer for a given namespace and name.
+func (s minIOJobNamespaceLister) Get(name string) (*v1alpha1.MinIOJob, error) {
+ obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
+ if err != nil {
+ return nil, err
+ }
+ if !exists {
+ return nil, errors.NewNotFound(v1alpha1.Resource("miniojob"), name)
+ }
+ return obj.(*v1alpha1.MinIOJob), nil
+}
diff --git a/pkg/client/listers/minio.min.io/v2/expansion_generated.go b/pkg/client/listers/minio.min.io/v2/expansion_generated.go
index 366706bf1a8..ba5ab1167e8 100644
--- a/pkg/client/listers/minio.min.io/v2/expansion_generated.go
+++ b/pkg/client/listers/minio.min.io/v2/expansion_generated.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/listers/minio.min.io/v2/tenant.go b/pkg/client/listers/minio.min.io/v2/tenant.go
index b4c9d40dd65..69c79903fd4 100644
--- a/pkg/client/listers/minio.min.io/v2/tenant.go
+++ b/pkg/client/listers/minio.min.io/v2/tenant.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/listers/sts.min.io/v1alpha1/expansion_generated.go b/pkg/client/listers/sts.min.io/v1alpha1/expansion_generated.go
index d6a4a571087..3e486ef14c4 100644
--- a/pkg/client/listers/sts.min.io/v1alpha1/expansion_generated.go
+++ b/pkg/client/listers/sts.min.io/v1alpha1/expansion_generated.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/client/listers/sts.min.io/v1alpha1/policybinding.go b/pkg/client/listers/sts.min.io/v1alpha1/policybinding.go
index 4f1384257d5..934dc42cd2b 100644
--- a/pkg/client/listers/sts.min.io/v1alpha1/policybinding.go
+++ b/pkg/client/listers/sts.min.io/v1alpha1/policybinding.go
@@ -1,5 +1,5 @@
// This file is part of MinIO Operator
-// Copyright (c) 2021 MinIO, Inc.
+// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/pkg/controller/artifacts.go b/pkg/controller/artifacts.go
index f5e8ed8ecb5..1058d15ef95 100644
--- a/pkg/controller/artifacts.go
+++ b/pkg/controller/artifacts.go
@@ -105,7 +105,8 @@ func (c *Controller) fetchArtifacts(tenant *miniov2.Tenant) (latest string, err
return latest, err
}
- keychain := authn.DefaultKeychain
+ var keychain authn.Keychain
+ keychain = authn.DefaultKeychain
// if the tenant has imagePullSecret use that for pulling the image, but if we fail to extract the secret or we
// can't find the expected registry in the secret we will continue with the default keychain. This is because the
diff --git a/pkg/controller/certificates/csr.go b/pkg/controller/certificates/csr.go
index c12c1dfa651..ec39f6a1fdc 100644
--- a/pkg/controller/certificates/csr.go
+++ b/pkg/controller/certificates/csr.go
@@ -61,11 +61,7 @@ var (
func getDefaultCsrSignerName() string {
defaultCsrSignerNameOnce.Do(func() {
- if os.Getenv(CSRSignerName) != "" {
- defaultCsrSignerName = os.Getenv(CSRSignerName)
- return
- }
- defaultCsrSignerName = certificatesV1.KubeletServingSignerName
+ defaultCsrSignerName = os.Getenv(CSRSignerName)
})
return defaultCsrSignerName
}
@@ -108,8 +104,10 @@ func GetCertificatesAPIVersion(clientSet kubernetes.Interface) CSRVersion {
// GetCSRSignerName returns the signer to be used
func GetCSRSignerName(clientSet kubernetes.Interface) string {
csrSignerNameOnce.Do(func() {
- // At the moment we will use kubernetes.io/kubelet-serving as the default
csrSignerName = getDefaultCsrSignerName()
+ if csrSignerName != "" {
+ return
+ }
// only for csr api v1 we will try to detect if we are running inside an EKS cluster and switch to AWS's way to
// get certificates using their CSRSignerName https://docs.aws.amazon.com/eks/latest/userguide/cert-signing.html
if GetCertificatesAPIVersion(clientSet) == CSRV1 {
@@ -136,6 +134,10 @@ func GetCSRSignerName(clientSet kubernetes.Interface) string {
}
}
}
+ if csrSignerName == "" {
+ // At the moment we will use kubernetes.io/kubelet-serving as the default
+ csrSignerName = certificatesV1.KubeletServingSignerName
+ }
})
return csrSignerName
}
diff --git a/pkg/controller/console.go b/pkg/controller/console.go
index fe65950ad55..8c1b4d99621 100644
--- a/pkg/controller/console.go
+++ b/pkg/controller/console.go
@@ -24,7 +24,6 @@ import (
"github.com/minio/operator/pkg/resources/services"
"github.com/minio/pkg/env"
corev1 "k8s.io/api/core/v1"
- v1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
@@ -57,7 +56,7 @@ func (c *Controller) checkConsoleSvc(ctx context.Context, tenant *miniov2.Tenant
if err != nil {
return err
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "SvcCreated", "Console Service Created")
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "SvcCreated", "Console Service Created")
} else {
return err
}
@@ -81,9 +80,9 @@ func (c *Controller) checkConsoleSvc(ctx context.Context, tenant *miniov2.Tenant
// Only when ExposeServices is set an explicit value we do modifications to the service type
if tenant.Spec.ExposeServices != nil {
if tenant.Spec.ExposeServices.Console {
- svc.Spec.Type = v1.ServiceTypeLoadBalancer
+ svc.Spec.Type = corev1.ServiceTypeLoadBalancer
} else {
- svc.Spec.Type = v1.ServiceTypeClusterIP
+ svc.Spec.Type = corev1.ServiceTypeClusterIP
}
}
@@ -94,7 +93,7 @@ func (c *Controller) checkConsoleSvc(ctx context.Context, tenant *miniov2.Tenant
if err != nil {
return err
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "Updated", "Console Service Updated")
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "Updated", "Console Service Updated")
}
return err
}
diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go
index 41a49cce345..40f92b2379f 100644
--- a/pkg/controller/controller.go
+++ b/pkg/controller/controller.go
@@ -23,6 +23,8 @@ import (
"syscall"
"time"
+ "github.com/minio/pkg/env"
+
"github.com/minio/operator/pkg"
"k8s.io/client-go/tools/clientcmd"
@@ -32,11 +34,15 @@ import (
"k8s.io/klog/v2"
+ "github.com/minio/operator/pkg/apis/job.min.io/v1alpha1"
+ v2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
+ stsv1alpha1 "github.com/minio/operator/pkg/apis/sts.min.io/v1alpha1"
clientset "github.com/minio/operator/pkg/client/clientset/versioned"
informers "github.com/minio/operator/pkg/client/informers/externalversions"
promclientset "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned"
kubeinformers "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -68,6 +74,9 @@ func init() {
// StartOperator starts the MinIO Operator controller
func StartOperator(kubeconfig string) {
+ _ = v2.AddToScheme(scheme.Scheme)
+ _ = v1alpha1.AddToScheme(scheme.Scheme)
+ _ = stsv1alpha1.AddToScheme(scheme.Scheme)
klog.Info("Starting MinIO Operator")
// set up signals, so we handle the first shutdown signal gracefully
stopCh := setupSignalHandler()
@@ -79,8 +88,21 @@ func StartOperator(kubeconfig string) {
return
}
- // Look for incluster config by default
- cfg, err := rest.InClusterConfig()
+ var cfg *rest.Config
+ var err error
+
+ if token := env.Get("DEV_MODE", ""); token == "on" {
+ klog.Info("DEV_MODE present, running dev mode")
+ cfg = &rest.Config{
+ Host: "http://localhost:8001",
+ TLSClientConfig: rest.TLSClientConfig{Insecure: true},
+ APIPath: "/",
+ BearerToken: "",
+ }
+ } else {
+ // Look for incluster config by default
+ cfg, err = rest.InClusterConfig()
+ }
// If config is passed as a flag use that instead
if kubeconfig != "" {
cfg, err = clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
@@ -146,6 +168,8 @@ func StartOperator(kubeconfig string) {
kubeInformerFactory.Core().V1().Services(),
hostsTemplate,
pkg.Version,
+ minioInformerFactory.Job().V1alpha1().MinIOJobs(),
+ kubeInformerFactory.Batch().V1().Jobs(),
)
go kubeInformerFactory.Start(stopCh)
diff --git a/pkg/controller/csr.go b/pkg/controller/csr.go
index 82a0be73a5a..92c2776afc5 100644
--- a/pkg/controller/csr.go
+++ b/pkg/controller/csr.go
@@ -41,7 +41,6 @@ import (
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
@@ -80,11 +79,11 @@ func (c *Controller) createCertificateSigningRequest(ctx context.Context, labels
certificatesV1.UsageServerAuth,
}
kubeCSR := &certificatesV1.CertificateSigningRequest{
- TypeMeta: v1.TypeMeta{
+ TypeMeta: metav1.TypeMeta{
APIVersion: "certificates.k8s.io/v1",
Kind: "CertificateSigningRequest",
},
- ObjectMeta: v1.ObjectMeta{
+ ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: labels,
Namespace: namespace,
@@ -129,11 +128,11 @@ func (c *Controller) createCertificateSigningRequest(ctx context.Context, labels
certificatesV1beta1.UsageClientAuth,
}
kubeCSR := &certificatesV1beta1.CertificateSigningRequest{
- TypeMeta: v1.TypeMeta{
+ TypeMeta: metav1.TypeMeta{
APIVersion: "certificates.k8s.io/v1beta1",
Kind: "CertificateSigningRequest",
},
- ObjectMeta: v1.ObjectMeta{
+ ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: labels,
Namespace: namespace,
@@ -199,7 +198,7 @@ func (c *Controller) fetchCertificate(ctx context.Context, csrName string) ([]by
case <-tick.C:
if certificates.GetCertificatesAPIVersion(c.kubeClientSet) == certificates.CSRV1 {
- r, err := c.kubeClientSet.CertificatesV1().CertificateSigningRequests().Get(ctx, csrName, v1.GetOptions{})
+ r, err := c.kubeClientSet.CertificatesV1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{})
if err != nil {
klog.Errorf("Unexpected error during certificate fetching of csr/%s V1: %s", csrName, err)
return nil, err
@@ -216,7 +215,7 @@ func (c *Controller) fetchCertificate(ctx context.Context, csrName string) ([]by
}
}
} else {
- r, err := c.kubeClientSet.CertificatesV1beta1().CertificateSigningRequests().Get(ctx, csrName, v1.GetOptions{})
+ r, err := c.kubeClientSet.CertificatesV1beta1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{})
if err != nil {
klog.Errorf("Unexpected error during certificate fetching of csr/%s V1beta1: %s", csrName, err)
return nil, err
diff --git a/pkg/controller/custom.go b/pkg/controller/custom.go
index ec8e13a4754..38c523b7040 100644
--- a/pkg/controller/custom.go
+++ b/pkg/controller/custom.go
@@ -107,16 +107,16 @@ func (c *Controller) getCustomCertificates(ctx context.Context, tenant *miniov2.
expiresInHuman := fmt.Sprintf("%v days, %v hours, %v minutes, %v seconds", expiresInDays, expiresInHours, expiresInMinutes, expiresInSeconds)
if expiresInDays >= 10 && expiresInDays < 30 {
- c.RegisterEvent(ctx, tenant, corev1.EventTypeWarning, "CertificateExpiring", fmt.Sprintf("%s certificate '%s' is expiring in %d days", certType, secret.Name, expiresInDays))
+ c.recorder.Event(tenant, corev1.EventTypeWarning, "CertificateExpiring", fmt.Sprintf("%s certificate '%s' is expiring in %d days", certType, secret.Name, expiresInDays))
}
if expiresInDays > 0 && expiresInDays < 10 {
- c.RegisterEvent(ctx, tenant, corev1.EventTypeWarning, "CertificateExpiryImminent", fmt.Sprintf("%s certificate '%s' is expiring in %d days", certType, secret.Name, expiresInDays))
+ c.recorder.Event(tenant, corev1.EventTypeWarning, "CertificateExpiryImminent", fmt.Sprintf("%s certificate '%s' is expiring in %d days", certType, secret.Name, expiresInDays))
}
if expiresInDays > 0 && expiresInDays < 1 {
expiresInHuman = fmt.Sprintf("%v hours, %v minutes, and %v seconds", expiresInHours, expiresInMinutes, expiresInSeconds)
}
if expiresInDays <= 0 {
- c.RegisterEvent(ctx, tenant, corev1.EventTypeWarning, "CertificateExpired", fmt.Sprintf("%s certificate '%s' has expired", certType, secret.Name))
+ c.recorder.Event(tenant, corev1.EventTypeWarning, "CertificateExpired", fmt.Sprintf("%s certificate '%s' has expired", certType, secret.Name))
expiresInHuman = "EXPIRED"
}
diff --git a/pkg/controller/decomission.go b/pkg/controller/decomission.go
index ca5e1bef40a..e97559df89f 100644
--- a/pkg/controller/decomission.go
+++ b/pkg/controller/decomission.go
@@ -126,7 +126,7 @@ func (c *Controller) checkForPoolDecommission(ctx context.Context, key string, t
}
for _, ssName := range poolNamesRemoved {
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "PoolRemoved", fmt.Sprintf("Tenant pool %s removed", ssName))
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "PoolRemoved", fmt.Sprintf("Tenant pool %s removed", ssName))
if err = c.kubeClientSet.AppsV1().StatefulSets(tenant.Namespace).Delete(ctx, ssName, metav1.DeleteOptions{}); err != nil {
if k8serrors.IsNotFound(err) {
continue
diff --git a/pkg/controller/job-controller.go b/pkg/controller/job-controller.go
new file mode 100644
index 00000000000..0c0d8c60d6b
--- /dev/null
+++ b/pkg/controller/job-controller.go
@@ -0,0 +1,328 @@
+// This file is part of MinIO Operator
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package controller
+
+import (
+ "context"
+ "fmt"
+ "reflect"
+ "sync"
+ "time"
+
+ "github.com/minio/minio-go/v7/pkg/set"
+ "github.com/minio/operator/pkg/apis/job.min.io/v1alpha1"
+ miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
+ stsv1alpha1 "github.com/minio/operator/pkg/apis/sts.min.io/v1alpha1"
+ jobinformers "github.com/minio/operator/pkg/client/informers/externalversions/job.min.io/v1alpha1"
+ joblisters "github.com/minio/operator/pkg/client/listers/job.min.io/v1alpha1"
+ "github.com/minio/operator/pkg/utils/miniojob"
+ batchjobv1 "k8s.io/api/batch/v1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/api/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/util/runtime"
+ batchv1 "k8s.io/client-go/informers/batch/v1"
+ "k8s.io/client-go/kubernetes"
+ k8sjoblisters "k8s.io/client-go/listers/batch/v1"
+ "k8s.io/client-go/tools/cache"
+ "k8s.io/client-go/tools/record"
+ "k8s.io/client-go/util/workqueue"
+ "k8s.io/klog/v2"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// JobController struct watches the Kubernetes API for changes to Tenant resources
+type JobController struct {
+ namespacesToWatch set.StringSet
+ minioJobLister joblisters.MinIOJobLister
+ minioJobHasSynced cache.InformerSynced
+ jobLister k8sjoblisters.JobLister
+ jobHasSynced cache.InformerSynced
+ recorder record.EventRecorder
+ workqueue workqueue.RateLimitingInterface
+ k8sClient client.Client
+}
+
+// runWorker is a long-running function that will continually call the
+// processNextWorkItem function in order to read and process a message on the
+// workqueue.
+func (c *JobController) runJobWorker() {
+ defer runtime.HandleCrash()
+ for processNextItem(c.workqueue, c.SyncHandler) {
+ }
+}
+
+func (c *JobController) enqueueJob(obj interface{}) {
+ key, err := cache.MetaNamespaceKeyFunc(obj)
+ if err != nil {
+ runtime.HandleError(err)
+ return
+ }
+ if !c.namespacesToWatch.IsEmpty() {
+ meta, err := meta.Accessor(obj)
+ if err != nil {
+ runtime.HandleError(err)
+ return
+ }
+ if !c.namespacesToWatch.Contains(meta.GetNamespace()) {
+ klog.Infof("Ignoring tenant `%s` in namespace that is not watched by this controller.", key)
+ return
+ }
+ }
+ // key = default/mc-job-1
+ c.workqueue.AddRateLimited(key)
+}
+
+// NewJobController returns a new Operator Controller
+func NewJobController(
+ minioJobInformer jobinformers.MinIOJobInformer,
+ jobInformer batchv1.JobInformer,
+ namespacesToWatch set.StringSet,
+ kubeClientSet kubernetes.Interface,
+ recorder record.EventRecorder,
+ workqueue workqueue.RateLimitingInterface,
+ k8sClient client.Client,
+) *JobController {
+ controller := &JobController{
+ namespacesToWatch: namespacesToWatch,
+ minioJobLister: minioJobInformer.Lister(),
+ minioJobHasSynced: minioJobInformer.Informer().HasSynced,
+ jobLister: jobInformer.Lister(),
+ jobHasSynced: jobInformer.Informer().HasSynced,
+ recorder: recorder,
+ workqueue: workqueue,
+ k8sClient: k8sClient,
+ }
+
+ // Set up an event handler for when resources change
+ minioJobInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+ AddFunc: controller.enqueueJob,
+ UpdateFunc: func(old, new interface{}) {
+ oldJob := old.(*v1alpha1.MinIOJob)
+ newJob := new.(*v1alpha1.MinIOJob)
+ if oldJob.ResourceVersion == newJob.ResourceVersion {
+ return
+ }
+ controller.enqueueJob(new)
+ },
+ })
+
+ jobInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+ UpdateFunc: func(old, new interface{}) {
+ newJob := new.(*batchjobv1.Job)
+ jobName, ok := newJob.Labels[miniojob.MinioJobName]
+ if !ok {
+ return
+ }
+ jobCRName, ok := newJob.Labels[miniojob.MinioJobCRName]
+ if !ok {
+ return
+ }
+ val, ok := globalIntervalJobStatus.Load(fmt.Sprintf("%s/%s", newJob.GetNamespace(), jobCRName))
+ if ok {
+ intervalJob := val.(*miniojob.MinIOIntervalJob)
+ command, ok := intervalJob.CommandMap[jobName]
+ if ok {
+ if newJob.Status.Succeeded > 0 {
+ command.SetStatus(true, "")
+ } else {
+ for _, condition := range newJob.Status.Conditions {
+ if condition.Type == batchjobv1.JobFailed {
+ command.SetStatus(false, condition.Message)
+ break
+ }
+ }
+ }
+ }
+ }
+ controller.HandleObject(newJob)
+ },
+ })
+ return controller
+}
+
+// HasSynced is to determine if obj is synced
+func (c *JobController) HasSynced() cache.InformerSynced {
+ return c.minioJobHasSynced
+}
+
+// HandleObject will take any resource implementing metav1.Object and attempt
+// to find the CRD resource that 'owns' it.
+func (c *JobController) HandleObject(obj metav1.Object) {
+ JobCRDResourceKind := "MinIOJob"
+ if ownerRef := metav1.GetControllerOf(obj); ownerRef != nil {
+ switch ownerRef.Kind {
+ case JobCRDResourceKind:
+ job, err := c.minioJobLister.MinIOJobs(obj.GetNamespace()).Get(ownerRef.Name)
+ if err != nil {
+ klog.V(4).Info("Ignore orphaned object", "object", klog.KObj(job), JobCRDResourceKind, ownerRef.Name)
+ return
+ }
+ c.enqueueJob(job)
+ default:
+ return
+ }
+ return
+ }
+}
+
+// SyncHandler compares the current Job state with the desired, and attempts to
+// converge the two. It then updates the Status block of the Job resource
+// with the current status of the resource.
+func (c *JobController) SyncHandler(key string) (Result, error) {
+ // Convert the namespace/name string into a distinct namespace and name
+ if key == "" {
+ runtime.HandleError(fmt.Errorf("Invalid resource key: %s", key))
+ return WrapResult(Result{}, nil)
+ }
+ namespace, jobName := key2NamespaceName(key)
+ ctx := context.Background()
+ jobCR := v1alpha1.MinIOJob{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: jobName,
+ Namespace: namespace,
+ },
+ }
+ err := c.k8sClient.Get(ctx, client.ObjectKeyFromObject(&jobCR), &jobCR)
+ if err != nil {
+ // job cr have gone
+ globalIntervalJobStatus.Delete(fmt.Sprintf("%s/%s", jobCR.Namespace, jobCR.Name))
+ if errors.IsNotFound(err) {
+ return WrapResult(Result{}, nil)
+ }
+ return WrapResult(Result{}, err)
+ }
+ // if job cr is Success, do nothing
+ if jobCR.Status.Phase == miniojob.MinioJobPhaseSuccess {
+ // delete the job status
+ globalIntervalJobStatus.Delete(fmt.Sprintf("%s/%s", jobCR.Namespace, jobCR.Name))
+ return WrapResult(Result{}, nil)
+ }
+ intervalJob, err := checkMinIOJob(&jobCR)
+ if err != nil {
+ return WrapResult(Result{}, err)
+ }
+ // get tenant
+ tenant := &miniov2.Tenant{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: jobCR.Spec.TenantRef.Namespace,
+ Name: jobCR.Spec.TenantRef.Name,
+ },
+ }
+ err = c.k8sClient.Get(ctx, client.ObjectKeyFromObject(tenant), tenant)
+ if err != nil {
+ jobCR.Status.Phase = miniojob.MinioJobPhaseError
+ jobCR.Status.Message = fmt.Sprintf("Get tenant %s/%s error:%v", jobCR.Spec.TenantRef.Namespace, jobCR.Spec.TenantRef.Name, err)
+ err = c.updateJobStatus(ctx, &jobCR)
+ return WrapResult(Result{}, err)
+ }
+ if tenant.Status.HealthStatus != miniov2.HealthStatusGreen {
+ return WrapResult(Result{RequeueAfter: time.Second * 5}, nil)
+ }
+ // check sa
+ pbs := &stsv1alpha1.PolicyBindingList{}
+ err = c.k8sClient.List(ctx, pbs, client.InNamespace(namespace))
+ if err != nil {
+ return WrapResult(Result{}, err)
+ }
+ if len(pbs.Items) == 0 {
+ return WrapResult(Result{}, fmt.Errorf("no policybinding found"))
+ }
+ saFound := false
+ for _, pb := range pbs.Items {
+ if pb.Spec.Application.Namespace == namespace && pb.Spec.Application.ServiceAccount == jobCR.Spec.ServiceAccountName {
+ saFound = true
+ }
+ }
+ if !saFound {
+ return WrapResult(Result{}, fmt.Errorf("no serviceaccount found"))
+ }
+ err = intervalJob.CreateCommandJob(ctx, c.k8sClient)
+ if err != nil {
+ jobCR.Status.Phase = miniojob.MinioJobPhaseError
+ jobCR.Status.Message = fmt.Sprintf("Create job error:%v", err)
+ err = c.updateJobStatus(ctx, &jobCR)
+ return WrapResult(Result{}, err)
+ }
+ // update status
+ jobCR.Status = intervalJob.GetMinioJobStatus(ctx)
+ err = c.updateJobStatus(ctx, &jobCR)
+ return WrapResult(Result{}, err)
+}
+
+func (c *JobController) updateJobStatus(ctx context.Context, job *v1alpha1.MinIOJob) error {
+ return c.k8sClient.Status().Update(ctx, job)
+}
+
+func checkMinIOJob(jobCR *v1alpha1.MinIOJob) (intervalJob *miniojob.MinIOIntervalJob, err error) {
+ defer func() {
+ if err != nil {
+ globalIntervalJobStatus.Delete(fmt.Sprintf("%s/%s", jobCR.Namespace, jobCR.Name))
+ }
+ }()
+ val, found := globalIntervalJobStatus.Load(fmt.Sprintf("%s/%s", jobCR.Namespace, jobCR.Name))
+ if found {
+ intervalJob = val.(*miniojob.MinIOIntervalJob)
+ if reflect.DeepEqual(intervalJob.JobCR.Spec, jobCR.Spec) {
+ intervalJob.JobCR.UID = jobCR.UID
+ return intervalJob, nil
+ }
+ }
+ intervalJob = &miniojob.MinIOIntervalJob{
+ JobCR: jobCR.DeepCopy(),
+ Command: []*miniojob.MinIOIntervalJobCommand{},
+ CommandMap: map[string]*miniojob.MinIOIntervalJobCommand{},
+ }
+ if jobCR.Spec.TenantRef.Namespace == "" {
+ return intervalJob, fmt.Errorf("tenant namespace is empty")
+ }
+ if jobCR.Spec.TenantRef.Name == "" {
+ return intervalJob, fmt.Errorf("tenant name is empty")
+ }
+ if jobCR.Spec.ServiceAccountName == "" {
+ return intervalJob, fmt.Errorf("serviceaccount name is empty")
+ }
+ for index, val := range jobCR.Spec.Commands {
+ mcCommand, found := miniojob.OperationAliasToMC(val.Operation)
+ if !found {
+ return intervalJob, fmt.Errorf("operation %s is not supported", val.Operation)
+ }
+ argsFuncs, found := miniojob.JobOperation[mcCommand]
+ if !found {
+ return intervalJob, fmt.Errorf("operation %s is not supported", mcCommand)
+ }
+ jobCommand, err := miniojob.GenerateMinIOIntervalJobCommand(mcCommand, index, val.DependsOn, val.Name, val.Args, argsFuncs)
+ if err != nil {
+ return intervalJob, err
+ }
+ intervalJob.Command = append(intervalJob.Command, jobCommand)
+ intervalJob.CommandMap[jobCommand.JobName] = jobCommand
+ }
+ // check all dependon
+ for _, command := range intervalJob.Command {
+ for _, dep := range command.DepnedsOn {
+ _, found := intervalJob.CommandMap[dep]
+ if !found {
+ return intervalJob, fmt.Errorf("dependent job %s not found", dep)
+ }
+ }
+ }
+ globalIntervalJobStatus.Store(fmt.Sprintf("%s/%s", jobCR.Namespace, jobCR.Name), intervalJob)
+ return intervalJob, nil
+}
+
+var globalIntervalJobStatus = sync.Map{}
diff --git a/pkg/controller/kes.go b/pkg/controller/kes.go
index eb5a6c8629d..52d312d8074 100644
--- a/pkg/controller/kes.go
+++ b/pkg/controller/kes.go
@@ -33,7 +33,6 @@ import (
"github.com/minio/operator/pkg/resources/services"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/minio/operator/pkg/resources/statefulsets"
@@ -101,13 +100,13 @@ func (c *Controller) createKESCSR(ctx context.Context, tenant *miniov2.Tenant) e
klog.Errorf("Unexpected error during the creation of the csr/%s: %v", tenant.KESCSRName(), err)
return err
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "CSRCreated", "KES CSR Created")
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "CSRCreated", "KES CSR Created")
// fetch certificate from CSR
certbytes, err := c.fetchCertificate(ctx, tenant.KESCSRName())
if err != nil {
klog.Errorf("Unexpected error during the creation of the csr/%s: %v", tenant.KESCSRName(), err)
- c.RegisterEvent(ctx, tenant, corev1.EventTypeWarning, "CSRFailed", fmt.Sprintf("KES CSR Failed to create: %s", err))
+ c.recorder.Event(tenant, corev1.EventTypeWarning, "CSRFailed", fmt.Sprintf("KES CSR Failed to create: %s", err))
return err
}
@@ -214,17 +213,17 @@ func (c *Controller) checkKESStatus(ctx context.Context, tenant *miniov2.Tenant,
klog.V(2).Infof("Creating a new Headless Service for cluster %q", nsName)
svc = services.NewHeadlessForKES(tenant)
if _, err = c.kubeClientSet.CoreV1().Services(svc.Namespace).Create(ctx, svc, cOpts); err != nil {
- c.RegisterEvent(ctx, tenant, corev1.EventTypeWarning, "SvcFailed", fmt.Sprintf("KES Headless Service failed to create: %s", err))
+ c.recorder.Event(tenant, corev1.EventTypeWarning, "SvcFailed", fmt.Sprintf("KES Headless Service failed to create: %s", err))
return err
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "SvcCreated", "KES Headless Service created")
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "SvcCreated", "KES Headless Service created")
} else {
return err
}
}
if tenant.HasGCPCredentialSecretForKES() {
- kesSA, err := c.kubeClientSet.CoreV1().ServiceAccounts(tenant.Namespace).Get(ctx, tenant.Spec.KES.ServiceAccountName, v1.GetOptions{})
+ kesSA, err := c.kubeClientSet.CoreV1().ServiceAccounts(tenant.Namespace).Get(ctx, tenant.Spec.KES.ServiceAccountName, metav1.GetOptions{})
if err != nil {
klog.Errorf("unable to get the service account %s/%s: %v", tenant.Namespace, tenant.Spec.KES.ServiceAccountName, err)
return err
@@ -244,10 +243,10 @@ func (c *Controller) checkKESStatus(ctx context.Context, tenant *miniov2.Tenant,
klog.V(2).Infof("Creating a new KES StatefulSet for %q", nsName)
if _, err = c.kubeClientSet.AppsV1().StatefulSets(tenant.Namespace).Create(ctx, ks, cOpts); err != nil {
klog.V(2).Infof(err.Error())
- c.RegisterEvent(ctx, tenant, corev1.EventTypeWarning, "StsFailed", fmt.Sprintf("KES Statefulset failed to create: %s", err))
+ c.recorder.Event(tenant, corev1.EventTypeWarning, "StsFailed", fmt.Sprintf("KES Statefulset failed to create: %s", err))
return err
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "StsCreated", "KES Statefulset Created")
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "StsCreated", "KES Statefulset Created")
} else {
return err
}
@@ -265,10 +264,10 @@ func (c *Controller) checkKESStatus(ctx context.Context, tenant *miniov2.Tenant,
return err
}
if _, err = c.kubeClientSet.AppsV1().StatefulSets(tenant.Namespace).Update(ctx, ks, uOpts); err != nil {
- c.RegisterEvent(ctx, tenant, corev1.EventTypeWarning, "StsFailed", fmt.Sprintf("KES Statefulset failed to update: %s", err))
+ c.recorder.Event(tenant, corev1.EventTypeWarning, "StsFailed", fmt.Sprintf("KES Statefulset failed to update: %s", err))
return err
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "StsUpdated", "KES Statefulset Updated")
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "StsUpdated", "KES Statefulset Updated")
}
}
}
@@ -286,7 +285,7 @@ func (c *Controller) checkAndCreateMinIOClientCertificates(ctx context.Context,
klog.V(2).Infof("Creating a new Client Certificate for MinIO, cluster %q", nsName)
if err = c.createMinIOClientCertificates(ctx, tenant); err != nil {
// we want to re-queue this tenant so we can re-check for the health at a later stage
- c.RegisterEvent(ctx, tenant, corev1.EventTypeWarning, "CertFailed", fmt.Sprintf("KES MinIO Client Certificate failed to create: %s", err))
+ c.recorder.Event(tenant, corev1.EventTypeWarning, "CertFailed", fmt.Sprintf("KES MinIO Client Certificate failed to create: %s", err))
return err
}
return errors.New("waiting for minio client cert")
diff --git a/pkg/controller/main-controller.go b/pkg/controller/main-controller.go
index b88cc35d244..5502a7321f4 100644
--- a/pkg/controller/main-controller.go
+++ b/pkg/controller/main-controller.go
@@ -28,7 +28,7 @@ import (
"github.com/minio/operator/pkg/utils"
- "github.com/minio/madmin-go/v2"
+ "github.com/minio/madmin-go/v3"
"github.com/minio/operator/pkg/common"
xcerts "github.com/minio/pkg/certs"
@@ -56,6 +56,7 @@ import (
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
appsinformers "k8s.io/client-go/informers/apps/v1"
+ batchv1 "k8s.io/client-go/informers/batch/v1"
coreinformers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
@@ -73,9 +74,9 @@ import (
miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
clientset "github.com/minio/operator/pkg/client/clientset/versioned"
minioscheme "github.com/minio/operator/pkg/client/clientset/versioned/scheme"
+ jobinformers "github.com/minio/operator/pkg/client/informers/externalversions/job.min.io/v1alpha1"
informers "github.com/minio/operator/pkg/client/informers/externalversions/minio.min.io/v2"
stsInformers "github.com/minio/operator/pkg/client/informers/externalversions/sts.min.io/v1alpha1"
- "github.com/minio/operator/pkg/resources/services"
"github.com/minio/operator/pkg/resources/statefulsets"
)
@@ -197,6 +198,11 @@ type Controller struct {
// policyBindingListerSynced returns true if the PolicyBinding shared informer
// has synced at least once.
policyBindingListerSynced cache.InformerSynced
+
+ // controllers denotes the list of components controlled
+ // by the controller. Each component is itself
+ // a controller. This handle is for supporting the abstraction.
+ controllers []*JobController
}
// EventType is Event type to handle
@@ -208,7 +214,7 @@ const (
LeaderElection
)
-// EventNotification - structure to send messages trough a channel regarding a error event to be handled
+// EventNotification - structure to send messages through a channel regarding a error event to be handled
type EventNotification struct {
// Err the error to handle if any, null when is just a message
Err error
@@ -216,8 +222,25 @@ type EventNotification struct {
Type EventType
}
-// NewController returns a new sample controller
-func NewController(podName string, namespacesToWatch set.StringSet, kubeClientSet kubernetes.Interface, k8sClient client.Client, minioClientSet clientset.Interface, promClient promclientset.Interface, statefulSetInformer appsinformers.StatefulSetInformer, deploymentInformer appsinformers.DeploymentInformer, podInformer coreinformers.PodInformer, tenantInformer informers.TenantInformer, policyBindingInformer stsInformers.PolicyBindingInformer, serviceInformer coreinformers.ServiceInformer, hostsTemplate, operatorVersion string) *Controller {
+// NewController returns a new Operator Controller
+func NewController(
+ podName string,
+ namespacesToWatch set.StringSet,
+ kubeClientSet kubernetes.Interface,
+ k8sClient client.Client,
+ minioClientSet clientset.Interface,
+ promClient promclientset.Interface,
+ statefulSetInformer appsinformers.StatefulSetInformer,
+ deploymentInformer appsinformers.DeploymentInformer,
+ podInformer coreinformers.PodInformer,
+ tenantInformer informers.TenantInformer,
+ policyBindingInformer stsInformers.PolicyBindingInformer,
+ serviceInformer coreinformers.ServiceInformer,
+ hostsTemplate,
+ operatorVersion string,
+ minioJobinformer jobinformers.MinIOJobInformer,
+ jobInformer batchv1.JobInformer,
+) *Controller {
// Create event broadcaster
// Add minio-controller types to the default Kubernetes Scheme so Events can be
// logged for minio-controller types.
@@ -248,6 +271,14 @@ func NewController(podName string, namespacesToWatch set.StringSet, kubeClientSe
oprImg = env.Get(DefaultOperatorImageEnv, oprImg)
+ //controllerConfig := controllerConfig{
+ // serviceLister: serviceInformer.Lister(),
+ // kubeClientSet: kubeClientSet,
+ // statefulSetLister: statefulSetInformer.Lister(),
+ // deploymentLister: deploymentInformer.Lister(),
+ // recorder: recorder,
+ //}
+
controller := &Controller{
podName: podName,
namespacesToWatch: namespacesToWatch,
@@ -270,6 +301,17 @@ func NewController(podName string, namespacesToWatch set.StringSet, kubeClientSe
operatorVersion: operatorVersion,
policyBindingListerSynced: policyBindingInformer.Informer().HasSynced,
operatorImage: oprImg,
+ controllers: []*JobController{
+ NewJobController(
+ minioJobinformer,
+ jobInformer,
+ namespacesToWatch,
+ kubeClientSet,
+ recorder,
+ queue.NewNamedRateLimitingQueue(MinIOControllerRateLimiter(), "MinioJobs"),
+ k8sClient,
+ ),
+ },
}
// Initialize operator HTTP upgrade server handlers
@@ -408,10 +450,19 @@ func leaderRun(ctx context.Context, c *Controller, threadiness int, stopCh <-cha
if ok := cache.WaitForCacheSync(stopCh, c.statefulSetListerSynced, c.deploymentListerSynced, c.tenantsSynced, c.policyBindingListerSynced); !ok {
panic("failed to wait for caches to sync")
}
+ // Wait for the caches to be synced before starting workers
+ for _, jobController := range c.controllers {
+ if ok := cache.WaitForCacheSync(stopCh, jobController.minioJobHasSynced, jobController.jobHasSynced); !ok {
+ panic("failed to wait for caches to sync")
+ }
+ }
- klog.Info("Starting workers")
- // Launch two workers to process Tenant resources
+ klog.Info("Starting workers and Job workers")
+ JobController := c.controllers[0]
+ // fmt.Println(controller.SyncHandler())
+ // Launch two workers to process Job resources
for i := 0; i < threadiness; i++ {
+ go wait.Until(JobController.runJobWorker, time.Second, stopCh)
go wait.Until(c.runWorker, time.Second, stopCh)
}
@@ -629,7 +680,7 @@ func (c *Controller) Stop() {
// workqueue.
func (c *Controller) runWorker() {
defer runtime.HandleCrash()
- for c.processNextWorkItem() {
+ for processNextItem(c.workqueue, c.syncHandler) {
}
}
@@ -638,72 +689,8 @@ func (c *Controller) runWorker() {
// healthCheckQueue.
func (c *Controller) runHealthCheckWorker() {
defer runtime.HandleCrash()
- for c.processNextHealthCheckItem() {
- }
-}
-
-// processNextWorkItem will read a single work item off the workqueue and
-// attempt to process it, by calling the syncHandler.
-func (c *Controller) processNextWorkItem() bool {
- obj, shutdown := c.workqueue.Get()
- if shutdown {
- return false
- }
-
- // We wrap this block in a func so we can defer c.workqueue.Done.
- processItem := func(obj interface{}) error {
- // We call Done here so the workqueue knows we have finished
- // processing this item. We also must remember to call Forget if we
- // do not want this work item being re-queued. For example, we do
- // not call Forget if a transient error occurs, instead the item is
- // put back on the workqueue and attempted again after a back-off
- // period.
- defer c.workqueue.Done(obj)
- var key string
- var ok bool
- // We expect strings to come off the workqueue. These are of the
- // form namespace/name. We do this as the delayed nature of the
- // workqueue means the items in the informer cache may actually be
- // more up to date that when the item was initially put onto the
- // workqueue.
- if key, ok = obj.(string); !ok {
- // As the item in the workqueue is actually invalid, we call
- // Forget here else we'd go into a loop of attempting to
- // process a work item that is invalid.
- c.workqueue.Forget(obj)
- runtime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj))
- return nil
- }
- klog.V(2).Infof("Key from workqueue: %s", key)
-
- result, err := c.syncHandler(key)
- switch {
- case err != nil:
- c.workqueue.AddRateLimited(key)
- return fmt.Errorf("error syncing '%s': %s", key, err.Error())
- case result.RequeueAfter > 0:
- // The result.RequeueAfter request will be lost, if it is returned
- // along with a non-nil error. But this is intended as
- // We need to drive to stable reconcile loops before queuing due
- // to result.RequestAfter
- c.workqueue.Forget(obj)
- c.workqueue.AddAfter(key, result.RequeueAfter)
- case result.Requeue:
- c.workqueue.AddRateLimited(key)
- default:
- // Finally, if no error occurs we Forget this item so it does not
- // get queued again until another change happens.
- c.workqueue.Forget(obj)
- klog.V(4).Infof("Successfully synced '%s'", key)
- }
- return nil
+ for processNextItem(c.healthCheckQueue, c.syncHealthCheckHandler) {
}
-
- if err := processItem(obj); err != nil {
- runtime.HandleError(err)
- return true
- }
- return true
}
const slashSeparator = "/"
@@ -799,7 +786,7 @@ func (c *Controller) syncHandler(key string) (Result, error) {
if _, err2 := c.updateTenantStatus(ctx, tenant, err.Error(), 0); err2 != nil {
klog.V(2).Infof(err2.Error())
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeWarning, "MissingCreds", "Tenant is missing root credentials")
+ c.recorder.Event(tenant, corev1.EventTypeWarning, "MissingCreds", "Tenant is missing root credentials")
return WrapResult(Result{}, nil)
}
return WrapResult(Result{}, err)
@@ -891,59 +878,11 @@ func (c *Controller) syncHandler(key string) (Result, error) {
return WrapResult(Result{}, err)
}
- // Handle the Internal Headless Service for Tenant StatefulSet
- hlSvc, err := c.serviceLister.Services(tenant.Namespace).Get(tenant.MinIOHLServiceName())
+ // Check MinIO Headless Service used for internode communication
+ err = c.checkMinIOHLSvc(ctx, tenant, nsName)
if err != nil {
- if k8serrors.IsNotFound(err) {
- if tenant, err = c.updateTenantStatus(ctx, tenant, StatusProvisioningHLService, 0); err != nil {
- return WrapResult(Result{}, err)
- }
- klog.V(2).Infof("Creating a new Headless Service for cluster %q", nsName)
- // Create the headless service for the tenant
- hlSvc = services.NewHeadlessForMinIO(tenant)
- _, err = c.kubeClientSet.CoreV1().Services(tenant.Namespace).Create(ctx, hlSvc, cOpts)
- if err != nil {
- return WrapResult(Result{}, err)
- }
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "SvcCreated", "Headless Service created")
- } else {
- return WrapResult(Result{}, err)
- }
- } else {
- existingPorts := hlSvc.Spec.Ports
- sftpPortFound := false
- for _, port := range existingPorts {
- if port.Name == miniov2.MinIOServiceSFTPPortName {
- sftpPortFound = true
- break
- }
- }
- var newPorts []corev1.ServicePort
- if tenant.Spec.Features != nil && tenant.Spec.Features.EnableSFTP != nil && *tenant.Spec.Features.EnableSFTP {
- if !sftpPortFound {
- newPorts = existingPorts
- newPorts = append(newPorts, corev1.ServicePort{Port: miniov2.MinIOSFTPPort, Name: miniov2.MinIOServiceSFTPPortName})
- hlSvc.Spec.Ports = newPorts
- _, err := c.kubeClientSet.CoreV1().Services(tenant.Namespace).Update(ctx, hlSvc, metav1.UpdateOptions(cOpts))
- if err != nil {
- return WrapResult(Result{}, err)
- }
- }
- } else {
- if sftpPortFound {
- for _, port := range existingPorts {
- if port.Name == miniov2.MinIOServiceSFTPPortName {
- continue
- }
- newPorts = append(newPorts, port)
- }
- hlSvc.Spec.Ports = newPorts
- _, err := c.kubeClientSet.CoreV1().Services(tenant.Namespace).Update(ctx, hlSvc, metav1.UpdateOptions(cOpts))
- if err != nil {
- return WrapResult(Result{}, err)
- }
- }
- }
+ klog.V(2).Infof("error consolidating headless service: %s", err.Error())
+ return WrapResult(Result{}, err)
}
// List all MinIO Tenants in this namespace.
@@ -992,7 +931,8 @@ func (c *Controller) syncHandler(key string) (Result, error) {
// check if operator-ca-tls has to be updated or re-created in the tenant namespace
operatorCATLSExists, err := c.checkOperatorCAForTenant(ctx, tenant)
if err != nil {
- return WrapResult(Result{}, err)
+ // Don't return here as we get stuck when recreating the stateful set
+ klog.Infof("There was an error while updating the certificate %s", err)
}
// consolidate the status of all pools. this is meant to cover for legacy tenants
@@ -1063,7 +1003,7 @@ func (c *Controller) syncHandler(key string) (Result, error) {
SkipEnvVars: skipEnvVars,
Pool: &pool,
PoolStatus: &tenant.Status.Pools[i],
- ServiceName: hlSvc.Name,
+ ServiceName: tenant.MinIOHLServiceName(),
HostsTemplate: c.hostsTemplate,
OperatorVersion: c.operatorVersion,
OperatorCATLS: operatorCATLSExists,
@@ -1073,7 +1013,7 @@ func (c *Controller) syncHandler(key string) (Result, error) {
if err != nil {
return WrapResult(Result{}, err)
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "PoolCreated", fmt.Sprintf("Tenant pool %s created", pool.Name))
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "PoolCreated", fmt.Sprintf("Tenant pool %s created", pool.Name))
// Report the pool is properly created
tenant.Status.Pools[i].State = miniov2.PoolCreated
// mark we are adding a new pool to the next block can act accordingly
@@ -1234,7 +1174,7 @@ func (c *Controller) syncHandler(key string) (Result, error) {
// Update failed, nothing needs to be changed in the container
return WrapResult(Result{}, err)
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeWarning, "Inplace update is disabled, falling back to performing only statefulset update.", fmt.Sprintf("Tenant %s", tenant.Name))
+ c.recorder.Event(tenant, corev1.EventTypeWarning, "Inplace update is disabled, falling back to performing only statefulset update.", fmt.Sprintf("Tenant %s", tenant.Name))
}
if err == nil {
if us.CurrentVersion != us.UpdatedVersion {
@@ -1273,7 +1213,7 @@ func (c *Controller) syncHandler(key string) (Result, error) {
SkipEnvVars: skipEnvVars,
Pool: &pool,
PoolStatus: &tenant.Status.Pools[i],
- ServiceName: hlSvc.Name,
+ ServiceName: tenant.MinIOHLServiceName(),
HostsTemplate: c.hostsTemplate,
OperatorVersion: c.operatorVersion,
OperatorCATLS: operatorCATLSExists,
@@ -1282,7 +1222,7 @@ func (c *Controller) syncHandler(key string) (Result, error) {
if _, err = c.kubeClientSet.AppsV1().StatefulSets(tenant.Namespace).Update(ctx, ss, uOpts); err != nil {
return WrapResult(Result{}, err)
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "PoolUpdated", fmt.Sprintf("Tenant pool %s updated", pool.Name))
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "PoolUpdated", fmt.Sprintf("Tenant pool %s updated", pool.Name))
}
}
@@ -1323,7 +1263,7 @@ func (c *Controller) syncHandler(key string) (Result, error) {
SkipEnvVars: skipEnvVars,
Pool: &pool,
PoolStatus: &tenant.Status.Pools[i],
- ServiceName: hlSvc.Name,
+ ServiceName: tenant.MinIOHLServiceName(),
HostsTemplate: c.hostsTemplate,
OperatorVersion: c.operatorVersion,
OperatorCATLS: operatorCATLSExists,
@@ -1389,22 +1329,22 @@ func (c *Controller) syncHandler(key string) (Result, error) {
if !tenant.Status.ProvisionedUsers && len(tenant.Spec.Users) > 0 {
if err := c.createUsers(ctx, tenant, tenantConfiguration); err != nil {
klog.V(2).Infof("Unable to create MinIO users: %v", err)
- c.RegisterEvent(ctx, tenant, corev1.EventTypeWarning, "UsersCreatedFailed", fmt.Sprintf("Users creation failed: %s", err))
+ c.recorder.Event(tenant, corev1.EventTypeWarning, "UsersCreatedFailed", fmt.Sprintf("Users creation failed: %s", err))
// retry after 5sec
return WrapResult(Result{RequeueAfter: time.Second * 5}, nil)
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "UsersCreated", "Users created")
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "UsersCreated", "Users created")
}
// Ensure we are only creating the bucket
if len(tenant.Spec.Buckets) > 0 {
if create, err := c.createBuckets(ctx, tenant, tenantConfiguration); err != nil {
klog.V(2).Infof("Unable to create MinIO buckets: %v", err)
- c.RegisterEvent(ctx, tenant, corev1.EventTypeWarning, "BucketsCreatedFailed", fmt.Sprintf("Buckets creation failed: %s", err))
+ c.recorder.Event(tenant, corev1.EventTypeWarning, "BucketsCreatedFailed", fmt.Sprintf("Buckets creation failed: %s", err))
// retry after 5sec
return WrapResult(Result{RequeueAfter: time.Second * 5}, err)
} else if create {
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "BucketsCreated", "Buckets created")
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "BucketsCreated", "Buckets created")
}
}
@@ -1500,3 +1440,65 @@ type patchAnnotation struct {
Path string `json:"path"`
Value string `json:"value"`
}
+
+func processNextItem(workqueue queue.RateLimitingInterface, syncer func(key string) (Result, error)) bool {
+ obj, shutdown := workqueue.Get()
+ if shutdown {
+ return false
+ }
+
+ // We wrap this block in a func so we can defer c.workqueue.Done.
+ processItem := func(obj interface{}) error {
+ // We call Done here so the workqueue knows we have finished
+ // processing this item. We also must remember to call Forget if we
+ // do not want this work item being re-queued. For example, we do
+ // not call Forget if a transient error occurs, instead the item is
+ // put back on the workqueue and attempted again after a back-off
+ // period.
+ defer workqueue.Done(obj)
+ var key string
+ var ok bool
+ // We expect strings to come off the workqueue. These are of the
+ // form namespace/name. We do this as the delayed nature of the
+ // workqueue means the items in the informer cache may actually be
+ // more up to date that when the item was initially put onto the
+ // workqueue.
+ if key, ok = obj.(string); !ok {
+ // As the item in the workqueue is actually invalid, we call
+ // Forget here else we'd go into a loop of attempting to
+ // process a work item that is invalid.
+ workqueue.Forget(obj)
+ runtime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj))
+ return nil
+ }
+ klog.V(2).Infof("Key from workqueue: %s", key)
+
+ result, err := syncer(key)
+ switch {
+ case err != nil:
+ workqueue.AddRateLimited(key)
+ return fmt.Errorf("error syncing '%s': %s", key, err.Error())
+ case result.RequeueAfter > 0:
+ // The result.RequeueAfter request will be lost, if it is returned
+ // along with a non-nil error. But this is intended as
+ // We need to drive to stable reconcile loops before queuing due
+ // to result.RequestAfter
+ workqueue.Forget(obj)
+ workqueue.AddAfter(key, result.RequeueAfter)
+ case result.Requeue:
+ workqueue.AddRateLimited(key)
+ default:
+ // Finally, if no error occurs we Forget this item so it does not
+ // get queued again until another change happens.
+ workqueue.Forget(obj)
+ klog.V(4).Infof("Successfully synced '%s'", key)
+ }
+ return nil
+ }
+
+ if err := processItem(obj); err != nil {
+ runtime.HandleError(err)
+ return true
+ }
+ return true
+}
diff --git a/pkg/controller/minio-services.go b/pkg/controller/minio-services.go
index fe425c94176..1eaf01a16e2 100644
--- a/pkg/controller/minio-services.go
+++ b/pkg/controller/minio-services.go
@@ -21,7 +21,6 @@ import (
miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
"github.com/minio/operator/pkg/resources/services"
corev1 "k8s.io/api/core/v1"
- v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -46,7 +45,7 @@ func (c *Controller) checkMinIOSvc(ctx context.Context, tenant *miniov2.Tenant,
if err != nil {
return err
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "SvcCreated", "MinIO Service Created")
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "SvcCreated", "MinIO Service Created")
} else {
return err
}
@@ -72,9 +71,9 @@ func (c *Controller) checkMinIOSvc(ctx context.Context, tenant *miniov2.Tenant,
// Only when ExposeServices is set an explicit value we do modifications to the service type
if tenant.Spec.ExposeServices != nil {
if tenant.Spec.ExposeServices.MinIO {
- svc.Spec.Type = v1.ServiceTypeLoadBalancer
+ svc.Spec.Type = corev1.ServiceTypeLoadBalancer
} else {
- svc.Spec.Type = v1.ServiceTypeClusterIP
+ svc.Spec.Type = corev1.ServiceTypeClusterIP
}
}
@@ -85,12 +84,12 @@ func (c *Controller) checkMinIOSvc(ctx context.Context, tenant *miniov2.Tenant,
if err != nil {
return err
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "Updated", "MinIO Service Updated")
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "Updated", "MinIO Service Updated")
}
return err
}
-func minioSvcMatchesSpecification(svc *v1.Service, expectedSvc *v1.Service) (bool, error) {
+func minioSvcMatchesSpecification(svc *corev1.Service, expectedSvc *corev1.Service) (bool, error) {
// expected labels match
for k, expVal := range expectedSvc.ObjectMeta.Labels {
if value, ok := svc.ObjectMeta.Labels[k]; !ok || value != expVal {
@@ -125,3 +124,54 @@ func minioSvcMatchesSpecification(svc *v1.Service, expectedSvc *v1.Service) (boo
}
return true, nil
}
+
+// checkMinIOHLSvc validates the existence of the MinIO headless service and validate its status against what
+// the specification states
+func (c *Controller) checkMinIOHLSvc(ctx context.Context, tenant *miniov2.Tenant, nsName types.NamespacedName) error {
+ // Handle the Internal Headless Service for Tenant StatefulSet
+ hlSvc, err := c.serviceLister.Services(tenant.Namespace).Get(tenant.MinIOHLServiceName())
+ if err != nil {
+ if k8serrors.IsNotFound(err) {
+ if tenant, err = c.updateTenantStatus(ctx, tenant, StatusProvisioningHLService, 0); err != nil {
+ return err
+ }
+ klog.V(2).Infof("Creating a new Headless Service for cluster %q", nsName)
+ // Create the headless service for the tenant
+ hlSvc = services.NewHeadlessForMinIO(tenant)
+ _, err = c.kubeClientSet.CoreV1().Services(tenant.Namespace).Create(ctx, hlSvc, metav1.CreateOptions{})
+ if err != nil {
+ return err
+ }
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "SvcCreated", "Headless Service created")
+ } else {
+ return err
+ }
+ }
+ // compare the current version of the service to what we expect
+ expectedHlSvc := services.NewHeadlessForMinIO(tenant)
+ // does the current service matches our specification?
+ minioSvcMatchesSpec, err := minioSvcMatchesSpecification(hlSvc, expectedHlSvc)
+
+ // check the specification of the MinIO ClusterIP service
+ if !minioSvcMatchesSpec {
+ if err != nil {
+ klog.Infof("Headless Services don't match: %s", err)
+ }
+
+ // impose what we care about
+ hlSvc.ObjectMeta.Annotations = expectedHlSvc.ObjectMeta.Annotations
+ hlSvc.ObjectMeta.Labels = expectedHlSvc.ObjectMeta.Labels
+ hlSvc.Spec.Ports = expectedHlSvc.Spec.Ports
+
+ // update the selector
+ hlSvc.Spec.Selector = expectedHlSvc.Spec.Selector
+
+ _, err = c.kubeClientSet.CoreV1().Services(tenant.Namespace).Update(ctx, hlSvc, metav1.UpdateOptions{})
+ if err != nil {
+ return err
+ }
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "Updated", "Headless Service Updated")
+
+ }
+ return err
+}
diff --git a/pkg/controller/minio.go b/pkg/controller/minio.go
index eaa7d6a92af..6f182e96d7c 100644
--- a/pkg/controller/minio.go
+++ b/pkg/controller/minio.go
@@ -92,7 +92,7 @@ func (c *Controller) deleteCSR(ctx context.Context, csrName string) error {
}
func (c *Controller) recreateMinIOCertsOnTenant(ctx context.Context, tenant *miniov2.Tenant, nsName types.NamespacedName) error {
- klog.V(2).Info("Deleting the TLS secret and CSR of expired cert on tenant %s", tenant.Name)
+ klog.V(2).Infof("Deleting the TLS secret and CSR of expired cert on tenant %s", tenant.Name)
// First delete the TLS secret of expired cert on the tenant
err := c.kubeClientSet.CoreV1().Secrets(tenant.Namespace).Delete(ctx, tenant.MinIOTLSSecretName(), metav1.DeleteOptions{})
@@ -373,13 +373,13 @@ func (c *Controller) createMinIOCSR(ctx context.Context, tenant *miniov2.Tenant)
klog.Errorf("Unexpected error during the creation of the csr/%s: %v", tenant.MinIOCSRName(), err)
return err
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "CSRCreated", "MinIO CSR Created")
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "CSRCreated", "MinIO CSR Created")
// fetch certificate from CSR
certbytes, err := c.fetchCertificate(ctx, tenant.MinIOCSRName())
if err != nil {
klog.Errorf("Unexpected error during the creation of the csr/%s: %v", tenant.MinIOCSRName(), err)
- c.RegisterEvent(ctx, tenant, corev1.EventTypeWarning, "CSRFailed", fmt.Sprintf("MinIO CSR Failed to create: %s", err))
+ c.recorder.Event(tenant, corev1.EventTypeWarning, "CSRFailed", fmt.Sprintf("MinIO CSR Failed to create: %s", err))
return err
}
diff --git a/pkg/controller/monitoring.go b/pkg/controller/monitoring.go
index f26051acbb3..cd3360d73d6 100644
--- a/pkg/controller/monitoring.go
+++ b/pkg/controller/monitoring.go
@@ -20,7 +20,7 @@ import (
"log"
"time"
- "github.com/minio/madmin-go/v2"
+ "github.com/minio/madmin-go/v3"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
@@ -38,8 +38,6 @@ const (
HealthHealingMessage = "Healing"
// HealthReduceAvailabilityMessage some drives are offline
HealthReduceAvailabilityMessage = "Reduced Availability"
- // HealthAboutToLoseQuorumMessage means we are close to losing write capabilities
- HealthAboutToLoseQuorumMessage = "About to lose quorum"
)
// recurrentTenantStatusMonitor loop that checks every 3 minutes for tenants health
@@ -122,6 +120,11 @@ func (c *Controller) updateHealthStatusForTenant(tenant *miniov2.Tenant) error {
if err != nil {
// show the error and continue
klog.Infof("'%s/%s' Failed to get cluster health: %v", tenant.Namespace, tenant.Name, err)
+ err = c.renewExternalCerts(context.Background(), tenant, err)
+ if err != nil {
+ klog.Errorf("There was an error on certificate renewal %s", err)
+ return err
+ }
return nil
}
@@ -259,80 +262,6 @@ type HealthResult struct {
WriteQuorumDrives int
}
-// HealthMode type of query we want to perform to MinIO cluster health
-type HealthMode string
-
-const (
- // MaintenanceMode query type for when we want to ask MinIO if we can take down 1 server
- MaintenanceMode HealthMode = "MaintenanceMode"
- // RegularMode query type for when we want to ask MinIO the current state of healing/health
- RegularMode = "RegularMode"
-)
-
-// processNextHealthCheckItem will read a single work item off the workqueue and
-// attempt to process it, by calling the syncHandler.
-func (c *Controller) processNextHealthCheckItem() bool {
- obj, shutdown := c.healthCheckQueue.Get()
- if shutdown {
- return false
- }
-
- // We wrap this block in a func so we can defer c.healthCheckQueue.Done.
- processItem := func(obj interface{}) error {
- // We call Done here so the healthCheckQueue knows we have finished
- // processing this item. We also must remember to call Forget if we
- // do not want this work item being re-queued. For example, we do
- // not call Forget if a transient error occurs, instead the item is
- // put back on the healthCheckQueue and attempted again after a back-off
- // period.
- defer c.healthCheckQueue.Done(obj)
- var key string
- var ok bool
- // We expect strings to come off the healthCheckQueue. These are of the
- // form namespace/name. We do this as the delayed nature of the
- // healthCheckQueue means the items in the informer cache may actually be
- // more up to date that when the item was initially put onto the
- // healthCheckQueue.
- if key, ok = obj.(string); !ok {
- // As the item in the healthCheckQueue is actually invalid, we call
- // Forget here else we'd go into a loop of attempting to
- // process a work item that is invalid.
- c.healthCheckQueue.Forget(obj)
- runtime.HandleError(fmt.Errorf("expected string in healthCheckQueue but got %#v", obj))
- return nil
- }
- klog.V(2).Infof("Key from healthCheckQueue: %s", key)
-
- result, err := c.syncHealthCheckHandler(key)
- switch {
- case err != nil:
- c.workqueue.AddRateLimited(key)
- return fmt.Errorf("error checking health check '%s': %s", key, err.Error())
- case result.RequeueAfter > 0:
- // The result.RequeueAfter request will be lost, if it is returned
- // along with a non-nil error. But this is intended as
- // We need to drive to stable reconcile loops before queuing due
- // to result.RequestAfter
- c.workqueue.Forget(obj)
- c.workqueue.AddAfter(key, result.RequeueAfter)
- case result.Requeue:
- c.workqueue.AddRateLimited(key)
- default:
- // Finally, if no error occurs we Forget this item so it does not
- // get queued again until another change happens.
- c.workqueue.Forget(obj)
- klog.V(4).Infof("Successfully health checked '%s'", key)
- }
- return nil
- }
-
- if err := processItem(obj); err != nil {
- runtime.HandleError(err)
- return true
- }
- return true
-}
-
// syncHealthCheckHandler acts on work items from the healthCheckQueue
func (c *Controller) syncHealthCheckHandler(key string) (Result, error) {
// Convert the namespace/name string into a distinct namespace and name
diff --git a/pkg/controller/operator.go b/pkg/controller/operator.go
index bdf6db38bcb..344d4ff4197 100644
--- a/pkg/controller/operator.go
+++ b/pkg/controller/operator.go
@@ -24,6 +24,7 @@ import (
"fmt"
"net"
"net/http"
+ "strings"
"time"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
@@ -60,7 +61,7 @@ const (
// DefaultDeploymentName is the default name of the operator deployment
DefaultDeploymentName = "minio-operator"
// DefaultOperatorImage is the version fo the operator being used
- DefaultOperatorImage = "minio/operator:v5.0.10"
+ DefaultOperatorImage = "minio/operator:v5.0.14"
// DefaultOperatorImageEnv is the default image to minio instance
DefaultOperatorImageEnv = "MINIO_OPERATOR_IMAGE"
)
@@ -172,6 +173,29 @@ func (c *Controller) fetchTransportCACertificates() (pool *x509.CertPool) {
rootCAs.AppendCertsFromPEM(val)
}
}
+
+ // Multi-tenancy support for external certificates
+ // One secret per tenant to allow for the automatic appending and renewal of certificates upon expiration.
+ secretsAvailableAtOperatorNS, _ := c.kubeClientSet.CoreV1().Secrets(miniov2.GetNSFromFile()).List(context.Background(), metav1.ListOptions{})
+ for _, secret := range secretsAvailableAtOperatorNS.Items {
+ // Check if secret starts with "operator-ca-tls-"
+ secretName := OperatorCATLSSecretName + "-"
+ if strings.HasPrefix(secret.Name, secretName) {
+ klog.Infof("External secret found: %s", secret.Name)
+ operatorCATLSCert, err := c.kubeClientSet.CoreV1().Secrets(miniov2.GetNSFromFile()).Get(context.Background(), secret.Name, metav1.GetOptions{})
+ if err == nil && operatorCATLSCert != nil {
+ if val, ok := operatorCATLSCert.Data["ca.crt"]; ok {
+ klog.Infof("Appending cert from %s secret", secret.Name)
+ rootCAs.AppendCertsFromPEM(val)
+ } else {
+ klog.Errorf("NOT appending %s secret, ok: %t", secret.Name, ok)
+ }
+ } else {
+ klog.Errorf("NOT appending %s secret, err: %s operatorCATLSCert: %s", secret.Name, err, operatorCATLSCert)
+ }
+ }
+ }
+
return rootCAs
}
diff --git a/pkg/controller/pools.go b/pkg/controller/pools.go
index 4d8f643daa7..1b366f07dc0 100644
--- a/pkg/controller/pools.go
+++ b/pkg/controller/pools.go
@@ -72,7 +72,10 @@ func poolSSMatchesSpec(expectedStatefulSet, existingStatefulSet *appsv1.Stateful
if !equality.Semantic.DeepEqual(expectedMetadata.Labels, existingStatefulSet.ObjectMeta.Labels) {
return false, nil
}
- expectedAnnotations := expectedMetadata.Annotations
+ expectedAnnotations := map[string]string{}
+ for k, v := range expectedMetadata.Annotations {
+ expectedAnnotations[k] = v
+ }
currentAnnotations := existingStatefulSet.ObjectMeta.Annotations
delete(expectedAnnotations, corev1.LastAppliedConfigAnnotation)
delete(currentAnnotations, corev1.LastAppliedConfigAnnotation)
diff --git a/pkg/controller/service-account.go b/pkg/controller/service-account.go
index 4ed82691c40..ede5d38b27d 100644
--- a/pkg/controller/service-account.go
+++ b/pkg/controller/service-account.go
@@ -42,9 +42,9 @@ func (c *Controller) checkAndCreateServiceAccount(ctx context.Context, tenant *m
if err != nil {
return err
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "SACreated", "Service Account Created")
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "SACreated", "Service Account Created")
} else {
- c.RegisterEvent(ctx, tenant, corev1.EventTypeWarning, "SAFailed", fmt.Sprintf("Service Account could not be created: %s", err.Error()))
+ c.recorder.Event(tenant, corev1.EventTypeWarning, "SAFailed", fmt.Sprintf("Service Account could not be created: %s", err.Error()))
return err
}
}
@@ -57,9 +57,9 @@ func (c *Controller) checkAndCreateServiceAccount(ctx context.Context, tenant *m
if err != nil {
return err
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "RoleCreated", "Role Created")
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "RoleCreated", "Role Created")
} else {
- c.RegisterEvent(ctx, tenant, corev1.EventTypeWarning, "RoleFailed", fmt.Sprintf("Role could not be created: %s", err.Error()))
+ c.recorder.Event(tenant, corev1.EventTypeWarning, "RoleFailed", fmt.Sprintf("Role could not be created: %s", err.Error()))
return err
}
}
@@ -71,9 +71,9 @@ func (c *Controller) checkAndCreateServiceAccount(ctx context.Context, tenant *m
if err != nil {
return err
}
- c.RegisterEvent(ctx, tenant, corev1.EventTypeNormal, "BindingCreated", "Role Binding Created")
+ c.recorder.Event(tenant, corev1.EventTypeNormal, "BindingCreated", "Role Binding Created")
} else {
- c.RegisterEvent(ctx, tenant, corev1.EventTypeWarning, "BindingFailed", fmt.Sprintf("Role Binding could not be created: %s", err.Error()))
+ c.recorder.Event(tenant, corev1.EventTypeWarning, "BindingFailed", fmt.Sprintf("Role Binding could not be created: %s", err.Error()))
return err
}
}
diff --git a/pkg/controller/sts.go b/pkg/controller/sts.go
index 28575aeefbd..72bb70bc6d7 100644
--- a/pkg/controller/sts.go
+++ b/pkg/controller/sts.go
@@ -10,7 +10,7 @@ import (
"time"
"github.com/gorilla/mux"
- "github.com/minio/madmin-go/v2"
+ "github.com/minio/madmin-go/v3"
"github.com/minio/minio-go/v7/pkg/credentials"
miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
xhttp "github.com/minio/operator/pkg/internal"
@@ -314,7 +314,7 @@ func GetPolicy(ctx context.Context, adminClient *madmin.AdminClient, policyName
}
// AssumeRole invokes the AssumeRole method in the Minio Tenant
-func AssumeRole(ctx context.Context, c *Controller, tenant *miniov2.Tenant, sessionPolicy string, duration int) (*credentials.Value, error) {
+func AssumeRole(ctx context.Context, c *Controller, tenant *miniov2.Tenant, region string, sessionPolicy string, duration int) (*credentials.Value, error) {
client, accessKey, secretKey, err := getTenantClient(ctx, c, tenant)
if err != nil {
return nil, err
@@ -330,6 +330,7 @@ func AssumeRole(ctx context.Context, c *Controller, tenant *miniov2.Tenant, sess
SecretKey: secretKey,
Policy: sessionPolicy,
DurationSeconds: duration,
+ Location: region,
}
stsAssumeRole := &credentials.STSAssumeRole{
diff --git a/pkg/controller/sts_handlers.go b/pkg/controller/sts_handlers.go
index 3a0c6d05087..505864ca6c0 100644
--- a/pkg/controller/sts_handlers.go
+++ b/pkg/controller/sts_handlers.go
@@ -178,6 +178,13 @@ func (c *Controller) AssumeRoleWithWebIdentityHandler(w http.ResponseWriter, r *
return
}
+ info, err := adminClient.ServerInfo(ctx)
+ if err != nil {
+ writeSTSErrorResponse(w, true, ErrSTSInternalError, fmt.Errorf("Error communicating with tenant '%s': %s", tenant.Name, err))
+ return
+ }
+ region := info.Region
+
// Session Policy
sessionPolicyStr := r.Form.Get(stsPolicy)
var compactedSessionPolicy string
@@ -236,8 +243,8 @@ func (c *Controller) AssumeRoleWithWebIdentityHandler(w http.ResponseWriter, r *
return
}
+ durationInSeconds := 3600 // Default expiration
durationStr := r.Form.Get(stsDurationSeconds)
- var durationInSeconds int
if durationStr != "" {
duration, err := strconv.Atoi(durationStr)
if err != nil {
@@ -252,7 +259,7 @@ func (c *Controller) AssumeRoleWithWebIdentityHandler(w http.ResponseWriter, r *
durationInSeconds = duration
}
- stsCredentials, err := AssumeRole(ctx, c, &tenant, bfCompact, durationInSeconds)
+ stsCredentials, err := AssumeRole(ctx, c, &tenant, region, bfCompact, durationInSeconds)
if err != nil {
writeSTSErrorResponse(w, true, ErrSTSInternalError, err)
return
@@ -264,6 +271,7 @@ func (c *Controller) AssumeRoleWithWebIdentityHandler(w http.ResponseWriter, r *
AccessKey: stsCredentials.AccessKeyID,
SecretKey: stsCredentials.SecretAccessKey,
SessionToken: stsCredentials.SessionToken,
+ Expiration: stsCredentials.Expiration,
},
},
}
diff --git a/pkg/controller/tenants.go b/pkg/controller/tenants.go
index 5f7d553e282..d869a167bdc 100644
--- a/pkg/controller/tenants.go
+++ b/pkg/controller/tenants.go
@@ -19,6 +19,10 @@ package controller
import (
"context"
"errors"
+ "strings"
+
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/klog/v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -45,6 +49,101 @@ func (c *Controller) getTenantConfiguration(ctx context.Context, tenant *miniov2
return tenantConfiguration, nil
}
+// renewCert will renew one certificate at a time
+func (c *Controller) renewCert(secret corev1.Secret, index int, tenant *miniov2.Tenant) error {
+ // Check if secret starts with "operator-ca-tls-"
+ secretName := OperatorCATLSSecretName + "-"
+ // If the secret does not start with "operator-ca-tls-" then no need to continue
+ if !strings.HasPrefix(secret.Name, secretName) {
+ klog.Info("No secret found for multi-tenancy architecture of external certificates")
+ return nil
+ }
+ klog.Infof("%d external secret found: %s", index, secret.Name)
+ klog.Info("We are going to renew the external certificate for the tenant...")
+ // Get the new certificate generated by cert-manager
+ tenantSecretName := tenant.Spec.ExternalCertSecret[0].Name
+ data, err := c.kubeClientSet.CoreV1().Secrets(tenant.Namespace).Get(context.Background(), tenantSecretName, metav1.GetOptions{})
+ if err != nil {
+ klog.Errorf("Couldn't get the certificate due to error %s", err)
+ return err
+ }
+ if data == nil || len(data.Data) <= 0 {
+ klog.Errorf("certificate's data can't be empty: %s", data)
+ return errors.New("empty cert data")
+ }
+ CACertificate := data.Data["ca.crt"]
+ if CACertificate == nil || len(CACertificate) <= 0 {
+ klog.Errorf("ca.crt certificate data can't be empty: %s", CACertificate)
+ return errors.New("empty cert ca data")
+ }
+ klog.Info("certificate data is not empty, proceed with renewal")
+ // Delete the secret that starts with operator-ca-tls- because it is expired
+ err = c.kubeClientSet.CoreV1().Secrets(miniov2.GetNSFromFile()).Delete(context.Background(), secret.Name, metav1.DeleteOptions{})
+ if err != nil {
+ klog.Infof("There was an error when deleting the secret: %s", err)
+ return err
+ }
+ // Create the new secret that contains the new certificate
+ newSecret := &corev1.Secret{
+ Type: "Opaque",
+ ObjectMeta: metav1.ObjectMeta{
+ Name: secret.Name,
+ Namespace: miniov2.GetNSFromFile(),
+ },
+ Data: map[string][]byte{
+ "ca.crt": CACertificate,
+ },
+ }
+ _, err = c.kubeClientSet.CoreV1().Secrets(miniov2.GetNSFromFile()).Create(context.Background(), newSecret, metav1.CreateOptions{})
+ if err != nil {
+ klog.Errorf("Secret not created %s", err)
+ return err
+ }
+ // Append it
+ c.fetchTransportCACertificates()
+ // Reload CA certificates
+ c.createTransport()
+ // Rollout the Operator Deployment to use new certificate and trust the tenant.
+ operatorDeployment, err := c.kubeClientSet.AppsV1().Deployments(miniov2.GetNSFromFile()).Get(context.Background(), miniov2.GetNSFromFile(), metav1.GetOptions{})
+ if err != nil || operatorDeployment == nil {
+ klog.Errorf("Couldn't retrieve the deployment %s", err)
+ return err
+ }
+ operatorDeployment.Spec.Template.ObjectMeta.Name = miniov2.GetNSFromFile()
+ operatorDeployment, err = c.kubeClientSet.AppsV1().Deployments(miniov2.GetNSFromFile()).Update(context.Background(), operatorDeployment, metav1.UpdateOptions{})
+ if err != nil {
+ klog.Errorf("There was an error on deployment update %s", err)
+ return err
+ }
+ klog.Info("external certificate successfully renewed for the tenant")
+ return nil
+}
+
+// renewExternalCerts renews external certificates when they expire, ensuring that the Operator trusts its tenants.
+func (c *Controller) renewExternalCerts(ctx context.Context, tenant *miniov2.Tenant, err error) error {
+ if strings.Contains(err.Error(), "failed to verify certificate") {
+ externalCertSecret := tenant.Spec.ExternalCertSecret
+ klog.Info("Let's check if there is an external cert for the tenant...")
+ if externalCertSecret != nil {
+ // Check that there is a secret that starts with "operator-ca-tls-" to proceed with the renewal
+ secretsAvailableAtOperatorNS, err := c.kubeClientSet.CoreV1().Secrets(miniov2.GetNSFromFile()).List(context.Background(), metav1.ListOptions{})
+ if err != nil {
+ klog.Info("No external certificates are found under the multi-tenancy architecture to handle.")
+ return nil
+ }
+ klog.Info("there are secret(s) for the operator")
+ for index, secret := range secretsAvailableAtOperatorNS.Items {
+ err = c.renewCert(secret, index, tenant)
+ if err != nil {
+ klog.Errorf("There was an error while renewing the cert: %s", err)
+ return err
+ }
+ }
+ }
+ }
+ return nil
+}
+
// getTenantCredentials returns a combination of env, credsSecret and Configuration tenant credentials
func (c *Controller) getTenantCredentials(ctx context.Context, tenant *miniov2.Tenant) (map[string][]byte, error) {
// Configuration for tenant can be passed using 2 different sources, tenant.spec.env and config.env secret
diff --git a/pkg/controller/tls.go b/pkg/controller/tls.go
index 6bb46b984e7..c29521fe23d 100644
--- a/pkg/controller/tls.go
+++ b/pkg/controller/tls.go
@@ -158,7 +158,7 @@ func (c *Controller) checkAndCreateCSR(ctx context.Context, deployment metav1.Ob
}
if err != nil {
if k8serrors.IsNotFound(err) {
- klog.V(2).Infof("Creating a new Certificate Signing Request for %s Server Certs, cluster %q", serviceName)
+ klog.V(2).Infof("Creating a new Certificate Signing Request for %s Server Certs.", serviceName)
if err = c.createAndStoreCSR(ctx, deployment, serviceName, csrName, secretName); err != nil {
return err
}
diff --git a/pkg/controller/upgrades.go b/pkg/controller/upgrades.go
index e3f33f2aada..2f7b304636e 100644
--- a/pkg/controller/upgrades.go
+++ b/pkg/controller/upgrades.go
@@ -41,6 +41,8 @@ const (
version430 = "v4.3.0"
version45 = "v4.5"
version500 = "v5.0.0"
+ // currentVersion will point to the latest released update version
+ currentVersion = version500
)
// Legacy const
@@ -62,7 +64,13 @@ func (c *Controller) checkForUpgrades(ctx context.Context, tenant *miniov2.Tenan
version500: c.upgrade500,
}
- // if the version is not empty, this is not a new tenant, upgrade accordingly
+ // if tenant has no version we mark it with latest version upgrade released
+ if tenant.Status.SyncVersion == "" {
+ tenant.Status.SyncVersion = currentVersion
+ return c.updateTenantSyncVersion(ctx, tenant, version500)
+ }
+
+ // if the version is empty, upgrades might not been applied, we apply them all
if tenant.Status.SyncVersion != "" {
currentSyncVersion, err := version.NewVersion(tenant.Status.SyncVersion)
if err != nil {
diff --git a/pkg/internal/http.go b/pkg/internal/http.go
index 3b4b3513d0e..8d5f93cd5a7 100644
--- a/pkg/internal/http.go
+++ b/pkg/internal/http.go
@@ -30,27 +30,10 @@ var (
// Standard S3 HTTP response constants
const (
- LastModified = "Last-Modified"
- Date = "Date"
- ETag = "ETag"
- ContentType = "Content-Type"
- ContentMD5 = "Content-Md5"
- ContentEncoding = "Content-Encoding"
- Expires = "Expires"
- ContentLength = "Content-Length"
- ContentLanguage = "Content-Language"
- ContentRange = "Content-Range"
- Connection = "Connection"
- AcceptRanges = "Accept-Ranges"
- AmzBucketRegion = "X-Amz-Bucket-Region"
- ServerInfo = "Server"
- RetryAfter = "Retry-After"
- Location = "Location"
- CacheControl = "Cache-Control"
- ContentDisposition = "Content-Disposition"
- Authorization = "Authorization"
- Action = "Action"
- Range = "Range"
+ ContentType = "Content-Type"
+ ContentLength = "Content-Length"
+ AcceptRanges = "Accept-Ranges"
+ ServerInfo = "Server"
)
// mimeType represents various MIME type used API responses.
@@ -59,8 +42,6 @@ type mimeType string
const (
// MimeNone Means no response type.
MimeNone mimeType = ""
- // MimeJSON Means response type is JSON.
- MimeJSON mimeType = "application/json"
// MimeXML Means response type is XML.
MimeXML mimeType = "application/xml"
)
diff --git a/pkg/logger/config/config.go b/pkg/logger/config/config.go
index ccb30cf28ef..7b3149aef4d 100644
--- a/pkg/logger/config/config.go
+++ b/pkg/logger/config/config.go
@@ -17,7 +17,7 @@
package config
import (
- "github.com/minio/madmin-go/v2"
+ "github.com/minio/madmin-go/v3"
)
// Default keys
diff --git a/pkg/resources/configmaps/prometheus.go b/pkg/resources/configmaps/prometheus.go
index 9028f3bc43d..df7f8455cc3 100644
--- a/pkg/resources/configmaps/prometheus.go
+++ b/pkg/resources/configmaps/prometheus.go
@@ -25,7 +25,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
- v2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
)
type globalConfig struct {
@@ -83,14 +82,14 @@ func GetPrometheusConfig(t *miniov2.Tenant, accessKey, secretKey string) *Promet
// populate config
promConfig := &PrometheusConfig{
Global: globalConfig{
- ScrapeInterval: v2.MinIOPrometheusScrapeInterval,
+ ScrapeInterval: miniov2.MinIOPrometheusScrapeInterval,
EvaluationInterval: 30 * time.Second,
},
ScrapeConfigs: []ScrapeConfig{
{
JobName: t.PrometheusConfigJobName(),
BearerToken: bearerToken,
- MetricsPath: v2.MinIOPrometheusPathCluster,
+ MetricsPath: miniov2.MinIOPrometheusPathCluster,
Scheme: minioScheme,
TLSConfig: tlsConfig{
CAFile: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
diff --git a/pkg/resources/services/service.go b/pkg/resources/services/service.go
index 3212e6b7953..cd6decec876 100644
--- a/pkg/resources/services/service.go
+++ b/pkg/resources/services/service.go
@@ -89,6 +89,8 @@ func NewClusterIPForConsole(t *miniov2.Tenant) *corev1.Service {
}
if t.Spec.ServiceMetadata != nil && t.Spec.ServiceMetadata.ConsoleServiceLabels != nil {
labels = miniov2.MergeMaps(internalLabels, t.Spec.ServiceMetadata.ConsoleServiceLabels)
+ } else {
+ labels = internalLabels
}
if t.Spec.ServiceMetadata != nil && t.Spec.ServiceMetadata.ConsoleServiceAnnotations != nil {
diff --git a/pkg/resources/statefulsets/kes-statefulset.go b/pkg/resources/statefulsets/kes-statefulset.go
index 8eb777e8608..49b04091602 100644
--- a/pkg/resources/statefulsets/kes-statefulset.go
+++ b/pkg/resources/statefulsets/kes-statefulset.go
@@ -17,6 +17,7 @@ package statefulsets
import (
"sort"
+ operatorApi "github.com/minio/operator/api"
miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
@@ -94,7 +95,15 @@ func KESEnvironmentVars(t *miniov2.Tenant) []corev1.EnvVar {
// KESServerContainer returns the KES container for a KES StatefulSet.
func KESServerContainer(t *miniov2.Tenant) corev1.Container {
// Args to start KES with config mounted at miniov2.KESConfigMountPath and require but don't verify mTLS authentication
- args := []string{"server", "--config=" + miniov2.KESConfigMountPath + "/server-config.yaml", "--auth=off"}
+ args := []string{"server", "--config=" + miniov2.KESConfigMountPath + "/server-config.yaml"}
+
+ kesVersion, _ := operatorApi.GetKesConfigVersion(t.Spec.KES.Image)
+ // Add `--auth` flag only on config versions that are still compatible with it (v1 and v2).
+ // Starting KES 2023-11-09T17-35-47Z (v3) is no longer supported.
+ switch kesVersion {
+ case operatorApi.KesConfigVersion1, operatorApi.KesConfigVersion2:
+ args = append(args, "--auth=off")
+ }
return corev1.Container{
Name: miniov2.KESContainerName,
@@ -109,6 +118,7 @@ func KESServerContainer(t *miniov2.Tenant) corev1.Container {
Args: args,
Env: KESEnvironmentVars(t),
Resources: t.Spec.KES.Resources,
+ SecurityContext: kesContainerSecurityContext(t),
}
}
@@ -133,6 +143,49 @@ func kesSecurityContext(t *miniov2.Tenant) *corev1.PodSecurityContext {
return &securityContext
}
+// Builds the security context for kes containers
+func kesContainerSecurityContext(t *miniov2.Tenant) *corev1.SecurityContext {
+ // Default values:
+ // By default, values should be totally empty if not provided
+ // This is specially needed in OpenShift where Security Context Constraints restrict them
+ // if let empty then OCP can pick the values from the constraints defined.
+ containerSecurityContext := corev1.SecurityContext{}
+ runAsNonRoot := true
+ var runAsUser int64 = 1000
+ var runAsGroup int64 = 1000
+ poolSCSet := false
+
+ // Values from pool.SecurityContext ONLY if provided
+ if t.Spec.KES != nil && t.Spec.KES.SecurityContext != nil {
+ if t.Spec.KES.SecurityContext.RunAsNonRoot != nil {
+ runAsNonRoot = *t.Spec.KES.SecurityContext.RunAsNonRoot
+ poolSCSet = true
+ }
+ if t.Spec.KES.SecurityContext.RunAsUser != nil {
+ runAsUser = *t.Spec.KES.SecurityContext.RunAsUser
+ poolSCSet = true
+ }
+ if t.Spec.KES.SecurityContext.RunAsGroup != nil {
+ runAsGroup = *t.Spec.KES.SecurityContext.RunAsGroup
+ poolSCSet = true
+ }
+ if poolSCSet {
+ // Only set values if one of above is set otherwise let it empty
+ containerSecurityContext = corev1.SecurityContext{
+ RunAsNonRoot: &runAsNonRoot,
+ RunAsUser: &runAsUser,
+ RunAsGroup: &runAsGroup,
+ }
+ }
+ }
+
+ // Values from kes.ContainerSecurityContext if provided
+ if t.Spec.KES != nil && t.Spec.KES.ContainerSecurityContext != nil {
+ containerSecurityContext = *t.Spec.KES.ContainerSecurityContext
+ }
+ return &containerSecurityContext
+}
+
// NewForKES creates a new KES StatefulSet for the given Cluster.
func NewForKES(t *miniov2.Tenant, serviceName string) *appsv1.StatefulSet {
replicas := t.KESReplicas()
diff --git a/pkg/resources/statefulsets/minio-statefulset.go b/pkg/resources/statefulsets/minio-statefulset.go
index 58722e67f5d..b0ca892cb85 100644
--- a/pkg/resources/statefulsets/minio-statefulset.go
+++ b/pkg/resources/statefulsets/minio-statefulset.go
@@ -27,7 +27,6 @@ import (
miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
- v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
@@ -154,6 +153,10 @@ func minioEnvironmentVars(t *miniov2.Tenant, skipEnvVars map[string][]byte, opVe
Name: "MINIO_KMS_KES_CA_PATH",
Value: miniov2.MinIOCertPath + "/CAs/kes.crt",
}
+ envVarsMap["MINIO_KMS_KES_CAPATH"] = corev1.EnvVar{
+ Name: "MINIO_KMS_KES_CAPATH",
+ Value: miniov2.MinIOCertPath + "/CAs/kes.crt",
+ }
envVarsMap["MINIO_KMS_KES_KEY_NAME"] = corev1.EnvVar{
Name: "MINIO_KMS_KES_KEY_NAME",
Value: t.Spec.KES.KeyName,
@@ -253,7 +256,7 @@ var TmpCfgVolumeMount = corev1.VolumeMount{
}
// Builds the volume mounts for MinIO container.
-func volumeMounts(t *miniov2.Tenant, pool *miniov2.Pool, certVolumeSources []v1.VolumeProjection) (mounts []v1.VolumeMount) {
+func volumeMounts(t *miniov2.Tenant, pool *miniov2.Pool, certVolumeSources []corev1.VolumeProjection) (mounts []corev1.VolumeMount) {
// Default volume name, unless another one was provided
name := miniov2.MinIOVolumeName
if pool.VolumeClaimTemplate != nil {
@@ -290,7 +293,7 @@ func volumeMounts(t *miniov2.Tenant, pool *miniov2.Pool, certVolumeSources []v1.
}
// Builds the MinIO container for a Tenant.
-func poolMinioServerContainer(t *miniov2.Tenant, skipEnvVars map[string][]byte, pool *miniov2.Pool, hostsTemplate string, opVersion string, certVolumeSources []v1.VolumeProjection) v1.Container {
+func poolMinioServerContainer(t *miniov2.Tenant, skipEnvVars map[string][]byte, pool *miniov2.Pool, hostsTemplate string, opVersion string, certVolumeSources []corev1.VolumeProjection) corev1.Container {
consolePort := miniov2.ConsolePort
if t.TLS() {
consolePort = miniov2.ConsoleTLSPort
@@ -316,7 +319,7 @@ func poolMinioServerContainer(t *miniov2.Tenant, skipEnvVars map[string][]byte,
"--sftp", fmt.Sprintf("address=:%d", miniov2.MinIOSFTPPort),
"--sftp", "ssh-private-key=" + pkFile,
}...)
- containerPorts = append(containerPorts, v1.ContainerPort{
+ containerPorts = append(containerPorts, corev1.ContainerPort{
ContainerPort: miniov2.MinIOSFTPPort,
})
}
@@ -380,7 +383,7 @@ func poolTopologySpreadConstraints(z *miniov2.Pool) []corev1.TopologySpreadConst
}
// Builds the security context for a Pool
-func poolSecurityContext(pool *miniov2.Pool, status *miniov2.PoolStatus) *v1.PodSecurityContext {
+func poolSecurityContext(pool *miniov2.Pool, status *miniov2.PoolStatus) *corev1.PodSecurityContext {
runAsNonRoot := true
var runAsUser int64 = 1000
var runAsGroup int64 = 1000
@@ -410,7 +413,7 @@ func poolSecurityContext(pool *miniov2.Pool, status *miniov2.PoolStatus) *v1.Pod
}
// Builds the security context for containers in a Pool
-func poolContainerSecurityContext(pool *miniov2.Pool) *v1.SecurityContext {
+func poolContainerSecurityContext(pool *miniov2.Pool) *corev1.SecurityContext {
// Default values:
// By default, values should be totally empty if not provided
// This is specially needed in OpenShift where Security Context Constraints restrict them
@@ -497,7 +500,7 @@ func NewPool(args *NewPoolArgs) *appsv1.StatefulSet {
podVolumes = append(podVolumes, corev1.Volume{
Name: CfgVol,
- VolumeSource: v1.VolumeSource{
+ VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
})
@@ -878,7 +881,7 @@ func NewPool(args *NewPoolArgs) *appsv1.StatefulSet {
return ss
}
-func getInitContainer(t *miniov2.Tenant, operatorImage string, pool *miniov2.Pool) v1.Container {
+func getInitContainer(t *miniov2.Tenant, operatorImage string, pool *miniov2.Pool) corev1.Container {
initContainer := corev1.Container{
Name: "validate-arguments",
Image: operatorImage,
@@ -908,7 +911,7 @@ func getInitContainer(t *miniov2.Tenant, operatorImage string, pool *miniov2.Poo
return initContainer
}
-func getSideCarContainer(t *miniov2.Tenant, operatorImage string, pool *miniov2.Pool) v1.Container {
+func getSideCarContainer(t *miniov2.Tenant, operatorImage string, pool *miniov2.Pool) corev1.Container {
sidecarContainer := corev1.Container{
Name: "sidecar",
Image: operatorImage,
diff --git a/pkg/subnet/subnet.go b/pkg/subnet/subnet.go
index 193cb44c75d..931508e3ce8 100644
--- a/pkg/subnet/subnet.go
+++ b/pkg/subnet/subnet.go
@@ -20,7 +20,7 @@ package subnet
import (
"errors"
- "github.com/minio/madmin-go/v2"
+ "github.com/minio/madmin-go/v3"
mc "github.com/minio/mc/cmd"
"github.com/minio/operator/pkg/http"
diff --git a/pkg/subnet/utils.go b/pkg/subnet/utils.go
index 32301fe75f4..e32b9e76aae 100644
--- a/pkg/subnet/utils.go
+++ b/pkg/subnet/utils.go
@@ -27,7 +27,7 @@ import (
xhttp "github.com/minio/operator/pkg/http"
- "github.com/minio/madmin-go/v2"
+ "github.com/minio/madmin-go/v3"
mc "github.com/minio/mc/cmd"
"github.com/minio/pkg/env"
)
diff --git a/pkg/utils/miniojob/minioJob.go b/pkg/utils/miniojob/minioJob.go
new file mode 100644
index 00000000000..c5921d0f6ee
--- /dev/null
+++ b/pkg/utils/miniojob/minioJob.go
@@ -0,0 +1,250 @@
+// This file is part of MinIO Operator
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package miniojob
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+)
+
+// ArgType - arg type
+type ArgType int
+
+const (
+ // ArgTypeKey - key=value print value
+ ArgTypeKey ArgType = iota
+ // ArgTypeFile - key=value print /temp/value.ext
+ ArgTypeFile
+ // ArgTypeKeyFile - key=value print key="/temp/value.ext"
+ ArgTypeKeyFile
+)
+
+// Arg - parse the arg result
+type Arg struct {
+ Command string
+ FileName string
+ FileExt string
+ FileContext string
+ ArgType ArgType
+}
+
+// FieldsFunc - alias function
+type FieldsFunc func(args map[string]string) (Arg, error)
+
+// Key - key=value|value1,value2,value3
+func Key(key string) FieldsFunc {
+ return KeyFormat(key, "$0")
+}
+
+// FLAGS - --key=""|value|value1,value2,value3
+func FLAGS(ignoreKeys ...string) FieldsFunc {
+ return prefixKeyFormat("-", ignoreKeys...)
+}
+
+// ALIAS - myminio
+func ALIAS() FieldsFunc {
+ return Static("myminio")
+}
+
+// Static - some static value
+func Static(val string) FieldsFunc {
+ return func(args map[string]string) (Arg, error) {
+ return Arg{Command: val}, nil
+ }
+}
+
+// File - fName is the the key, value is content, ext is the file ext
+func File(fName string, ext string) FieldsFunc {
+ return func(args map[string]string) (out Arg, err error) {
+ if args == nil {
+ return out, fmt.Errorf("args is nil")
+ }
+ if val, ok := args[fName]; ok {
+ if val == "" {
+ return out, fmt.Errorf("value is empty")
+ }
+ out.FileName = fName
+ out.FileExt = ext
+ out.FileContext = strings.TrimSpace(val)
+ out.ArgType = ArgTypeFile
+ delete(args, fName)
+ return out, nil
+ }
+ return out, fmt.Errorf("file %s not found", fName)
+ }
+}
+
+// KeyValue - match key and putout the key, like endpoint="https://webhook-1.example.net"
+func KeyValue(key string) FieldsFunc {
+ return func(args map[string]string) (out Arg, err error) {
+ if args == nil {
+ return out, fmt.Errorf("args is nil")
+ }
+ val, ok := args[key]
+ if !ok {
+ return out, fmt.Errorf("key %s not found", key)
+ }
+ out.Command = fmt.Sprintf(`%s="%s"`, key, val)
+ delete(args, key)
+ return out, nil
+ }
+}
+
+// KeyFile - match key and putout the key, like client_cert="[here is content]"
+func KeyFile(key string, ext string) FieldsFunc {
+ return func(args map[string]string) (out Arg, err error) {
+ if args == nil {
+ return out, fmt.Errorf("args is nil")
+ }
+ val, ok := args[key]
+ if !ok {
+ return out, fmt.Errorf("key %s not found", key)
+ }
+ out.FileName = key
+ out.FileExt = ext
+ out.FileContext = strings.TrimSpace(val)
+ out.ArgType = ArgTypeKeyFile
+ delete(args, key)
+ return out, nil
+ }
+}
+
+// Option - ignore the error
+func Option(opt FieldsFunc) FieldsFunc {
+ return func(args map[string]string) (out Arg, err error) {
+ if args == nil {
+ return out, nil
+ }
+ out, _ = opt(args)
+ return out, nil
+ }
+}
+
+// KeyFormat - match key and get outPut to replace $0 to output the value
+// if format not contain $0, will add $0 to the end
+func KeyFormat(key string, format string) FieldsFunc {
+ return func(args map[string]string) (out Arg, err error) {
+ if args == nil {
+ return out, fmt.Errorf("args is nil")
+ }
+ if !strings.Contains(format, "$0") {
+ format = fmt.Sprintf("%s %s", format, "$0")
+ }
+ val, ok := args[key]
+ if !ok {
+ return out, fmt.Errorf("key %s not found", key)
+ }
+ out.Command = strings.ReplaceAll(format, "$0", strings.ReplaceAll(val, ",", " "))
+ delete(args, key)
+ return out, nil
+ }
+}
+
+// OthersKeyValues - get all the key values
+func OthersKeyValues(ignoreFileKeys ...string) FieldsFunc {
+ return func(args map[string]string) (out Arg, err error) {
+ if args == nil {
+ return out, fmt.Errorf("args is nil")
+ }
+ data := []string{}
+ for key, val := range args {
+ if val != "" {
+ data = append(data, fmt.Sprintf(`%s="%s"`, key, val))
+ } else {
+ data = append(data, key)
+ }
+ delete(args, key)
+ }
+ sort.Slice(data, func(i, j int) bool {
+ return data[i] < data[j]
+ })
+ return Arg{Command: strings.Join(data, " ")}, nil
+ }
+}
+
+// OneOf - one of the funcs must be found
+// mc admin policy attach OneOf(--user | --group) = mc admin policy attach --user user or mc admin policy attach --group group
+func OneOf(funcs ...FieldsFunc) FieldsFunc {
+ return func(args map[string]string) (out Arg, err error) {
+ if args == nil {
+ return out, fmt.Errorf("args is nil")
+ }
+ for _, fn := range funcs {
+ if out, err = fn(args); err == nil {
+ return out, nil
+ }
+ }
+ return out, fmt.Errorf("not found")
+ }
+}
+
+// Sanitize - no space for the command
+// mc mb Sanitize(alias / bucketName) = mc mb alias/bucketName
+func Sanitize(funcs ...FieldsFunc) FieldsFunc {
+ return func(args map[string]string) (out Arg, err error) {
+ if args == nil {
+ return out, fmt.Errorf("args is nil")
+ }
+ commands := []string{}
+ for _, func1 := range funcs {
+ if out, err = func1(args); err != nil {
+ return out, err
+ }
+ if out.Command == "" {
+ return out, fmt.Errorf("command is empty")
+ }
+ commands = append(commands, out.Command)
+ }
+ return Arg{Command: strings.Join(commands, "")}, nil
+ }
+}
+
+var prefixKeyFormat = func(pkey string, ignoreKeys ...string) FieldsFunc {
+ return func(args map[string]string) (out Arg, err error) {
+ if args == nil {
+ return out, fmt.Errorf("args is nil")
+ }
+ igrnoreKeyMap := make(map[string]bool)
+ for _, key := range ignoreKeys {
+ if !strings.HasPrefix(key, pkey) {
+ key = fmt.Sprintf("%s%s%s", pkey, pkey, key)
+ }
+ igrnoreKeyMap[key] = true
+ }
+ data := []string{}
+ for key, val := range args {
+ if strings.HasPrefix(key, pkey) && !igrnoreKeyMap[key] {
+ if val == "" {
+ data = append(data, key)
+ } else {
+ for _, singalVal := range strings.Split(val, ",") {
+ if strings.TrimSpace(singalVal) != "" {
+ data = append(data, fmt.Sprintf("%s=%s", key, singalVal))
+ }
+ }
+ }
+ delete(args, key)
+ }
+ }
+ // avoid flags change the order
+ sort.Slice(data, func(i, j int) bool {
+ return data[i] < data[j]
+ })
+ return Arg{Command: strings.Join(data, " ")}, nil
+ }
+}
diff --git a/pkg/utils/miniojob/minioJob_test.go b/pkg/utils/miniojob/minioJob_test.go
new file mode 100644
index 00000000000..b6fb7a08c4c
--- /dev/null
+++ b/pkg/utils/miniojob/minioJob_test.go
@@ -0,0 +1,337 @@
+// This file is part of MinIO Operator
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package miniojob
+
+import "testing"
+
+func TestParser(t *testing.T) {
+ args := map[string]string{
+ "--user": "a1,b2,c3,d4",
+ "user": "a,b,c,d",
+ "group": "group1,group2,group3",
+ "password": "somepassword",
+ "--with-locks": "",
+ "--region": "us-west-2",
+ "policy": ` {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": [
+ "s3:*"
+ ],
+ "Resource": [
+ "arn:aws:s3:::memes",
+ "arn:aws:s3:::memes/*"
+ ]
+ }
+ ]
+ }`,
+ "name": "mybucketName",
+ }
+ testCase := []struct {
+ command FieldsFunc
+ args map[string]string
+ expect Arg
+ expectError bool
+ }{
+ {
+ command: FLAGS("--user"),
+ args: copyArgs(args),
+ expect: Arg{Command: "--region=us-west-2 --with-locks"},
+ expectError: false,
+ },
+ {
+ command: FLAGS("user"),
+ args: copyArgs(args),
+ expect: Arg{Command: "--region=us-west-2 --with-locks"},
+ expectError: false,
+ },
+ {
+ command: Key("password"),
+ args: copyArgs(args),
+ expect: Arg{Command: "somepassword"},
+ expectError: false,
+ },
+ {
+ command: KeyFormat("user", "--user $0"),
+ args: copyArgs(args),
+ expect: Arg{Command: "--user a b c d"},
+ expectError: false,
+ },
+ {
+ command: KeyFormat("user", "--user"),
+ args: copyArgs(args),
+ expect: Arg{Command: "--user a b c d"},
+ expectError: false,
+ },
+ {
+ command: ALIAS(),
+ args: copyArgs(args),
+ expect: Arg{Command: "myminio"},
+ expectError: false,
+ },
+ {
+ command: Static("test-static"),
+ args: copyArgs(args),
+ expect: Arg{Command: "test-static"},
+ expectError: false,
+ },
+ {
+ command: File("policy", "json"),
+ args: copyArgs(args),
+ expect: Arg{
+ FileName: "policy",
+ FileExt: "json",
+ FileContext: `{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": [
+ "s3:*"
+ ],
+ "Resource": [
+ "arn:aws:s3:::memes",
+ "arn:aws:s3:::memes/*"
+ ]
+ }
+ ]
+ }`,
+ },
+ expectError: false,
+ },
+ {
+ command: OneOf(KeyFormat("user", "--user"), KeyFormat("group", "--group")),
+ args: copyArgs(args),
+ expect: Arg{Command: "--user a b c d"},
+ expectError: false,
+ },
+ {
+ command: OneOf(KeyFormat("miss_user", "--user"), KeyFormat("group", "--group")),
+ args: copyArgs(args),
+ expect: Arg{Command: "--group group1 group2 group3"},
+ expectError: false,
+ },
+ {
+ command: OneOf(KeyFormat("miss_user", "--user"), KeyFormat("miss_group", "--group")),
+ args: copyArgs(args),
+ expect: Arg{Command: "--group group1 group2 group3"},
+ expectError: true,
+ },
+ {
+ command: Sanitize(ALIAS(), Static("/"), Key("name")),
+ args: copyArgs(args),
+ expect: Arg{Command: "myminio/mybucketName"},
+ expectError: false,
+ },
+ }
+ for _, tc := range testCase {
+ cmd, err := tc.command(tc.args)
+ if tc.expectError && err == nil {
+ t.Fatalf("expectCommand error")
+ }
+ if !tc.expectError && err != nil {
+ t.Fatalf("expectCommand not a error")
+ }
+ if !tc.expectError {
+ if tc.expect.Command != "" && cmd.Command != tc.expect.Command {
+ t.Fatalf("expectCommand %s, but got %s", tc.expect.Command, cmd.Command)
+ }
+ if tc.expect.FileName != "" {
+ if tc.expect.FileContext != cmd.FileContext {
+ t.Fatalf("expectCommand %s, but got %s", tc.expect.FileContext, cmd.FileContext)
+ }
+ if tc.expect.FileExt != cmd.FileExt {
+ t.Fatalf("expectCommand %s, but got %s", tc.expect.FileExt, cmd.FileExt)
+ }
+ if tc.expect.FileName != cmd.FileName {
+ t.Fatalf("expectCommand %s, but got %s", tc.expect.FileName, cmd.FileName)
+ }
+ }
+ }
+ }
+}
+
+func TestAdminPolicyCreate(t *testing.T) {
+ mcCommand := "admin/policy/create"
+ funcs := JobOperation[mcCommand]
+ testCase := []struct {
+ name string
+ args map[string]string
+ expectError bool
+ expectCommand string
+ expectFileNumber int
+ }{
+ {
+ name: "testFull",
+ args: map[string]string{
+ "name": "mypolicy",
+ "policy": "JsonContent",
+ },
+ expectCommand: "myminio mypolicy /temp/policy.json",
+ expectFileNumber: 1,
+ },
+ {
+ name: "testError1",
+ args: map[string]string{
+ "name": "mypolicy",
+ },
+ expectCommand: "",
+ expectError: true,
+ },
+ {
+ name: "testError2",
+ args: map[string]string{
+ "policy": "JsonContent",
+ },
+ expectCommand: "",
+ expectError: true,
+ },
+ }
+ for _, tc := range testCase {
+ command, err := GenerateMinIOIntervalJobCommand(mcCommand, 0, nil, "test", tc.args, funcs)
+ if !tc.expectError {
+ if err != nil {
+ t.Fatal(err)
+ }
+ if command.Command != tc.expectCommand {
+ t.Fatalf("[%s] expectCommand %s, but got %s", tc.name, tc.expectCommand, command.Command)
+ }
+ } else {
+ if err == nil {
+ t.Fatalf("[%s] expectCommand error", tc.name)
+ }
+ }
+ }
+}
+
+func TestMCConfigSet(t *testing.T) {
+ mcCommand := "admin/config/set"
+ funcs := JobOperation[mcCommand]
+ testCase := []struct {
+ name string
+ args map[string]string
+ expectCommand string
+ expectError bool
+ expectFileNumber int
+ }{
+ {
+ name: "testFull",
+ args: map[string]string{
+ "webhookName": "webhook1",
+ "endpoint": "endpoint1",
+ "auth_token": "token1",
+ "client_cert": "cert1",
+ "client_key": "key1",
+ },
+ expectCommand: "myminio webhook1 endpoint=\"endpoint1\" client_key=\"/temp/client_key.key\" client_cert=\"/temp/client_cert.pem\" auth_token=\"token1\"",
+ expectFileNumber: 2,
+ },
+ {
+ name: "testOptionFile",
+ args: map[string]string{
+ "webhookName": "webhook1",
+ "endpoint": "endpoint1",
+ "auth_token": "token1",
+ "client_key": "key1",
+ },
+ expectCommand: "myminio webhook1 endpoint=\"endpoint1\" client_key=\"/temp/client_key.key\" auth_token=\"token1\"",
+ expectFileNumber: 1,
+ },
+ {
+ name: "testOptionKeyValue",
+ args: map[string]string{
+ "webhookName": "webhook1",
+ "endpoint": "endpoint1",
+ "client_key": "key1",
+ },
+ expectCommand: "myminio webhook1 endpoint=\"endpoint1\" client_key=\"/temp/client_key.key\"",
+ expectFileNumber: 1,
+ },
+ {
+ name: "notify_mysql",
+ args: map[string]string{
+ "webhookName": "notify_mysql",
+ "dsn_string": "username:password@tcp(mysql.example.com:3306)/miniodb",
+ "table": "minioevents",
+ "format": "namespace",
+ },
+ expectCommand: "myminio notify_mysql dsn_string=\"username:password@tcp(mysql.example.com:3306)/miniodb\" format=\"namespace\" table=\"minioevents\"",
+ expectFileNumber: 0,
+ },
+ {
+ name: "notify_amqp",
+ args: map[string]string{
+ "webhookName": "notify_amqp:primary",
+ "url": "user:password@amqp://amqp-endpoint.example.net:5672",
+ },
+ expectCommand: "myminio notify_amqp:primary url=\"user:password@amqp://amqp-endpoint.example.net:5672\"",
+ expectFileNumber: 0,
+ },
+ {
+ name: "notify_elasticsearch",
+ args: map[string]string{
+ "webhookName": "notify_elasticsearch:primary",
+ "url": "user:password@https://elasticsearch-endpoint.example.net:9200",
+ "index": "bucketevents",
+ "format": "namespace",
+ },
+ expectCommand: "myminio notify_elasticsearch:primary format=\"namespace\" index=\"bucketevents\" url=\"user:password@https://elasticsearch-endpoint.example.net:9200\"",
+ expectFileNumber: 0,
+ },
+ {
+ name: "identity_ldap",
+ args: map[string]string{
+ "webhookName": "identity_ldap",
+ "enabled": "true",
+ "server_addr": "ad-ldap.example.net/",
+ "lookup_bind_dn": "cn=miniolookupuser,dc=example,dc=net",
+ "lookup_bind_dn_password": "userpassword",
+ "user_dn_search_base_dn": "dc=example,dc=net",
+ "user_dn_search_filter": "(&(objectCategory=user)(sAMAccountName=%s))",
+ },
+ expectCommand: "myminio identity_ldap enabled=\"true\" lookup_bind_dn=\"cn=miniolookupuser,dc=example,dc=net\" lookup_bind_dn_password=\"userpassword\" server_addr=\"ad-ldap.example.net/\" user_dn_search_base_dn=\"dc=example,dc=net\" user_dn_search_filter=\"(&(objectCategory=user)(sAMAccountName=%s))\"",
+ },
+ }
+ for _, tc := range testCase {
+ command, err := GenerateMinIOIntervalJobCommand(mcCommand, 0, nil, "test", tc.args, funcs)
+ if !tc.expectError {
+ if err != nil {
+ t.Fatal(err)
+ }
+ if command.Command != tc.expectCommand {
+ t.Fatalf("[%s] expectCommand %s, but got %s", tc.name, tc.expectCommand, command.Command)
+ }
+ if tc.expectFileNumber != len(command.Files) {
+ t.Fatalf("[%s] expectFileNumber %d, but got %d", tc.name, tc.expectFileNumber, len(command.Files))
+ }
+ } else {
+ if err == nil {
+ t.Fatalf("[%s] expectCommand error", tc.name)
+ }
+ }
+ }
+}
+
+func copyArgs(args map[string]string) map[string]string {
+ newArgs := make(map[string]string)
+ for key, val := range args {
+ newArgs[key] = val
+ }
+ return newArgs
+}
diff --git a/pkg/utils/miniojob/types.go b/pkg/utils/miniojob/types.go
new file mode 100644
index 00000000000..3913b21ea1f
--- /dev/null
+++ b/pkg/utils/miniojob/types.go
@@ -0,0 +1,387 @@
+// This file is part of MinIO Operator
+// Copyright (c) 2024 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package miniojob
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "sync"
+
+ "github.com/minio/operator/pkg/apis/job.min.io/v1alpha1"
+ miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
+ "github.com/minio/operator/pkg/runtime"
+ batchjobv1 "k8s.io/api/batch/v1"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+const (
+ // DefaultMCImage - job mc image
+ DefaultMCImage = "minio/mc:latest"
+ // MinioJobName - job name
+ MinioJobName = "job.min.io/job-name"
+ // MinioJobCRName - job cr name
+ MinioJobCRName = "job.min.io/job-cr-name"
+ // CommandFilePath - command file path
+ CommandFilePath = "/temp"
+ // MinioJobPhaseError - error
+ MinioJobPhaseError = "Error"
+ // MinioJobPhaseSuccess - Success
+ MinioJobPhaseSuccess = "Success"
+ // MinioJobPhaseRunning - running
+ MinioJobPhaseRunning = "Running"
+ // MinioJobPhaseFailed - failed
+ MinioJobPhaseFailed = "Failed"
+)
+
+var operationAlias = map[string]string{
+ "make-bucket": "mb",
+ "admin/policy/add": "admin/policy/create",
+}
+
+// JobOperation - job operation
+var JobOperation = map[string][]FieldsFunc{
+ "mb": {FLAGS(), Sanitize(ALIAS(), Static("/"), Key("name")), Static("--ignore-existing")},
+ "admin/user/add": {ALIAS(), Key("user"), Key("password")},
+ "admin/policy/create": {ALIAS(), Key("name"), File("policy", "json")},
+ "admin/policy/attach": {ALIAS(), Key("policy"), OneOf(KeyFormat("user", "--user"), KeyFormat("group", "--group"))},
+ "admin/config/set": {ALIAS(), Key("webhookName"), Option(KeyValue("endpoint")), Option(KeyFile("client_key", "key")), Option(KeyFile("client_cert", "pem")), OthersKeyValues()},
+}
+
+// OperationAliasToMC - convert operation to mc operation
+func OperationAliasToMC(operation string) (op string, found bool) {
+ for k, v := range operationAlias {
+ if k == operation {
+ return v, true
+ }
+ if v == operation {
+ return v, true
+ }
+ }
+ // operation like admin/policy/attach match nothing.
+ // but it's a valid operation
+ if strings.Contains(operation, "/") {
+ return operation, true
+ }
+ // operation like replace match nothing
+ // it's not a valid operation
+ return "", false
+}
+
+// MinIOIntervalJobCommandFile - Job run command need a file such as /temp/policy.json
+type MinIOIntervalJobCommandFile struct {
+ Name string
+ Ext string
+ Dir string
+ Content string
+}
+
+// MinIOIntervalJobCommand - Job run command
+type MinIOIntervalJobCommand struct {
+ mutex sync.RWMutex
+ JobName string
+ MCOperation string
+ Command string
+ DepnedsOn []string
+ Files []MinIOIntervalJobCommandFile
+ Succeeded bool
+ Message string
+ Created bool
+}
+
+// SetStatus - set job command status
+func (jobCommand *MinIOIntervalJobCommand) SetStatus(success bool, message string) {
+ if jobCommand == nil {
+ return
+ }
+ jobCommand.mutex.Lock()
+ jobCommand.Succeeded = success
+ jobCommand.Message = message
+ jobCommand.mutex.Unlock()
+}
+
+// Success - check job command status
+func (jobCommand *MinIOIntervalJobCommand) Success() bool {
+ if jobCommand == nil {
+ return false
+ }
+ jobCommand.mutex.Lock()
+ defer jobCommand.mutex.Unlock()
+ return jobCommand.Succeeded
+}
+
+// CreateJob - create job
+func (jobCommand *MinIOIntervalJobCommand) CreateJob(ctx context.Context, k8sClient client.Client, jobCR *v1alpha1.MinIOJob) error {
+ if jobCommand == nil {
+ return nil
+ }
+ jobCommand.mutex.RLock()
+ if jobCommand.Created || jobCommand.Succeeded {
+ jobCommand.mutex.RUnlock()
+ return nil
+ }
+ jobCommand.mutex.RUnlock()
+ jobCommands := []string{}
+ commands := []string{"mc"}
+ commands = append(commands, strings.SplitN(jobCommand.MCOperation, "/", -1)...)
+ commands = append(commands, strings.SplitN(jobCommand.Command, " ", -1)...)
+ for _, command := range commands {
+ trimCommand := strings.TrimSpace(command)
+ if trimCommand != "" {
+ jobCommands = append(jobCommands, trimCommand)
+ }
+ }
+ jobCommands = append(jobCommands, "--insecure")
+ objs := []client.Object{}
+ mcImage := jobCR.Spec.MCImage
+ if mcImage == "" {
+ mcImage = DefaultMCImage
+ }
+ job := &batchjobv1.Job{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: fmt.Sprintf("%s-%s", jobCR.Name, jobCommand.JobName),
+ Namespace: jobCR.Namespace,
+ Labels: map[string]string{
+ MinioJobName: jobCommand.JobName,
+ MinioJobCRName: jobCR.Name,
+ },
+ Annotations: map[string]string{
+ "job.min.io/operation": jobCommand.MCOperation,
+ },
+ },
+ Spec: batchjobv1.JobSpec{
+ Template: corev1.PodTemplateSpec{
+ ObjectMeta: metav1.ObjectMeta{
+ Labels: map[string]string{
+ MinioJobName: jobCommand.JobName,
+ },
+ },
+ Spec: corev1.PodSpec{
+ ServiceAccountName: jobCR.Spec.ServiceAccountName,
+ Containers: []corev1.Container{
+ {
+ Name: "mc",
+ Image: mcImage,
+ ImagePullPolicy: corev1.PullIfNotPresent,
+ Env: []corev1.EnvVar{
+ {
+ Name: "MC_HOST_myminio",
+ Value: fmt.Sprintf("https://$(ACCESS_KEY):$(SECRET_KEY)@minio.%s.svc.cluster.local", jobCR.Namespace),
+ },
+ {
+ Name: "MC_STS_ENDPOINT_myminio",
+ Value: fmt.Sprintf("https://sts.%s.svc.cluster.local:4223/sts/%s", miniov2.GetNSFromFile(), jobCR.Namespace),
+ },
+ {
+ Name: "MC_WEB_IDENTITY_TOKEN_FILE_myminio",
+ Value: "/var/run/secrets/kubernetes.io/serviceaccount/token",
+ },
+ },
+ Command: jobCommands,
+ },
+ },
+ },
+ },
+ },
+ }
+ if jobCR.Spec.FailureStrategy == v1alpha1.StopOnFailure {
+ job.Spec.Template.Spec.RestartPolicy = corev1.RestartPolicyNever
+ } else {
+ job.Spec.Template.Spec.RestartPolicy = corev1.RestartPolicyOnFailure
+ }
+ if len(jobCommand.Files) > 0 {
+ cmName := fmt.Sprintf("%s-%s-cm", jobCR.Name, jobCommand.JobName)
+ job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
+ Name: "file-volume",
+ ReadOnly: true,
+ MountPath: jobCommand.Files[0].Dir,
+ })
+ job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{
+ Name: "file-volume",
+ VolumeSource: corev1.VolumeSource{
+ ConfigMap: &corev1.ConfigMapVolumeSource{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: cmName,
+ },
+ },
+ },
+ })
+ configMap := &corev1.ConfigMap{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: cmName,
+ Namespace: jobCR.Namespace,
+ Labels: map[string]string{
+ "job.min.io/name": jobCR.Name,
+ },
+ },
+ Data: map[string]string{},
+ }
+ for _, file := range jobCommand.Files {
+ configMap.Data[fmt.Sprintf("%s.%s", file.Name, file.Ext)] = file.Content
+ }
+ objs = append(objs, configMap)
+ }
+ objs = append(objs, job)
+ for _, obj := range objs {
+ _, err := runtime.NewObjectSyncer(ctx, k8sClient, jobCR, func() error {
+ return nil
+ }, obj, runtime.SyncTypeCreateOrUpdate).Sync(ctx)
+ if err != nil {
+ return err
+ }
+ }
+ jobCommand.mutex.Lock()
+ jobCommand.Created = true
+ jobCommand.mutex.Unlock()
+ return nil
+}
+
+// MinIOIntervalJob - Interval Job
+type MinIOIntervalJob struct {
+ // to see if that change
+ JobCR *v1alpha1.MinIOJob
+ Command []*MinIOIntervalJobCommand
+ CommandMap map[string]*MinIOIntervalJobCommand
+}
+
+// GetMinioJobStatus - get job status
+func (intervalJob *MinIOIntervalJob) GetMinioJobStatus(ctx context.Context) v1alpha1.MinIOJobStatus {
+ status := v1alpha1.MinIOJobStatus{}
+ failed := false
+ running := false
+ message := ""
+ for _, command := range intervalJob.Command {
+ command.mutex.RLock()
+ if command.Succeeded {
+ status.CommandsStatus = append(status.CommandsStatus, v1alpha1.CommandStatus{
+ Name: command.JobName,
+ Result: "Success",
+ Message: command.Message,
+ })
+ } else {
+ failed = true
+ message = command.Message
+ // if Success is false and message is empty, it means the job is running
+ if command.Message == "" {
+ running = true
+ status.CommandsStatus = append(status.CommandsStatus, v1alpha1.CommandStatus{
+ Name: command.JobName,
+ Result: "running",
+ Message: command.Message,
+ })
+ } else {
+ status.CommandsStatus = append(status.CommandsStatus, v1alpha1.CommandStatus{
+ Name: command.JobName,
+ Result: "failed",
+ Message: command.Message,
+ })
+ }
+ }
+ command.mutex.RUnlock()
+ }
+ if running {
+ status.Phase = MinioJobPhaseRunning
+ } else {
+ if failed {
+ status.Phase = MinioJobPhaseFailed
+ status.Message = message
+ } else {
+ status.Phase = MinioJobPhaseSuccess
+ }
+ }
+ return status
+}
+
+// CreateCommandJob - create command job
+func (intervalJob *MinIOIntervalJob) CreateCommandJob(ctx context.Context, k8sClient client.Client) error {
+ for _, command := range intervalJob.Command {
+ if len(command.DepnedsOn) == 0 {
+ err := command.CreateJob(ctx, k8sClient, intervalJob.JobCR)
+ if err != nil {
+ return err
+ }
+ } else {
+ allDepsSuccess := true
+ for _, dep := range command.DepnedsOn {
+ status, found := intervalJob.CommandMap[dep]
+ if !found {
+ return fmt.Errorf("dependent job %s not found", dep)
+ }
+ if !status.Success() {
+ allDepsSuccess = false
+ break
+ }
+ }
+ if allDepsSuccess {
+ err := command.CreateJob(ctx, k8sClient, intervalJob.JobCR)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ }
+ return nil
+}
+
+// GenerateMinIOIntervalJobCommand - generate command
+func GenerateMinIOIntervalJobCommand(mcCommand string, commandIndex int, dependsOn []string, jobName string, args map[string]string, argsFuncs []FieldsFunc) (*MinIOIntervalJobCommand, error) {
+ commands := []string{}
+ files := []MinIOIntervalJobCommandFile{}
+ for _, argsFunc := range argsFuncs {
+ jobArg, err := argsFunc(args)
+ if err != nil {
+ return nil, err
+ }
+ switch jobArg.ArgType {
+ case ArgTypeKey:
+ if jobArg.Command != "" {
+ commands = append(commands, jobArg.Command)
+ }
+ case ArgTypeFile:
+ files = append(files, MinIOIntervalJobCommandFile{
+ Name: jobArg.FileName,
+ Ext: jobArg.FileExt,
+ Dir: CommandFilePath,
+ Content: jobArg.FileContext,
+ })
+ commands = append(commands, fmt.Sprintf("%s/%s.%s", CommandFilePath, jobArg.FileName, jobArg.FileExt))
+ case ArgTypeKeyFile:
+ files = append(files, MinIOIntervalJobCommandFile{
+ Name: jobArg.FileName,
+ Ext: jobArg.FileExt,
+ Dir: CommandFilePath,
+ Content: jobArg.FileContext,
+ })
+ commands = append(commands, fmt.Sprintf(`%s="%s/%s.%s"`, jobArg.FileName, CommandFilePath, jobArg.FileName, jobArg.FileExt))
+ }
+
+ }
+ jobCommand := &MinIOIntervalJobCommand{
+ JobName: jobName,
+ MCOperation: mcCommand,
+ Command: strings.Join(commands, " "),
+ DepnedsOn: dependsOn,
+ Files: files,
+ }
+ // some commands need to have a empty name
+ if jobCommand.JobName == "" {
+ jobCommand.JobName = fmt.Sprintf("command-%d", commandIndex)
+ }
+ return jobCommand, nil
+}
diff --git a/redhat-marketplace/manifests/console-env_v1_configmap.yaml b/redhat-marketplace/manifests/console-env_v1_configmap.yaml
index 1c276708cd0..a74206147b2 100644
--- a/redhat-marketplace/manifests/console-env_v1_configmap.yaml
+++ b/redhat-marketplace/manifests/console-env_v1_configmap.yaml
@@ -8,4 +8,5 @@ metadata:
operator.min.io/authors: MinIO, Inc.
operator.min.io/license: AGPLv3
operator.min.io/support: https://subnet.min.io
+ operator.min.io/version: v5.0.14
name: console-env
diff --git a/redhat-marketplace/manifests/console-sa-secret_v1_secret.yaml b/redhat-marketplace/manifests/console-sa-secret_v1_secret.yaml
index 8f7c7e18363..77a6d71d9e6 100644
--- a/redhat-marketplace/manifests/console-sa-secret_v1_secret.yaml
+++ b/redhat-marketplace/manifests/console-sa-secret_v1_secret.yaml
@@ -6,5 +6,6 @@ metadata:
operator.min.io/authors: MinIO, Inc.
operator.min.io/license: AGPLv3
operator.min.io/support: https://subnet.min.io
+ operator.min.io/version: v5.0.14
name: console-sa-secret
type: kubernetes.io/service-account-token
diff --git a/redhat-marketplace/manifests/console_v1_service.yaml b/redhat-marketplace/manifests/console_v1_service.yaml
index 1d2af3ffb8a..544027e0a9c 100644
--- a/redhat-marketplace/manifests/console_v1_service.yaml
+++ b/redhat-marketplace/manifests/console_v1_service.yaml
@@ -5,6 +5,7 @@ metadata:
operator.min.io/authors: MinIO, Inc.
operator.min.io/license: AGPLv3
operator.min.io/support: https://subnet.min.io
+ operator.min.io/version: v5.0.14
service.beta.openshift.io/serving-cert-secret-name: console-tls
creationTimestamp: null
labels:
diff --git a/redhat-marketplace/manifests/job.min.io_miniojobs.yaml b/redhat-marketplace/manifests/job.min.io_miniojobs.yaml
new file mode 100644
index 00000000000..065f917fed5
--- /dev/null
+++ b/redhat-marketplace/manifests/job.min.io_miniojobs.yaml
@@ -0,0 +1,123 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.13.0
+ operator.min.io/authors: MinIO, Inc.
+ operator.min.io/license: AGPLv3
+ operator.min.io/support: https://subnet.min.io
+ operator.min.io/version: v5.0.14
+ creationTimestamp: null
+ name: miniojobs.job.min.io
+spec:
+ group: job.min.io
+ names:
+ kind: MinIOJob
+ listKind: MinIOJobList
+ plural: miniojobs
+ shortNames:
+ - miniojob
+ singular: miniojob
+ scope: Namespaced
+ versions:
+ - additionalPrinterColumns:
+ - jsonPath: .spec.tenant.name
+ name: Tenant
+ type: string
+ - jsonPath: .spec.status.phase
+ name: Phase
+ type: string
+ name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ properties:
+ apiVersion:
+ type: string
+ kind:
+ type: string
+ metadata:
+ type: object
+ spec:
+ properties:
+ commands:
+ items:
+ properties:
+ args:
+ additionalProperties:
+ type: string
+ type: object
+ dependsOn:
+ items:
+ type: string
+ type: array
+ name:
+ type: string
+ op:
+ type: string
+ required:
+ - op
+ type: object
+ type: array
+ execution:
+ default: parallel
+ enum:
+ - parallel
+ - sequential
+ type: string
+ failureStrategy:
+ default: continueOnFailure
+ enum:
+ - continueOnFailure
+ - stopOnFailure
+ type: string
+ mcImage:
+ default: minio/mc:latest
+ type: string
+ serviceAccountName:
+ type: string
+ tenant:
+ properties:
+ name:
+ type: string
+ namespace:
+ type: string
+ required:
+ - name
+ - namespace
+ type: object
+ required:
+ - commands
+ - serviceAccountName
+ - tenant
+ type: object
+ status:
+ properties:
+ commands:
+ items:
+ properties:
+ message:
+ type: string
+ name:
+ type: string
+ result:
+ type: string
+ required:
+ - result
+ type: object
+ type: array
+ message:
+ type: string
+ phase:
+ type: string
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
+status:
+ acceptedNames:
+ kind: ""
+ plural: ""
+ conditions: null
+ storedVersions: null
diff --git a/redhat-marketplace/manifests/minio-operator-rhmp.clusterserviceversion.yaml b/redhat-marketplace/manifests/minio-operator-rhmp.clusterserviceversion.yaml
index da0bd26a29b..99261406086 100644
--- a/redhat-marketplace/manifests/minio-operator-rhmp.clusterserviceversion.yaml
+++ b/redhat-marketplace/manifests/minio-operator-rhmp.clusterserviceversion.yaml
@@ -32,7 +32,7 @@ metadata:
"bucketDNS": false,
"domains": {}
},
- "image": "quay.io/minio/minio@sha256:7e697b900f60d68e9edd2e8fc0dccd158e98938d924298612c5bbd294f2a1e65",
+ "image": "quay.io/minio/minio@sha256:ea2c70cda70860c7d10f04ce1df640d3abb995784cb7aeedef8d4baa53a5a113",
"imagePullSecret": {},
"mountPath": "/export",
"podManagementPolicy": "Parallel",
@@ -80,25 +80,37 @@ metadata:
]
capabilities: Full Lifecycle
categories: AI/Machine Learning, Big Data, Cloud Provider, Storage
- createdAt: "2023-10-12T17:54:23Z"
description: |-
MinIO is a Kubernetes-native high performance object store with an
S3-compatible API. The MinIO Operator supports deploying MinIO Tenants
onto any Kubernetes.
+ features.operators.openshift.io/cnf: "false"
+ features.operators.openshift.io/cni: "false"
+ features.operators.openshift.io/csi: "false"
+ features.operators.openshift.io/disconnected: "true"
+ features.operators.openshift.io/fips-compliant: "true"
+ features.operators.openshift.io/proxy-aware: "true"
+ features.operators.openshift.io/tls-profiles: "false"
+ features.operators.openshift.io/token-auth-aws: "false"
+ features.operators.openshift.io/token-auth-azure: "false"
+ features.operators.openshift.io/token-auth-gcp: "false"
k8sMinVersion: "1.18"
marketplace.openshift.io/remote-workflow: https://marketplace.redhat.com/en-us/operators/minio-operator-rhmp/pricing?utm_source=openshift_console
marketplace.openshift.io/support-workflow: https://marketplace.redhat.com/en-us/operators/minio-operator-rhmp/support?utm_source=openshift_console
operatorframework.io/suggested-namespace: minio-operator
- operators.operatorframework.io/builder: operator-sdk-v1.32.0
+ operators.operatorframework.io/builder: operator-sdk-v1.22.2
operators.operatorframework.io/project_layout: unknown
repository: https://github.com/minio/operator
- containerImage: quay.io/minio/operator:v5.0.10
- name: minio-operator-rhmp.v5.0.10
+ containerImage: quay.io/minio/operator:v5.0.14
+ name: minio-operator-rhmp.v5.0.14
namespace: minio-operator
spec:
apiservicedefinitions: {}
customresourcedefinitions:
owned:
+ - kind: MinIOJob
+ name: miniojobs.job.min.io
+ version: v1alpha1
- kind: PolicyBinding
name: policybindings.sts.min.io
version: v1alpha1
@@ -380,6 +392,7 @@ spec:
- get
- update
- list
+ - delete
- apiGroups:
- ""
resources:
@@ -501,6 +514,7 @@ spec:
- apiGroups:
- minio.min.io
- sts.min.io
+ - job.min.io
resources:
- '*'
verbs:
@@ -555,6 +569,7 @@ spec:
operator.min.io/authors: MinIO, Inc.
operator.min.io/license: AGPLv3
operator.min.io/support: https://subnet.min.io
+ operator.min.io/version: v5.0.14
labels:
app: console
app.kubernetes.io/instance: minio-operator-console
@@ -564,7 +579,7 @@ spec:
- args:
- ui
- --certs-dir=/tmp/certs
- image: quay.io/minio/operator:v5.0.10
+ image: quay.io/minio/operator@sha256:db47d598488f10daf6a4d26431ab2b65e24f3dffbae0edb76bc44f63c6daf500
imagePullPolicy: IfNotPresent
name: console
ports:
@@ -577,6 +592,8 @@ spec:
volumeMounts:
- mountPath: /tmp/certs
name: tls-certificates
+ - mountPath: /tmp/certs/CAs
+ name: tmp
serviceAccountName: console-sa
volumes:
- name: tls-certificates
@@ -597,6 +614,8 @@ spec:
path: CAs/ca.crt
name: openshift-service-ca.crt
optional: true
+ - emptyDir: {}
+ name: tmp
- label:
app.kubernetes.io/instance: minio-operator
app.kubernetes.io/name: operator
@@ -614,6 +633,7 @@ spec:
operator.min.io/authors: MinIO, Inc.
operator.min.io/license: AGPLv3
operator.min.io/support: https://subnet.min.io
+ operator.min.io/version: v5.0.14
labels:
app.kubernetes.io/instance: minio-operator
app.kubernetes.io/name: operator
@@ -638,8 +658,8 @@ spec:
- name: MINIO_CONSOLE_TLS_ENABLE
value: "on"
- name: OPERATOR_STS_ENABLED
- value: "off"
- image: quay.io/minio/operator:v5.0.10
+ value: "on"
+ image: quay.io/minio/operator@sha256:db47d598488f10daf6a4d26431ab2b65e24f3dffbae0edb76bc44f63c6daf500
imagePullPolicy: IfNotPresent
name: minio-operator
resources:
@@ -757,11 +777,11 @@ spec:
name: MinIO Inc
url: https://min.io
relatedImages:
- - image: minio/operator@sha256:170b154d2c61c5a6f9dfbe4137e78815102a99fb9ab3aa6baf68750647e6261a
+ - image: quay.io/minio/operator@sha256:db47d598488f10daf6a4d26431ab2b65e24f3dffbae0edb76bc44f63c6daf500
name: console
- - image: minio/operator@sha256:170b154d2c61c5a6f9dfbe4137e78815102a99fb9ab3aa6baf68750647e6261a
+ - image: quay.io/minio/operator@sha256:db47d598488f10daf6a4d26431ab2b65e24f3dffbae0edb76bc44f63c6daf500
name: minio-operator
- - image: quay.io/minio/minio@sha256:7e697b900f60d68e9edd2e8fc0dccd158e98938d924298612c5bbd294f2a1e65
- name: minio-7e697b900f60d68e9edd2e8fc0dccd158e98938d924298612c5bbd294f2a1e65-annotation
- version: 5.0.10
- replaces: minio-operator-rhmp.v5.0.9
+ - image: quay.io/minio/minio@sha256:ea2c70cda70860c7d10f04ce1df640d3abb995784cb7aeedef8d4baa53a5a113
+ name: minio-ea2c70cda70860c7d10f04ce1df640d3abb995784cb7aeedef8d4baa53a5a113-annotation
+ version: 5.0.14
+ replaces: minio-operator-rhmp.v5.0.13
diff --git a/redhat-marketplace/manifests/minio.min.io_tenants.yaml b/redhat-marketplace/manifests/minio.min.io_tenants.yaml
index d18f067d261..1c9fa3aa98d 100644
--- a/redhat-marketplace/manifests/minio.min.io_tenants.yaml
+++ b/redhat-marketplace/manifests/minio.min.io_tenants.yaml
@@ -2,10 +2,11 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
- controller-gen.kubebuilder.io/version: v0.11.1
+ controller-gen.kubebuilder.io/version: v0.13.0
operator.min.io/authors: MinIO, Inc.
operator.min.io/license: AGPLv3
operator.min.io/support: https://subnet.min.io
+ operator.min.io/version: v5.0.14
creationTimestamp: null
name: tenants.minio.min.io
spec:
@@ -312,18 +313,6 @@ spec:
type: object
resources:
properties:
- claims:
- items:
- properties:
- name:
- type: string
- required:
- - name
- type: object
- type: array
- x-kubernetes-list-map-keys:
- - name
- x-kubernetes-list-type: map
limits:
additionalProperties:
anyOf:
@@ -367,6 +356,8 @@ spec:
x-kubernetes-map-type: atomic
storageClassName:
type: string
+ volumeAttributesClassName:
+ type: string
volumeMode:
type: string
volumeName:
@@ -555,6 +546,43 @@ spec:
sources:
items:
properties:
+ clusterTrustBundle:
+ properties:
+ labelSelector:
+ properties:
+ matchExpressions:
+ items:
+ properties:
+ key:
+ type: string
+ operator:
+ type: string
+ values:
+ items:
+ type: string
+ type: array
+ required:
+ - key
+ - operator
+ type: object
+ type: array
+ matchLabels:
+ additionalProperties:
+ type: string
+ type: object
+ type: object
+ x-kubernetes-map-type: atomic
+ name:
+ type: string
+ optional:
+ type: boolean
+ path:
+ type: string
+ signerName:
+ type: string
+ required:
+ - path
+ type: object
configMap:
properties:
items:
@@ -1109,6 +1137,14 @@ spec:
required:
- port
type: object
+ sleep:
+ properties:
+ seconds:
+ format: int64
+ type: integer
+ required:
+ - seconds
+ type: object
tcpSocket:
properties:
host:
@@ -1159,6 +1195,14 @@ spec:
required:
- port
type: object
+ sleep:
+ properties:
+ seconds:
+ format: int64
+ type: integer
+ required:
+ - seconds
+ type: object
tcpSocket:
properties:
host:
@@ -1355,6 +1399,19 @@ spec:
format: int32
type: integer
type: object
+ resizePolicy:
+ items:
+ properties:
+ resourceName:
+ type: string
+ restartPolicy:
+ type: string
+ required:
+ - resourceName
+ - restartPolicy
+ type: object
+ type: array
+ x-kubernetes-list-type: atomic
resources:
properties:
claims:
@@ -1386,6 +1443,8 @@ spec:
x-kubernetes-int-or-string: true
type: object
type: object
+ restartPolicy:
+ type: string
securityContext:
properties:
allowPrivilegeEscalation:
@@ -1702,6 +1761,16 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ matchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ mismatchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
@@ -1770,6 +1839,16 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ matchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ mismatchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
@@ -1836,6 +1915,16 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ matchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ mismatchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
@@ -1904,6 +1993,16 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ matchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ mismatchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
@@ -1953,6 +2052,67 @@ spec:
required:
- name
type: object
+ containerSecurityContext:
+ properties:
+ allowPrivilegeEscalation:
+ type: boolean
+ capabilities:
+ properties:
+ add:
+ items:
+ type: string
+ type: array
+ drop:
+ items:
+ type: string
+ type: array
+ type: object
+ privileged:
+ type: boolean
+ procMount:
+ type: string
+ readOnlyRootFilesystem:
+ type: boolean
+ runAsGroup:
+ format: int64
+ type: integer
+ runAsNonRoot:
+ type: boolean
+ runAsUser:
+ format: int64
+ type: integer
+ seLinuxOptions:
+ properties:
+ level:
+ type: string
+ role:
+ type: string
+ type:
+ type: string
+ user:
+ type: string
+ type: object
+ seccompProfile:
+ properties:
+ localhostProfile:
+ type: string
+ type:
+ type: string
+ required:
+ - type
+ type: object
+ windowsOptions:
+ properties:
+ gmsaCredentialSpec:
+ type: string
+ gmsaCredentialSpecName:
+ type: string
+ hostProcess:
+ type: boolean
+ runAsUserName:
+ type: string
+ type: object
+ type: object
env:
items:
properties:
@@ -2442,6 +2602,16 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ matchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ mismatchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
@@ -2510,6 +2680,16 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ matchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ mismatchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
@@ -2576,6 +2756,16 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ matchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ mismatchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
@@ -2644,6 +2834,16 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ matchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ mismatchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
@@ -2755,6 +2955,8 @@ spec:
additionalProperties:
type: string
type: object
+ reclaimStorage:
+ type: boolean
resources:
properties:
claims:
@@ -2983,18 +3185,6 @@ spec:
type: object
resources:
properties:
- claims:
- items:
- properties:
- name:
- type: string
- required:
- - name
- type: object
- type: array
- x-kubernetes-list-map-keys:
- - name
- x-kubernetes-list-type: map
limits:
additionalProperties:
anyOf:
@@ -3038,6 +3228,8 @@ spec:
x-kubernetes-map-type: atomic
storageClassName:
type: string
+ volumeAttributesClassName:
+ type: string
volumeMode:
type: string
volumeName:
@@ -3049,6 +3241,11 @@ spec:
items:
type: string
type: array
+ allocatedResourceStatuses:
+ additionalProperties:
+ type: string
+ type: object
+ x-kubernetes-map-type: granular
allocatedResources:
additionalProperties:
anyOf:
@@ -3087,9 +3284,18 @@ spec:
- type
type: object
type: array
- phase:
+ currentVolumeAttributesClassName:
type: string
- resizeStatus:
+ modifyVolumeStatus:
+ properties:
+ status:
+ type: string
+ targetVolumeAttributesClassName:
+ type: string
+ required:
+ - status
+ type: object
+ phase:
type: string
type: object
type: object
@@ -3350,6 +3556,14 @@ spec:
required:
- port
type: object
+ sleep:
+ properties:
+ seconds:
+ format: int64
+ type: integer
+ required:
+ - seconds
+ type: object
tcpSocket:
properties:
host:
@@ -3400,6 +3614,14 @@ spec:
required:
- port
type: object
+ sleep:
+ properties:
+ seconds:
+ format: int64
+ type: integer
+ required:
+ - seconds
+ type: object
tcpSocket:
properties:
host:
@@ -3596,6 +3818,19 @@ spec:
format: int32
type: integer
type: object
+ resizePolicy:
+ items:
+ properties:
+ resourceName:
+ type: string
+ restartPolicy:
+ type: string
+ required:
+ - resourceName
+ - restartPolicy
+ type: object
+ type: array
+ x-kubernetes-list-type: atomic
resources:
properties:
claims:
@@ -3627,6 +3862,8 @@ spec:
x-kubernetes-int-or-string: true
type: object
type: object
+ restartPolicy:
+ type: string
securityContext:
properties:
allowPrivilegeEscalation:
@@ -3906,18 +4143,6 @@ spec:
type: object
resources:
properties:
- claims:
- items:
- properties:
- name:
- type: string
- required:
- - name
- type: object
- type: array
- x-kubernetes-list-map-keys:
- - name
- x-kubernetes-list-type: map
limits:
additionalProperties:
anyOf:
@@ -3961,6 +4186,8 @@ spec:
x-kubernetes-map-type: atomic
storageClassName:
type: string
+ volumeAttributesClassName:
+ type: string
volumeMode:
type: string
volumeName:
@@ -3972,6 +4199,11 @@ spec:
items:
type: string
type: array
+ allocatedResourceStatuses:
+ additionalProperties:
+ type: string
+ type: object
+ x-kubernetes-map-type: granular
allocatedResources:
additionalProperties:
anyOf:
@@ -4010,9 +4242,18 @@ spec:
- type
type: object
type: array
- phase:
+ currentVolumeAttributesClassName:
type: string
- resizeStatus:
+ modifyVolumeStatus:
+ properties:
+ status:
+ type: string
+ targetVolumeAttributesClassName:
+ type: string
+ required:
+ - status
+ type: object
+ phase:
type: string
type: object
type: object
@@ -4264,18 +4505,6 @@ spec:
type: object
resources:
properties:
- claims:
- items:
- properties:
- name:
- type: string
- required:
- - name
- type: object
- type: array
- x-kubernetes-list-map-keys:
- - name
- x-kubernetes-list-type: map
limits:
additionalProperties:
anyOf:
@@ -4319,6 +4548,8 @@ spec:
x-kubernetes-map-type: atomic
storageClassName:
type: string
+ volumeAttributesClassName:
+ type: string
volumeMode:
type: string
volumeName:
@@ -4507,6 +4738,43 @@ spec:
sources:
items:
properties:
+ clusterTrustBundle:
+ properties:
+ labelSelector:
+ properties:
+ matchExpressions:
+ items:
+ properties:
+ key:
+ type: string
+ operator:
+ type: string
+ values:
+ items:
+ type: string
+ type: array
+ required:
+ - key
+ - operator
+ type: object
+ type: array
+ matchLabels:
+ additionalProperties:
+ type: string
+ type: object
+ type: object
+ x-kubernetes-map-type: atomic
+ name:
+ type: string
+ optional:
+ type: boolean
+ path:
+ type: string
+ signerName:
+ type: string
+ required:
+ - path
+ type: object
configMap:
properties:
items:
@@ -4745,8 +5013,6 @@ spec:
- name
type: object
type: array
- required:
- - containers
type: object
startup:
properties:
diff --git a/redhat-marketplace/manifests/operator_v1_service.yaml b/redhat-marketplace/manifests/operator_v1_service.yaml
index 011f9599ff8..6b7b8d53ba8 100644
--- a/redhat-marketplace/manifests/operator_v1_service.yaml
+++ b/redhat-marketplace/manifests/operator_v1_service.yaml
@@ -5,6 +5,7 @@ metadata:
operator.min.io/authors: MinIO, Inc.
operator.min.io/license: AGPLv3
operator.min.io/support: https://subnet.min.io
+ operator.min.io/version: v5.0.14
creationTimestamp: null
labels:
app.kubernetes.io/instance: minio-operator
diff --git a/redhat-marketplace/manifests/sts.min.io_policybindings.yaml b/redhat-marketplace/manifests/sts.min.io_policybindings.yaml
index fbbf279207d..d74cf747abc 100644
--- a/redhat-marketplace/manifests/sts.min.io_policybindings.yaml
+++ b/redhat-marketplace/manifests/sts.min.io_policybindings.yaml
@@ -2,10 +2,11 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
- controller-gen.kubebuilder.io/version: v0.11.1
+ controller-gen.kubebuilder.io/version: v0.13.0
operator.min.io/authors: MinIO, Inc.
operator.min.io/license: AGPLv3
operator.min.io/support: https://subnet.min.io
+ operator.min.io/version: v5.0.14
creationTimestamp: null
name: policybindings.sts.min.io
spec:
diff --git a/redhat-marketplace/manifests/sts_v1_service.yaml b/redhat-marketplace/manifests/sts_v1_service.yaml
index cdec8486952..34c64e69366 100644
--- a/redhat-marketplace/manifests/sts_v1_service.yaml
+++ b/redhat-marketplace/manifests/sts_v1_service.yaml
@@ -5,6 +5,7 @@ metadata:
operator.min.io/authors: MinIO, Inc.
operator.min.io/license: AGPLv3
operator.min.io/support: https://subnet.min.io
+ operator.min.io/version: v5.0.14
service.beta.openshift.io/serving-cert-secret-name: sts-tls
creationTimestamp: null
labels:
diff --git a/redhat-marketplace/metadata/annotations.yaml b/redhat-marketplace/metadata/annotations.yaml
index f3fd77270a2..9d5b0a5a5fd 100644
--- a/redhat-marketplace/metadata/annotations.yaml
+++ b/redhat-marketplace/metadata/annotations.yaml
@@ -5,6 +5,6 @@ annotations:
operators.operatorframework.io.bundle.metadata.v1: metadata/
operators.operatorframework.io.bundle.package.v1: minio-operator-rhmp
operators.operatorframework.io.bundle.channels.v1: stable
- operators.operatorframework.io.metrics.builder: operator-sdk-v1.32.0
+ operators.operatorframework.io.metrics.builder: operator-sdk-v1.22.2
operators.operatorframework.io.metrics.mediatype.v1: metrics+v1
operators.operatorframework.io.metrics.project_layout: unknown
diff --git a/release.sh b/release.sh
index c2188f89941..ac37b7f407b 100755
--- a/release.sh
+++ b/release.sh
@@ -14,6 +14,7 @@ KES_RELEASE=$(get_latest_release minio/kes)
KES_CURRENT_RELEASE=$(sed -nr 's/.*(minio\/kes\:)([v]?.*)"/\2/p' pkg/apis/minio.min.io/v2/constants.go)
files=(
+ "README.md"
"api/consts.go"
"docs/tenant_crd.adoc"
"docs/policybinding_crd.adoc"
@@ -25,14 +26,10 @@ files=(
"helm/operator/values.yaml"
"helm/tenant/Chart.yaml"
"helm/tenant/values.yaml"
- "kubectl-minio/README.md"
- "kubectl-minio/cmd/helpers/constants.go"
- "kubectl-minio/cmd/tenant-upgrade.go"
"pkg/apis/minio.min.io/v2/constants.go"
"pkg/controller/operator.go"
"resources/base/deployment.yaml"
"resources/base/console-ui.yaml"
- "update-operator-krew.py"
"testing/console-tenant+kes.sh"
"web-app/src/screens/Console/Tenants/AddTenant/Steps/Images.tsx"
"web-app/src/screens/Console/Tenants/TenantDetails/TenantEncryption.tsx")
@@ -55,11 +52,22 @@ for file in "${files[@]}"; do
sed -i -e "s/${KES_CURRENT_RELEASE}/${KES_RELEASE}/g" "$file"
done
-echo "Re-indexing helm chart releases for $RELEASE"
-./helm-reindex.sh
+annotations_files=(
+ "pkg/apis/job.min.io/v1alpha1/types.go"
+ "pkg/apis/minio.min.io/v2/types.go"
+ "pkg/apis/sts.min.io/v1alpha1/types.go"
+)
+
+for file in "${annotations_files[@]}"; do
+ sed -i -e "s~operator.min.io/version=.*~operator.min.io/version=v${RELEASE}~g" "$file"
+done
+
+# Update annotation in kustomization yaml
+sed -i -e "s~operator.min.io/version: .*~operator.min.io/version: v${RELEASE}~g" "resources/kustomization.yaml"
# Add all the generated files to git
echo "clean -e files"
rm -vf $(git ls-files --others | grep -e "-e$" | awk '{print $1}')
git add .
+
diff --git a/resources/base/cluster-role.yaml b/resources/base/cluster-role.yaml
index 2a4df1dab66..316607ab051 100644
--- a/resources/base/cluster-role.yaml
+++ b/resources/base/cluster-role.yaml
@@ -140,6 +140,7 @@ rules:
- apiGroups:
- minio.min.io
- sts.min.io
+ - job.min.io
resources:
- "*"
verbs:
diff --git a/resources/base/console-ui.yaml b/resources/base/console-ui.yaml
index 7688b48d67d..1dfe98b98ea 100644
--- a/resources/base/console-ui.yaml
+++ b/resources/base/console-ui.yaml
@@ -289,13 +289,19 @@ spec:
- args:
- ui
- --certs-dir=/tmp/certs
- image: minio/operator:v5.0.10
+ image: minio/operator:v5.0.14
imagePullPolicy: IfNotPresent
name: console
securityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - ALL
+ seccompProfile:
+ type: RuntimeDefault
ports:
- containerPort: 9090
name: http
@@ -304,6 +310,8 @@ spec:
volumeMounts:
- mountPath: /tmp/certs
name: tls-certificates
+ - mountPath: /tmp/certs/CAs
+ name: tmp
volumes:
- name: tls-certificates
projected:
@@ -322,6 +330,8 @@ spec:
path: CAs/tls.crt
- key: tls.key
path: tls.key
- name: operator-console-tls
+ name: console-tls
optional: true
+ - name: tmp
+ emptyDir: { }
serviceAccountName: console-sa
diff --git a/resources/base/crds/job.min.io_miniojobs.yaml b/resources/base/crds/job.min.io_miniojobs.yaml
new file mode 100644
index 00000000000..2b0f025c890
--- /dev/null
+++ b/resources/base/crds/job.min.io_miniojobs.yaml
@@ -0,0 +1,114 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.14.0
+ operator.min.io/version: v5.0.14
+ name: miniojobs.job.min.io
+spec:
+ group: job.min.io
+ names:
+ kind: MinIOJob
+ listKind: MinIOJobList
+ plural: miniojobs
+ shortNames:
+ - miniojob
+ singular: miniojob
+ scope: Namespaced
+ versions:
+ - additionalPrinterColumns:
+ - jsonPath: .spec.tenant.name
+ name: Tenant
+ type: string
+ - jsonPath: .spec.status.phase
+ name: Phase
+ type: string
+ name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ properties:
+ apiVersion:
+ type: string
+ kind:
+ type: string
+ metadata:
+ type: object
+ spec:
+ properties:
+ commands:
+ items:
+ properties:
+ args:
+ additionalProperties:
+ type: string
+ type: object
+ dependsOn:
+ items:
+ type: string
+ type: array
+ name:
+ type: string
+ op:
+ type: string
+ required:
+ - op
+ type: object
+ type: array
+ execution:
+ default: parallel
+ enum:
+ - parallel
+ - sequential
+ type: string
+ failureStrategy:
+ default: continueOnFailure
+ enum:
+ - continueOnFailure
+ - stopOnFailure
+ type: string
+ mcImage:
+ default: minio/mc:latest
+ type: string
+ serviceAccountName:
+ type: string
+ tenant:
+ properties:
+ name:
+ type: string
+ namespace:
+ type: string
+ required:
+ - name
+ - namespace
+ type: object
+ required:
+ - commands
+ - serviceAccountName
+ - tenant
+ type: object
+ status:
+ properties:
+ commands:
+ items:
+ properties:
+ message:
+ type: string
+ name:
+ type: string
+ result:
+ type: string
+ required:
+ - result
+ type: object
+ type: array
+ message:
+ type: string
+ phase:
+ type: string
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
diff --git a/resources/base/crds/kustomization.yaml b/resources/base/crds/kustomization.yaml
index 8d240e41c29..1387d04ee1b 100644
--- a/resources/base/crds/kustomization.yaml
+++ b/resources/base/crds/kustomization.yaml
@@ -2,4 +2,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- minio.min.io_tenants.yaml
- - sts.min.io_policybindings.yaml
\ No newline at end of file
+ - sts.min.io_policybindings.yaml
+ - job.min.io_miniojobs.yaml
diff --git a/resources/base/crds/minio.min.io_tenants.yaml b/resources/base/crds/minio.min.io_tenants.yaml
index b06226fa719..cf9395f4a61 100644
--- a/resources/base/crds/minio.min.io_tenants.yaml
+++ b/resources/base/crds/minio.min.io_tenants.yaml
@@ -3,8 +3,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
- controller-gen.kubebuilder.io/version: v0.11.1
- creationTimestamp: null
+ controller-gen.kubebuilder.io/version: v0.14.0
+ operator.min.io/version: v5.0.14
name: tenants.minio.min.io
spec:
group: minio.min.io
@@ -310,18 +310,6 @@ spec:
type: object
resources:
properties:
- claims:
- items:
- properties:
- name:
- type: string
- required:
- - name
- type: object
- type: array
- x-kubernetes-list-map-keys:
- - name
- x-kubernetes-list-type: map
limits:
additionalProperties:
anyOf:
@@ -365,6 +353,8 @@ spec:
x-kubernetes-map-type: atomic
storageClassName:
type: string
+ volumeAttributesClassName:
+ type: string
volumeMode:
type: string
volumeName:
@@ -553,6 +543,43 @@ spec:
sources:
items:
properties:
+ clusterTrustBundle:
+ properties:
+ labelSelector:
+ properties:
+ matchExpressions:
+ items:
+ properties:
+ key:
+ type: string
+ operator:
+ type: string
+ values:
+ items:
+ type: string
+ type: array
+ required:
+ - key
+ - operator
+ type: object
+ type: array
+ matchLabels:
+ additionalProperties:
+ type: string
+ type: object
+ type: object
+ x-kubernetes-map-type: atomic
+ name:
+ type: string
+ optional:
+ type: boolean
+ path:
+ type: string
+ signerName:
+ type: string
+ required:
+ - path
+ type: object
configMap:
properties:
items:
@@ -1107,6 +1134,14 @@ spec:
required:
- port
type: object
+ sleep:
+ properties:
+ seconds:
+ format: int64
+ type: integer
+ required:
+ - seconds
+ type: object
tcpSocket:
properties:
host:
@@ -1157,6 +1192,14 @@ spec:
required:
- port
type: object
+ sleep:
+ properties:
+ seconds:
+ format: int64
+ type: integer
+ required:
+ - seconds
+ type: object
tcpSocket:
properties:
host:
@@ -1715,6 +1758,16 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ matchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ mismatchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
@@ -1783,6 +1836,16 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ matchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ mismatchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
@@ -1849,6 +1912,16 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ matchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ mismatchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
@@ -1917,6 +1990,16 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ matchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ mismatchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
@@ -1966,6 +2049,67 @@ spec:
required:
- name
type: object
+ containerSecurityContext:
+ properties:
+ allowPrivilegeEscalation:
+ type: boolean
+ capabilities:
+ properties:
+ add:
+ items:
+ type: string
+ type: array
+ drop:
+ items:
+ type: string
+ type: array
+ type: object
+ privileged:
+ type: boolean
+ procMount:
+ type: string
+ readOnlyRootFilesystem:
+ type: boolean
+ runAsGroup:
+ format: int64
+ type: integer
+ runAsNonRoot:
+ type: boolean
+ runAsUser:
+ format: int64
+ type: integer
+ seLinuxOptions:
+ properties:
+ level:
+ type: string
+ role:
+ type: string
+ type:
+ type: string
+ user:
+ type: string
+ type: object
+ seccompProfile:
+ properties:
+ localhostProfile:
+ type: string
+ type:
+ type: string
+ required:
+ - type
+ type: object
+ windowsOptions:
+ properties:
+ gmsaCredentialSpec:
+ type: string
+ gmsaCredentialSpecName:
+ type: string
+ hostProcess:
+ type: boolean
+ runAsUserName:
+ type: string
+ type: object
+ type: object
env:
items:
properties:
@@ -2558,6 +2702,16 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ matchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ mismatchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
@@ -2626,6 +2780,16 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ matchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ mismatchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
@@ -2692,6 +2856,16 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ matchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ mismatchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
@@ -2760,6 +2934,16 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
+ matchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ mismatchLabelKeys:
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
namespaceSelector:
properties:
matchExpressions:
@@ -2973,6 +3157,9 @@ spec:
servers:
format: int32
type: integer
+ x-kubernetes-validations:
+ - message: servers is immutable
+ rule: self == oldSelf
tolerations:
items:
properties:
@@ -3101,18 +3288,6 @@ spec:
type: object
resources:
properties:
- claims:
- items:
- properties:
- name:
- type: string
- required:
- - name
- type: object
- type: array
- x-kubernetes-list-map-keys:
- - name
- x-kubernetes-list-type: map
limits:
additionalProperties:
anyOf:
@@ -3156,6 +3331,8 @@ spec:
x-kubernetes-map-type: atomic
storageClassName:
type: string
+ volumeAttributesClassName:
+ type: string
volumeMode:
type: string
volumeName:
@@ -3210,6 +3387,17 @@ spec:
- type
type: object
type: array
+ currentVolumeAttributesClassName:
+ type: string
+ modifyVolumeStatus:
+ properties:
+ status:
+ type: string
+ targetVolumeAttributesClassName:
+ type: string
+ required:
+ - status
+ type: object
phase:
type: string
type: object
@@ -3217,12 +3405,19 @@ spec:
volumesPerServer:
format: int32
type: integer
+ x-kubernetes-validations:
+ - message: volumesPerServer is immutable
+ rule: self == oldSelf
required:
+ - name
- servers
- volumeClaimTemplate
- volumesPerServer
type: object
type: array
+ x-kubernetes-list-map-keys:
+ - name
+ x-kubernetes-list-type: map
priorityClassName:
type: string
prometheusOperator:
@@ -3471,6 +3666,14 @@ spec:
required:
- port
type: object
+ sleep:
+ properties:
+ seconds:
+ format: int64
+ type: integer
+ required:
+ - seconds
+ type: object
tcpSocket:
properties:
host:
@@ -3521,6 +3724,14 @@ spec:
required:
- port
type: object
+ sleep:
+ properties:
+ seconds:
+ format: int64
+ type: integer
+ required:
+ - seconds
+ type: object
tcpSocket:
properties:
host:
@@ -4042,18 +4253,6 @@ spec:
type: object
resources:
properties:
- claims:
- items:
- properties:
- name:
- type: string
- required:
- - name
- type: object
- type: array
- x-kubernetes-list-map-keys:
- - name
- x-kubernetes-list-type: map
limits:
additionalProperties:
anyOf:
@@ -4097,6 +4296,8 @@ spec:
x-kubernetes-map-type: atomic
storageClassName:
type: string
+ volumeAttributesClassName:
+ type: string
volumeMode:
type: string
volumeName:
@@ -4151,6 +4352,17 @@ spec:
- type
type: object
type: array
+ currentVolumeAttributesClassName:
+ type: string
+ modifyVolumeStatus:
+ properties:
+ status:
+ type: string
+ targetVolumeAttributesClassName:
+ type: string
+ required:
+ - status
+ type: object
phase:
type: string
type: object
@@ -4403,18 +4615,6 @@ spec:
type: object
resources:
properties:
- claims:
- items:
- properties:
- name:
- type: string
- required:
- - name
- type: object
- type: array
- x-kubernetes-list-map-keys:
- - name
- x-kubernetes-list-type: map
limits:
additionalProperties:
anyOf:
@@ -4458,6 +4658,8 @@ spec:
x-kubernetes-map-type: atomic
storageClassName:
type: string
+ volumeAttributesClassName:
+ type: string
volumeMode:
type: string
volumeName:
@@ -4646,6 +4848,43 @@ spec:
sources:
items:
properties:
+ clusterTrustBundle:
+ properties:
+ labelSelector:
+ properties:
+ matchExpressions:
+ items:
+ properties:
+ key:
+ type: string
+ operator:
+ type: string
+ values:
+ items:
+ type: string
+ type: array
+ required:
+ - key
+ - operator
+ type: object
+ type: array
+ matchLabels:
+ additionalProperties:
+ type: string
+ type: object
+ type: object
+ x-kubernetes-map-type: atomic
+ name:
+ type: string
+ optional:
+ type: boolean
+ path:
+ type: string
+ signerName:
+ type: string
+ required:
+ - path
+ type: object
configMap:
properties:
items:
diff --git a/resources/base/crds/sts.min.io_policybindings.yaml b/resources/base/crds/sts.min.io_policybindings.yaml
index b01576f5bda..b605e3da91f 100644
--- a/resources/base/crds/sts.min.io_policybindings.yaml
+++ b/resources/base/crds/sts.min.io_policybindings.yaml
@@ -3,8 +3,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
- controller-gen.kubebuilder.io/version: v0.11.1
- creationTimestamp: null
+ controller-gen.kubebuilder.io/version: v0.14.0
+ operator.min.io/version: v5.0.14
name: policybindings.sts.min.io
spec:
group: sts.min.io
diff --git a/resources/base/deployment.yaml b/resources/base/deployment.yaml
index 38dc0179207..59c477fab6d 100644
--- a/resources/base/deployment.yaml
+++ b/resources/base/deployment.yaml
@@ -23,7 +23,7 @@ spec:
serviceAccountName: minio-operator
containers:
- name: minio-operator
- image: minio/operator:v5.0.10
+ image: minio/operator:v5.0.14
imagePullPolicy: IfNotPresent
args:
- controller
@@ -36,6 +36,12 @@ spec:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - ALL
+ seccompProfile:
+ type: RuntimeDefault
env:
- name: MINIO_CONSOLE_TLS_ENABLE
value: "off"
diff --git a/resources/base/namespace.yaml b/resources/base/namespace.yaml
index 1002072f374..08ebbed8f09 100644
--- a/resources/base/namespace.yaml
+++ b/resources/base/namespace.yaml
@@ -2,3 +2,10 @@ apiVersion: v1
kind: Namespace
metadata:
name: minio-operator
+ labels:
+ pod-security.kubernetes.io/enforce: restricted
+ pod-security.kubernetes.io/enforce-version: latest
+ pod-security.kubernetes.io/audit: restricted
+ pod-security.kubernetes.io/audit-version: latest
+ pod-security.kubernetes.io/warn: restricted
+ pod-security.kubernetes.io/warn-version: latest
diff --git a/resources/kustomization.yaml b/resources/kustomization.yaml
index 9ddaa92a528..f0e5e5c6f17 100644
--- a/resources/kustomization.yaml
+++ b/resources/kustomization.yaml
@@ -5,7 +5,9 @@ commonAnnotations:
operator.min.io/authors: "MinIO, Inc."
operator.min.io/license: "AGPLv3"
operator.min.io/support: "https://subnet.min.io"
-
+ operator.min.io/version: v5.0.14
+commonLabels:
+ app.kubernetes.io/name: operator
resources:
- base/namespace.yaml
- base/service-account.yaml
diff --git a/swagger.yml b/swagger.yml
index 4e6f7b63cb4..2cc2cc5dc3c 100644
--- a/swagger.yml
+++ b/swagger.yml
@@ -1,6 +1,6 @@
swagger: "2.0"
info:
- title: MinIO Console Server
+ title: MinIO Operator
version: 0.1.0
consumes:
- application/json
diff --git a/testing/common.sh b/testing/common.sh
index f45169c5f3c..2f256592e1f 100644
--- a/testing/common.sh
+++ b/testing/common.sh
@@ -47,6 +47,7 @@ die() {
try() { "$@" || die "cannot $*"; }
function setup_kind() {
+ echo "setup_kind():"
if [ "$TEST_FLOOR" = "true" ]; then
try kind create cluster --config "${SCRIPT_DIR}/kind-config-floor.yaml"
else
@@ -67,8 +68,487 @@ function install_cert_manager() {
try kubectl wait -n cert-manager --for=condition=ready pod -l app=webhook --timeout=120s
}
-function install_operator() {
+# removes the pool from the provided tenant.
+# usage: remove_decommissioned_pool
+function remove_decommissioned_pool() {
+
+ TENANT_NAME=$1
+ NAMESPACE=$2
+
+ # While there is a conflict, let's retry
+ RESULT=1
+ while [ "$RESULT" == "1" ]; do
+ sleep 10 # wait for new tenant spec to be ready
+ get_tenant_spec "$TENANT_NAME" "$NAMESPACE"
+ # modify it
+ yq eval 'del(.spec.pools[0])' ~/tenant.yaml >~/new-tenant.yaml
+ # replace it
+ RESULT=$(kubectl_replace ~/new-tenant.yaml)
+ done
+
+}
+
+# waits for n resources to come up.
+# usage: wait_for_n_resources
+function wait_for_n_resources() {
+ NUMBER_OF_RESOURCES=$3
+ waitdone=0
+ totalwait=0
+ DEFAULT_WAIT_TIME=300 # 300 SECONDS
+ if [ -z "$4" ]; then
+ echo "No requested waiting time hence using default value"
+ else
+ echo "Requested specific waiting time"
+ DEFAULT_WAIT_TIME=$4
+ fi
+ echo "* Waiting for ${NUMBER_OF_RESOURCES} pods to come up; wait_time: ${DEFAULT_WAIT_TIME};"
+ command_to_wait="kubectl -n $1 get pods --field-selector=status.phase=Running --no-headers --ignore-not-found=true"
+ if [ "$2" ]; then
+ command_to_wait="kubectl -n $1 get pods --field-selector=status.phase=Running --no-headers --ignore-not-found=true -l $2"
+ fi
+ echo "* Waiting on: $command_to_wait"
+
+ while true; do
+ # xargs to trim whitespaces from bash variable.
+ waitdone=$($command_to_wait | wc -l | xargs)
+
+ echo " "
+ echo " "
+ echo "##############################"
+ echo "To show visibility in all pods"
+ echo "##############################"
+ kubectl get pods -A
+ echo " "
+ echo " "
+ echo " "
+
+ if [ "$waitdone" -ne 0 ]; then
+ if [ "$NUMBER_OF_RESOURCES" == "$waitdone" ]; then
+ break
+ fi
+ fi
+ sleep 5
+ totalwait=$((totalwait + 10))
+ if [ "$totalwait" -gt "$DEFAULT_WAIT_TIME" ]; then
+ echo "* Unable to get resource after 10 minutes, exiting."
+ try false
+ fi
+ done
+}
+
+# waits for n tenant pods to come up.
+# usage: wait_for_n_tenant_pods
+function wait_for_n_tenant_pods() {
+ NUMBER_OF_RESOURCES=$1
+ NAMESPACE=$2
+ TENANT_NAME=$3
+ echo "wait_for_n_tenant_pods(): * Waiting for ${NUMBER_OF_RESOURCES} '${TENANT_NAME}' tenant pods in ${NAMESPACE} namespace"
+ wait_for_n_resources "$NAMESPACE" "v1.min.io/tenant=$TENANT_NAME" "$NUMBER_OF_RESOURCES" 600
+ echo "Waiting for the tenant pods to be ready (5m timeout)"
+ try kubectl wait --namespace "$NAMESPACE" \
+ --for=condition=ready pod \
+ --selector v1.min.io/tenant="$TENANT_NAME" \
+ --timeout=600s
+}
+
+# copies the script to the pod.
+# usage: copy_script_to_pod