k8s/stset/desire.go
package stset
import (
"context"
"code.cloudfoundry.org/eirini/api"
"code.cloudfoundry.org/eirini/k8s/shared"
"code.cloudfoundry.org/eirini/k8s/utils"
"code.cloudfoundry.org/eirini/k8s/utils/dockerutils"
"code.cloudfoundry.org/lager"
"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
//counterfeiter:generate . SecretsClient
//counterfeiter:generate . StatefulSetCreator
//counterfeiter:generate . LRPToStatefulSetConverter
//counterfeiter:generate . PodDisruptionBudgetUpdater
type LRPToStatefulSetConverter interface {
Convert(statefulSetName string, lrp *api.LRP, privateRegistrySecret *corev1.Secret) (*appsv1.StatefulSet, error)
}
type SecretsClient interface {
Create(ctx context.Context, namespace string, secret *corev1.Secret) (*corev1.Secret, error)
SetOwner(ctx context.Context, secret *corev1.Secret, owner metav1.Object) (*corev1.Secret, error)
Delete(ctx context.Context, namespace string, name string) error
}
type StatefulSetCreator interface {
Create(ctx context.Context, namespace string, statefulSet *appsv1.StatefulSet) (*appsv1.StatefulSet, error)
}
type PodDisruptionBudgetUpdater interface {
Update(ctx context.Context, stset *appsv1.StatefulSet, lrp *api.LRP) error
}
type Desirer struct {
logger lager.Logger
secrets SecretsClient
statefulSets StatefulSetCreator
lrpToStatefulSetConverter LRPToStatefulSetConverter
podDisruptionBudgetCreator PodDisruptionBudgetUpdater
}
func NewDesirer(
logger lager.Logger,
secrets SecretsClient,
statefulSets StatefulSetCreator,
lrpToStatefulSetConverter LRPToStatefulSetConverter,
podDisruptionBudgetCreator PodDisruptionBudgetUpdater,
) Desirer {
return Desirer{
logger: logger,
secrets: secrets,
statefulSets: statefulSets,
lrpToStatefulSetConverter: lrpToStatefulSetConverter,
podDisruptionBudgetCreator: podDisruptionBudgetCreator,
}
}
func (d *Desirer) Desire(ctx context.Context, namespace string, lrp *api.LRP, opts ...shared.Option) error {
logger := d.logger.Session("desire", lager.Data{"guid": lrp.GUID, "version": lrp.Version, "namespace": namespace})
statefulSetName, err := utils.GetStatefulsetName(lrp)
if err != nil {
return err
}
privateRegistrySecret, err := d.createRegistryCredsSecretIfRequired(ctx, namespace, lrp)
if err != nil {
return err
}
st, err := d.lrpToStatefulSetConverter.Convert(statefulSetName, lrp, privateRegistrySecret)
if err != nil {
return err
}
st.Namespace = namespace
err = shared.ApplyOpts(st, opts...)
if err != nil {
return err
}
stSet, err := d.statefulSets.Create(ctx, namespace, st)
if err != nil {
var statusErr *k8serrors.StatusError
if errors.As(err, &statusErr) && statusErr.Status().Reason == metav1.StatusReasonAlreadyExists {
logger.Debug("statefulset-already-exists", lager.Data{"error": err.Error()})
return nil
}
return d.cleanupAndError(ctx, errors.Wrap(err, "failed to create statefulset"), privateRegistrySecret)
}
if err := d.setSecretOwner(ctx, privateRegistrySecret, stSet); err != nil {
logger.Error("failed-to-set-owner-to-the-registry-secret", err)
return errors.Wrap(err, "failed to set owner to the registry secret")
}
if err := d.podDisruptionBudgetCreator.Update(ctx, stSet, lrp); err != nil {
logger.Error("failed-to-create-pod-disruption-budget", err)
return errors.Wrap(err, "failed to create pod disruption budget")
}
return nil
}
func (d *Desirer) setSecretOwner(ctx context.Context, privateRegistrySecret *corev1.Secret, stSet *appsv1.StatefulSet) error {
if privateRegistrySecret == nil {
return nil
}
_, err := d.secrets.SetOwner(ctx, privateRegistrySecret, stSet)
return err
}
func (d *Desirer) createRegistryCredsSecretIfRequired(ctx context.Context, namespace string, lrp *api.LRP) (*corev1.Secret, error) {
if lrp.PrivateRegistry == nil {
return nil, nil // nolint:nilnil
}
secret, err := generateRegistryCredsSecret(lrp)
if err != nil {
return nil, errors.Wrap(err, "failed to generate private registry secret for statefulset")
}
secret, err = d.secrets.Create(ctx, namespace, secret)
return secret, errors.Wrap(err, "failed to create private registry secret for statefulset")
}
func (d *Desirer) cleanupAndError(ctx context.Context, stsetCreationError error, privateRegistrySecret *corev1.Secret) error {
resultError := multierror.Append(nil, stsetCreationError)
if privateRegistrySecret != nil {
err := d.secrets.Delete(ctx, privateRegistrySecret.Namespace, privateRegistrySecret.Name)
if err != nil {
resultError = multierror.Append(resultError, errors.Wrap(err, "failed to cleanup registry secret"))
}
}
return resultError
}
func generateRegistryCredsSecret(lrp *api.LRP) (*corev1.Secret, error) {
dockerConfig := dockerutils.NewDockerConfig(
lrp.PrivateRegistry.Server,
lrp.PrivateRegistry.Username,
lrp.PrivateRegistry.Password,
)
dockerConfigJSON, err := dockerConfig.JSON()
if err != nil {
return nil, errors.Wrap(err, "failed to generate privete registry config")
}
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
GenerateName: PrivateRegistrySecretGenerateName,
},
Type: corev1.SecretTypeDockerConfigJson,
StringData: map[string]string{
dockerutils.DockerConfigKey: dockerConfigJSON,
},
}, nil
}