Skip to content

Commit

Permalink
Rebuild service template using stategic patch
Browse files Browse the repository at this point in the history
  • Loading branch information
sergeyshevch committed Apr 20, 2024
1 parent 61d57de commit dd19281
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 56 deletions.
12 changes: 10 additions & 2 deletions api/v1alpha1/etcdcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type EtcdClusterSpec struct {
PodTemplate PodTemplate `json:"podTemplate,omitempty"`
// Service defines the desired state of Service for etcd members. If not specified, default values will be used.
// +optional
ServiceTemplate *ServiceSpec `json:"serviceTemplate,omitempty"`
ServiceTemplate *EmbeddedService `json:"serviceTemplate,omitempty"`
// HeadlessService defines the desired state of HeadlessService for etcd members. If not specified, default values will be used.
// +optional
HeadlessServiceTemplate *EmbeddedMetadataResource `json:"headlessServiceTemplate,omitempty"`
Expand Down Expand Up @@ -134,6 +134,14 @@ type EmbeddedObjectMetadata struct {
Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"`
}

func (r *EmbeddedObjectMetadata) ToObjectMeta() metav1.ObjectMeta {
return metav1.ObjectMeta{
Name: r.Name,
Labels: r.Labels,
Annotations: r.Annotations,
}
}

// PodTemplate allows overrides, such as sidecars, init containers, changes to the security context, etc to the pod template generated by the operator.
type PodTemplate struct {
// EmbeddedObjectMetadata contains metadata relevant to an EmbeddedResource
Expand Down Expand Up @@ -232,7 +240,7 @@ type PodDisruptionBudgetSpec struct {
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"`
}

type ServiceSpec struct {
type EmbeddedService struct {
// EmbeddedMetadata contains metadata relevant to an EmbeddedResource.
// +optional
EmbeddedObjectMetadata `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Expand Down
44 changes: 22 additions & 22 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 35 additions & 29 deletions internal/controller/factory/svc.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,26 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

etcdaenixiov1alpha1 "github.com/aenix-io/etcd-operator/api/v1alpha1"
"sigs.k8s.io/controller-runtime/pkg/log"

etcdaenixiov1alpha1 "github.com/aenix-io/etcd-operator/api/v1alpha1"
"github.com/aenix-io/etcd-operator/internal/k8sutils"
)

func GetClientServiceName(cluster *etcdaenixiov1alpha1.EtcdCluster) string {
if cluster.Spec.ServiceTemplate != nil && cluster.Spec.ServiceTemplate.Name != "" {
return cluster.Spec.ServiceTemplate.Name
}

return fmt.Sprintf("%s-client", cluster.Name)
return cluster.Name
}

func GetClusterServiceName(cluster *etcdaenixiov1alpha1.EtcdCluster) string {
if cluster.Spec.HeadlessServiceTemplate != nil && cluster.Spec.HeadlessServiceTemplate.Name != "" {
return cluster.Spec.HeadlessServiceTemplate.Name
}

return cluster.Name
return fmt.Sprintf("%s-headless", cluster.Name)
}

func CreateOrUpdateClusterService(
Expand All @@ -54,12 +56,23 @@ func CreateOrUpdateClusterService(
rscheme *runtime.Scheme,
) error {
logger := log.FromContext(ctx)
var err error

metadata := metav1.ObjectMeta{
Name: GetClusterServiceName(cluster),
Namespace: cluster.Namespace,
Labels: NewLabelsBuilder().WithName().WithInstance(cluster.Name).WithManagedBy(),
}

if cluster.Spec.HeadlessServiceTemplate != nil {
metadata, err = k8sutils.StrategicMerge(metadata, cluster.Spec.HeadlessServiceTemplate.ToObjectMeta())
if err != nil {
return fmt.Errorf("cannot strategic-merge base svc metadata with headlessServiceTemplate.metadata: %w", err)
}
}

svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: GetClusterServiceName(cluster),
Namespace: cluster.Namespace,
Labels: NewLabelsBuilder().WithName().WithInstance(cluster.Name).WithManagedBy(),
},
ObjectMeta: metadata,
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{Name: "peer", TargetPort: intstr.FromInt32(2380), Port: 2380, Protocol: corev1.ProtocolTCP},
Expand All @@ -71,11 +84,8 @@ func CreateOrUpdateClusterService(
PublishNotReadyAddresses: true,
},
}
logger.V(2).Info("cluster service spec generated", "svc_name", svc.Name, "svc_spec", svc.Spec)

if cluster.Spec.HeadlessServiceTemplate != nil {
svc.ObjectMeta = mergeObjectMeta(svc.ObjectMeta, cluster.Spec.HeadlessServiceTemplate.EmbeddedObjectMetadata)
}
logger.V(2).Info("cluster service spec generated", "svc_name", svc.Name, "svc_spec", svc.Spec)

if err := ctrl.SetControllerReference(cluster, svc, rscheme); err != nil {
return fmt.Errorf("cannot set controller reference: %w", err)
Expand All @@ -91,7 +101,9 @@ func CreateOrUpdateClientService(
rscheme *runtime.Scheme,
) error {
logger := log.FromContext(ctx)
svc := &corev1.Service{
var err error

svc := corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: GetClientServiceName(cluster),
Namespace: cluster.Namespace,
Expand All @@ -105,28 +117,22 @@ func CreateOrUpdateClientService(
Selector: NewLabelsBuilder().WithName().WithInstance(cluster.Name).WithManagedBy(),
},
}
logger.V(2).Info("client service spec generated", "svc_name", svc.Name, "svc_spec", svc.Spec)

if cluster.Spec.ServiceTemplate != nil {
svc.ObjectMeta = mergeObjectMeta(svc.ObjectMeta, cluster.Spec.ServiceTemplate.EmbeddedObjectMetadata)

spec := cluster.Spec.ServiceTemplate.Spec
if spec.Ports == nil {
spec.Ports = svc.Spec.Ports
svc, err = k8sutils.StrategicMerge(svc, corev1.Service{
ObjectMeta: cluster.Spec.ServiceTemplate.EmbeddedObjectMetadata.ToObjectMeta(),
Spec: cluster.Spec.ServiceTemplate.Spec,
})
if err != nil {
return fmt.Errorf("cannot strategic-merge base svc with serviceTemplate: %w", err)
}
if spec.Type == "" {
spec.Type = svc.Spec.Type
}
if spec.Selector == nil || len(spec.Selector) == 0 {
spec.Selector = svc.Spec.Selector
}

svc.Spec = spec
}

if err := ctrl.SetControllerReference(cluster, svc, rscheme); err != nil {
logger.V(2).Info("client service spec generated", "svc_name", svc.Name, "svc_spec", svc.Spec)

if err := ctrl.SetControllerReference(cluster, &svc, rscheme); err != nil {
return fmt.Errorf("cannot set controller reference: %w", err)
}

return reconcileService(ctx, rclient, cluster.Name, svc)
return reconcileService(ctx, rclient, cluster.Name, &svc)
}
4 changes: 2 additions & 2 deletions internal/controller/factory/svc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ var _ = Describe("CreateOrUpdateService handlers", func() {
It("should successfully create the client service with custom spec", func() {
svc := &corev1.Service{}
cluster := etcdcluster.DeepCopy()
cluster.Spec.ServiceTemplate = &etcdaenixiov1alpha1.ServiceSpec{
cluster.Spec.ServiceTemplate = &etcdaenixiov1alpha1.EmbeddedService{
EmbeddedObjectMetadata: etcdaenixiov1alpha1.EmbeddedObjectMetadata{
Name: "client-name",
Labels: map[string]string{"label": "value"},
Expand Down Expand Up @@ -181,7 +181,7 @@ var _ = Describe("CreateOrUpdateService handlers", func() {
trafficPolicy := corev1.ServiceInternalTrafficPolicyLocal
svc := &corev1.Service{}
cluster := etcdcluster.DeepCopy()
cluster.Spec.ServiceTemplate = &etcdaenixiov1alpha1.ServiceSpec{
cluster.Spec.ServiceTemplate = &etcdaenixiov1alpha1.EmbeddedService{
EmbeddedObjectMetadata: etcdaenixiov1alpha1.EmbeddedObjectMetadata{
Name: "client-name",
Labels: map[string]string{"label": "value"},
Expand Down
5 changes: 4 additions & 1 deletion internal/k8sutils/strategicmerge.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
"k8s.io/apimachinery/pkg/util/strategicpatch"
)

// StrategicMerge merges two objects using strategic merge patch.
// It accepts two objects, base and patch, and returns a merged object.
// Both base and patch objects must be of the same type and must not be a pointer.
func StrategicMerge[K any](base, patch K) (merged K, err error) {
baseBytes, err := json.Marshal(base)
if err != nil {
Expand All @@ -20,7 +23,7 @@ func StrategicMerge[K any](base, patch K) (merged K, err error) {

mergedBytes, err := strategicpatch.StrategicMergePatch(baseBytes, patchBytes, &merged)
if err != nil {
return merged, fmt.Errorf("cannot patch base pod spec with podTemplate.spec: %w", err)
return merged, fmt.Errorf("cannot patch base object with given spec: %w", err)
}

err = json.Unmarshal(mergedBytes, &merged)
Expand Down

0 comments on commit dd19281

Please sign in to comment.