scc-operator/pkg/controllers/offline.go

244 lines
9.5 KiB
Go

package controllers
import (
"fmt"
"github.com/SUSE/connect-ng/pkg/registration"
"github.com/rancher/scc-operator/internal/suseconnect/offlinevalidator"
"github.com/rancher/scc-operator/internal/telemetry"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
rootLog "github.com/rancher/scc-operator/internal/log"
"github.com/rancher/scc-operator/internal/suseconnect"
offlineSecrets "github.com/rancher/scc-operator/internal/suseconnect/offline"
"github.com/rancher/scc-operator/internal/types"
v1 "github.com/rancher/scc-operator/pkg/apis/scc.cattle.io/v1"
"github.com/rancher/scc-operator/pkg/controllers/shared"
)
type sccOfflineMode struct {
rancherURL string
rancherUUID string
options *types.RunOptions
registration *v1.Registration
log rootLog.StructuredLogger
offlineSecrets *offlineSecrets.SecretManager
rancherMetrics telemetry.MetricsWrapper
}
const InitialOfflineCertificateReadyMessage = "Awaiting registration certificate secret"
func (s *sccOfflineMode) SetRancherMetrics(rancherMetrics telemetry.MetricsWrapper) {
s.rancherMetrics = rancherMetrics
}
func (s *sccOfflineMode) NeedsRegistration(registrationObj *v1.Registration) bool {
return registrationObj.Spec.OfflineRegistrationCertificateSecretRef == nil &&
(shared.RegistrationHasNotStarted(registrationObj) ||
!registrationObj.HasCondition(v1.RegistrationConditionOfflineRequestReady) ||
v1.RegistrationConditionOfflineRequestReady.IsFalse(registrationObj))
}
func (s *sccOfflineMode) PrepareForRegister(registrationObj *v1.Registration) (*v1.Registration, error) {
if registrationObj.Status.OfflineRegistrationRequest == nil {
err := s.offlineSecrets.InitRequestSecret()
if err != nil {
return registrationObj, err
}
s.offlineSecrets.SetRegistrationOfflineRegistrationRequestSecretRef(registrationObj)
}
return registrationObj, nil
}
func (s *sccOfflineMode) RefreshOfflineRequestSecret() error {
// TODO: sort out something other than nil
sccWrapper := suseconnect.OfflineRancherRegistration(s.rancherURL, s.rancherMetrics)
generatedOfflineRegistrationRequest, err := sccWrapper.PrepareOfflineRegistrationRequest()
if err != nil {
return err
}
return s.offlineSecrets.UpdateOfflineRequest(generatedOfflineRegistrationRequest)
}
func (s *sccOfflineMode) Register(_ *v1.Registration) (suseconnect.RegistrationSystemID, error) {
refreshErr := s.RefreshOfflineRequestSecret()
if refreshErr != nil {
return suseconnect.EmptyRegistrationSystemID, refreshErr
}
return suseconnect.OfflineRegistrationSystemID, nil
}
func (s *sccOfflineMode) PrepareRegisteredForActivation(registrationObj *v1.Registration) (*v1.Registration, error) {
v1.RegistrationConditionOfflineRequestReady.True(registrationObj)
v1.RegistrationConditionOfflineCertificateReady.False(registrationObj)
v1.RegistrationConditionOfflineCertificateReady.SetMessageIfBlank(registrationObj, InitialOfflineCertificateReadyMessage)
registrationObj.SetCurrentCondition(v1.RegistrationConditionOfflineCertificateReady)
return registrationObj, nil
}
// ReconcileRegisterError helps reconcile any errors in the register phase
func (s *sccOfflineMode) ReconcileRegisterError(registrationObj *v1.Registration, registerErr error, phase types.RegistrationPhase) *v1.Registration {
if phase == types.RegistrationInit {
v1.RegistrationConditionOfflineRequestReady.SetError(registrationObj, "Failed to prepare Offline Request secret & ref", registerErr)
}
if phase == types.RegistrationMain {
v1.RegistrationConditionOfflineRequestReady.SetError(registrationObj, "Failed to update Offline Request secret", registerErr)
}
registrationObj.SetCurrentCondition(v1.RegistrationConditionOfflineRequestReady)
return registrationObj
}
func (s *sccOfflineMode) NeedsActivation(registrationObj *v1.Registration) bool {
return registrationObj.Status.OfflineRegistrationRequest != nil &&
shared.RegistrationNeedsActivation(registrationObj)
}
func (s *sccOfflineMode) ReadyForActivation(registrationObj *v1.Registration) bool {
return registrationObj.Status.OfflineRegistrationRequest != nil &&
registrationObj.Spec.OfflineRegistrationCertificateSecretRef != nil
}
func (s *sccOfflineMode) ResetToRegisteredForActivation(registrationObj *v1.Registration) (*v1.Registration, error) {
registrationObj.RemoveCondition(v1.RegistrationConditionActivated)
registrationObj.RemoveCondition(v1.RegistrationConditionOfflineCertificateReady)
registrationObj.RemoveCondition(v1.ResourceConditionFailure)
registrationObj.RemoveCondition(v1.ResourceConditionReady)
v1.ResourceConditionProgressing.True(registrationObj)
registrationObj, prepErr := s.PrepareRegisteredForActivation(registrationObj)
if prepErr != nil {
return nil, fmt.Errorf("failed resetting Registration back to RegisteredForActivation, setting conditions failed: %w", prepErr)
}
certErr := s.RemoveOfflineCertificate()
if certErr != nil {
return nil, fmt.Errorf("failed resetting Registration back to RegisteredForActivation, removing certificate failed: %w", certErr)
}
return registrationObj, nil
}
func (s *sccOfflineMode) Activate(_ *v1.Registration) error {
certReader, err := s.offlineSecrets.OfflineCertificateReader()
if err != nil {
return fmt.Errorf("activate failed, cannot get offline certificate reader: %w", err)
}
offlineCert, certErr := registration.OfflineCertificateFrom(certReader, false)
if certErr != nil {
return fmt.Errorf("activate failed, cannot prepare offline certificate: %w", certErr)
}
offlineCertValidator := offlinevalidator.New(offlineCert, s.rancherUUID)
return offlineCertValidator.ValidateCertificate()
}
func (s *sccOfflineMode) PrepareActivatedForKeepalive(registrationObj *v1.Registration) (*v1.Registration, error) {
// TODO: can we actually get the SCC systemID in offline mode?
// GH issue: https://github.com/SUSE/connect-ng/issues/313
/*
certReader, err := s.offlineSecrets.OfflineCertificateReader()
if err != nil {
return registrationObj, fmt.Errorf("activate failed, cannot get offline certificate reader: %w", err)
}
offlineCert, certErr := registration.OfflineCertificateFrom(certReader, false)
if certErr != nil {
return registrationObj, fmt.Errorf("activate failed, cannot prepare offline certificate: %w", certErr)
}
*/
registrationObj.RemoveCondition(v1.RegistrationConditionOfflineCertificateReady)
v1.RegistrationConditionOfflineCertificateReady.True(registrationObj)
v1.ActivationConditionOfflineDone.True(registrationObj)
return registrationObj, nil
}
func (s *sccOfflineMode) RemoveOfflineCertificate() error {
certErr := s.offlineSecrets.RemoveOfflineCertificate()
if certErr != nil {
return fmt.Errorf("failed removing offline certificate: %w", certErr)
}
return nil
}
func (s *sccOfflineMode) ReconcileActivateError(registrationObj *v1.Registration, activationErr error, _ types.ActivationPhase) *v1.Registration {
// TODO: this will need updating to use phase after todo inside PrepareActivatedForKeepalive is solved
v1.RegistrationConditionActivated.False(registrationObj)
v1.RegistrationConditionActivated.Reason(registrationObj, "offline activation failed")
v1.RegistrationConditionOfflineCertificateReady.SetError(registrationObj, "cannot validate offline certificate", activationErr)
registrationObj.SetCurrentCondition(v1.RegistrationConditionOfflineCertificateReady)
// Cannot recover from this error so must set failure
registrationObj.Status.ActivationStatus.Activated = false
return shared.PrepareFailed(registrationObj, activationErr)
}
func (s *sccOfflineMode) Keepalive(registrationObj *v1.Registration) error {
s.log.Debugf("For now offline keepalive is an intentional noop")
// TODO: eventually keepalive for offline should mimic `PrepareRegisteredForActivation` creation of ORR (to update metrics for next offline registration)
expiresAt := registrationObj.Status.RegistrationExpiresAt
now := metav1.Now()
if expiresAt.Before(&now) {
return fmt.Errorf("offline registration has expired; expired at %v before current time (%v)", expiresAt, now)
}
certReader, err := s.offlineSecrets.OfflineCertificateReader()
if err != nil {
return fmt.Errorf("activate failed, cannot get offline certificate reader: %w", err)
}
offlineCert, certErr := registration.OfflineCertificateFrom(certReader, false)
if certErr != nil {
return fmt.Errorf("activate failed, cannot prepare offline certificate: %w", certErr)
}
offlineCertValidator := offlinevalidator.New(offlineCert, s.rancherUUID)
validateErr := offlineCertValidator.ValidateCertificate()
if validateErr != nil {
return fmt.Errorf("activate failed, cannot validate offline certificate: %w", validateErr)
}
return nil
}
func (s *sccOfflineMode) PrepareKeepaliveSucceeded(registrationObj *v1.Registration) (*v1.Registration, error) {
sccWrapper := suseconnect.OfflineRancherRegistration(s.rancherURL, s.rancherMetrics)
generatedOfflineRegistrationRequest, err := sccWrapper.PrepareOfflineRegistrationRequest()
if err != nil {
return registrationObj, err
}
updateErr := s.offlineSecrets.UpdateOfflineRequest(generatedOfflineRegistrationRequest)
if updateErr != nil {
return registrationObj, updateErr
}
return registrationObj, nil
}
func (s *sccOfflineMode) ReconcileKeepaliveError(registration *v1.Registration, err error) *v1.Registration {
s.log.Error(err)
// TODO: handle errors from Keepalive and PrepareKeepaliveSucceeded
return registration
}
func (s *sccOfflineMode) Deregister() error {
delErr := s.offlineSecrets.Remove()
if delErr != nil {
return fmt.Errorf("deregister failed: %w", delErr)
}
return nil
}
var _ SCCHandler = &sccOfflineMode{}