github.com/redhat-appstudio/release-service@v0.0.0-20240507045911-a8558ef3422a/controllers/release/adapter.go (about)

     1  /*
     2  Copyright 2022.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package release
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"os"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/go-logr/logr"
    27  	"github.com/konflux-ci/operator-toolkit/controller"
    28  	integrationgitops "github.com/redhat-appstudio/integration-service/gitops"
    29  	"github.com/redhat-appstudio/release-service/api/v1alpha1"
    30  	"github.com/redhat-appstudio/release-service/loader"
    31  	"github.com/redhat-appstudio/release-service/metadata"
    32  	"github.com/redhat-appstudio/release-service/syncer"
    33  	"github.com/redhat-appstudio/release-service/tekton/utils"
    34  	tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
    35  	rbac "k8s.io/api/rbac/v1"
    36  	"k8s.io/apimachinery/pkg/api/errors"
    37  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    38  	"k8s.io/apimachinery/pkg/types"
    39  	"knative.dev/pkg/apis"
    40  	ctrl "sigs.k8s.io/controller-runtime"
    41  	"sigs.k8s.io/controller-runtime/pkg/client"
    42  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    43  )
    44  
    45  // adapter holds the objects needed to reconcile a Release.
    46  type adapter struct {
    47  	client               client.Client
    48  	ctx                  context.Context
    49  	loader               loader.ObjectLoader
    50  	logger               *logr.Logger
    51  	release              *v1alpha1.Release
    52  	releaseServiceConfig *v1alpha1.ReleaseServiceConfig
    53  	syncer               *syncer.Syncer
    54  	validations          []controller.ValidationFunction
    55  }
    56  
    57  // newAdapter creates and returns an adapter instance.
    58  func newAdapter(ctx context.Context, client client.Client, release *v1alpha1.Release, loader loader.ObjectLoader, logger *logr.Logger) *adapter {
    59  	releaseAdapter := &adapter{
    60  		client:  client,
    61  		ctx:     ctx,
    62  		loader:  loader,
    63  		logger:  logger,
    64  		release: release,
    65  		syncer:  syncer.NewSyncerWithContext(client, logger, ctx),
    66  	}
    67  
    68  	releaseAdapter.validations = []controller.ValidationFunction{
    69  		releaseAdapter.validateProcessingResources,
    70  		releaseAdapter.validateAuthor,
    71  		releaseAdapter.validatePipelineRef,
    72  	}
    73  
    74  	return releaseAdapter
    75  }
    76  
    77  // EnsureConfigIsLoaded is an operation that will load the service ReleaseServiceConfig from the manager namespace. If not found,
    78  // an empty ReleaseServiceConfig resource will be generated and attached to the adapter.
    79  func (a *adapter) EnsureConfigIsLoaded() (controller.OperationResult, error) {
    80  	namespace := os.Getenv("SERVICE_NAMESPACE")
    81  	if namespace == "" {
    82  		patch := client.MergeFrom(a.release.DeepCopy())
    83  		a.release.MarkValidationFailed("SERVICE_NAMESPACE env var not set")
    84  		a.release.MarkReleaseFailed("Release validation failed")
    85  		return controller.RequeueOnErrorOrStop(a.client.Status().Patch(a.ctx, a.release, patch))
    86  	}
    87  
    88  	var err error
    89  	a.releaseServiceConfig, err = a.loader.GetReleaseServiceConfig(a.ctx, a.client, v1alpha1.ReleaseServiceConfigResourceName, namespace)
    90  	if err != nil && !errors.IsNotFound(err) {
    91  		return controller.RequeueWithError(err)
    92  	}
    93  
    94  	if err != nil {
    95  		a.releaseServiceConfig = a.getEmptyReleaseServiceConfig(namespace)
    96  	}
    97  
    98  	return controller.ContinueProcessing()
    99  }
   100  
   101  // EnsureFinalizersAreCalled is an operation that will ensure that finalizers are called whenever the Release being
   102  // processed is marked for deletion. Once finalizers get called, the finalizer will be removed and the Release will go
   103  // back to the queue, so it gets deleted. If a finalizer function fails its execution or a finalizer fails to be removed,
   104  // the Release will be requeued with the error attached.
   105  func (a *adapter) EnsureFinalizersAreCalled() (controller.OperationResult, error) {
   106  	// Check if the Release is marked for deletion and continue processing other operations otherwise
   107  	if a.release.GetDeletionTimestamp() == nil {
   108  		return controller.ContinueProcessing()
   109  	}
   110  
   111  	if controllerutil.ContainsFinalizer(a.release, metadata.ReleaseFinalizer) {
   112  		if err := a.finalizeRelease(); err != nil {
   113  			return controller.RequeueWithError(err)
   114  		}
   115  
   116  		patch := client.MergeFrom(a.release.DeepCopy())
   117  		controllerutil.RemoveFinalizer(a.release, metadata.ReleaseFinalizer)
   118  		err := a.client.Patch(a.ctx, a.release, patch)
   119  		if err != nil {
   120  			return controller.RequeueWithError(err)
   121  		}
   122  	}
   123  
   124  	// Requeue the release again so it gets deleted and other operations are not executed
   125  	return controller.Requeue()
   126  }
   127  
   128  // EnsureFinalizerIsAdded is an operation that will ensure that the Release being processed contains a finalizer.
   129  func (a *adapter) EnsureFinalizerIsAdded() (controller.OperationResult, error) {
   130  	var finalizerFound bool
   131  	for _, finalizer := range a.release.GetFinalizers() {
   132  		if finalizer == metadata.ReleaseFinalizer {
   133  			finalizerFound = true
   134  		}
   135  	}
   136  
   137  	if !finalizerFound {
   138  		a.logger.Info("Adding Finalizer to the Release")
   139  		patch := client.MergeFrom(a.release.DeepCopy())
   140  		controllerutil.AddFinalizer(a.release, metadata.ReleaseFinalizer)
   141  		err := a.client.Patch(a.ctx, a.release, patch)
   142  
   143  		return controller.RequeueOnErrorOrContinue(err)
   144  	}
   145  
   146  	return controller.ContinueProcessing()
   147  }
   148  
   149  // EnsureReleaseIsCompleted is an operation that will ensure that a Release is completed (marked as released) when
   150  // all required phases (e.g. deployment or processing) have been completed.
   151  func (a *adapter) EnsureReleaseIsCompleted() (controller.OperationResult, error) {
   152  	// Do nothing if the release status has been already added
   153  	if a.release.HasReleaseFinished() {
   154  		return controller.ContinueProcessing()
   155  	}
   156  
   157  	// The processing has to complete for a Release to be completed
   158  	if !a.release.HasProcessingFinished() {
   159  		return controller.ContinueProcessing()
   160  	}
   161  
   162  	patch := client.MergeFrom(a.release.DeepCopy())
   163  	a.release.MarkReleased()
   164  	return controller.RequeueOnErrorOrContinue(a.client.Status().Patch(a.ctx, a.release, patch))
   165  }
   166  
   167  // EnsureReleaseIsRunning is an operation that will ensure that a Release has not finished already and that
   168  // it is marked as releasing. If the Release has finished, no other operation after this one will be executed.
   169  func (a *adapter) EnsureReleaseIsRunning() (controller.OperationResult, error) {
   170  	if a.release.HasReleaseFinished() {
   171  		return controller.StopProcessing()
   172  	}
   173  
   174  	if !a.release.IsReleasing() {
   175  		patch := client.MergeFrom(a.release.DeepCopy())
   176  		a.release.MarkReleasing("")
   177  		return controller.RequeueOnErrorOrContinue(a.client.Status().Patch(a.ctx, a.release, patch))
   178  	}
   179  
   180  	return controller.ContinueProcessing()
   181  }
   182  
   183  // EnsureReleaseIsProcessed is an operation that will ensure that a managed Release PipelineRun associated to the Release
   184  // being processed and a RoleBinding to grant its serviceAccount permissions exist. Otherwise, it will create them.
   185  func (a *adapter) EnsureReleaseIsProcessed() (controller.OperationResult, error) {
   186  	if a.release.HasProcessingFinished() {
   187  		return controller.ContinueProcessing()
   188  	}
   189  
   190  	pipelineRun, err := a.loader.GetManagedReleasePipelineRun(a.ctx, a.client, a.release)
   191  	if err != nil && !errors.IsNotFound(err) {
   192  		return controller.RequeueWithError(err)
   193  	}
   194  
   195  	roleBinding, _ := a.loader.GetRoleBindingFromReleaseStatus(a.ctx, a.client, a.release)
   196  	if err != nil && !errors.IsNotFound(err) && !strings.Contains(err.Error(), "valid reference to a RoleBinding") {
   197  		return controller.RequeueWithError(err)
   198  	}
   199  
   200  	if pipelineRun == nil || !a.release.IsProcessing() {
   201  		resources, err := a.loader.GetProcessingResources(a.ctx, a.client, a.release)
   202  		if err != nil {
   203  			return controller.RequeueWithError(err)
   204  		}
   205  
   206  		if pipelineRun == nil {
   207  			// Only create a RoleBinding if a ServiceAccount is specified
   208  			if roleBinding == nil && resources.ReleasePlanAdmission.Spec.Pipeline.ServiceAccount != "" {
   209  				// This string should probably be a constant somewhere
   210  				roleBinding, err = a.createRoleBindingForClusterRole("release-pipeline-resource-role", resources.ReleasePlanAdmission)
   211  				if err != nil {
   212  					return controller.RequeueWithError(err)
   213  				}
   214  			}
   215  
   216  			pipelineRun, err = a.createManagedPipelineRun(resources)
   217  			if err != nil {
   218  				return controller.RequeueWithError(err)
   219  			}
   220  
   221  			a.logger.Info("Created managed Release PipelineRun",
   222  				"PipelineRun.Name", pipelineRun.Name, "PipelineRun.Namespace", pipelineRun.Namespace)
   223  		}
   224  
   225  		return controller.RequeueOnErrorOrContinue(a.registerProcessingData(pipelineRun, roleBinding))
   226  	}
   227  
   228  	return controller.ContinueProcessing()
   229  }
   230  
   231  // EnsureReleaseExpirationTimeIsAdded is an operation that ensures that a Release has the ExpirationTime set.
   232  func (a *adapter) EnsureReleaseExpirationTimeIsAdded() (controller.OperationResult, error) {
   233  	if a.release.Status.ExpirationTime == nil {
   234  		releasePlan, err := a.loader.GetReleasePlan(a.ctx, a.client, a.release)
   235  		if err != nil && !errors.IsNotFound(err) {
   236  			return controller.RequeueWithError(err)
   237  		}
   238  
   239  		patch := client.MergeFrom(a.release.DeepCopy())
   240  		if a.release.Spec.GracePeriodDays == 0 {
   241  			a.release.Spec.GracePeriodDays = releasePlan.Spec.ReleaseGracePeriodDays
   242  		}
   243  		a.release.SetExpirationTime(time.Duration(a.release.Spec.GracePeriodDays))
   244  
   245  		return controller.RequeueOnErrorOrContinue(a.client.Status().Patch(a.ctx, a.release, patch))
   246  	}
   247  
   248  	return controller.ContinueProcessing()
   249  }
   250  
   251  // EnsureReleaseIsValid is an operation that will ensure that a Release is valid by performing all
   252  // validation checks.
   253  func (a *adapter) EnsureReleaseIsValid() (controller.OperationResult, error) {
   254  	patch := client.MergeFrom(a.release.DeepCopy())
   255  
   256  	result := controller.Validate(a.validations...)
   257  	if !result.Valid {
   258  		if result.Err != nil {
   259  			return controller.RequeueWithError(result.Err)
   260  		}
   261  		a.release.MarkReleaseFailed("Release validation failed")
   262  	}
   263  
   264  	// IsReleasing will be false if MarkReleaseFailed was called
   265  	if a.release.IsReleasing() {
   266  		a.release.MarkValidated()
   267  		return controller.RequeueOnErrorOrContinue(a.client.Status().Patch(a.ctx, a.release, patch))
   268  	}
   269  
   270  	return controller.RequeueOnErrorOrStop(a.client.Status().Patch(a.ctx, a.release, patch))
   271  }
   272  
   273  // EnsureReleaseProcessingIsTracked is an operation that will ensure that the managed Release PipelineRun status is tracked
   274  // in the Release being processed.
   275  func (a *adapter) EnsureReleaseProcessingIsTracked() (controller.OperationResult, error) {
   276  	if !a.release.IsProcessing() || a.release.HasProcessingFinished() {
   277  		return controller.ContinueProcessing()
   278  	}
   279  
   280  	pipelineRun, err := a.loader.GetManagedReleasePipelineRun(a.ctx, a.client, a.release)
   281  	if err != nil {
   282  		return controller.RequeueWithError(err)
   283  	}
   284  	if pipelineRun != nil {
   285  		err = a.registerProcessingStatus(pipelineRun)
   286  		if err != nil {
   287  			return controller.RequeueWithError(err)
   288  		}
   289  	}
   290  
   291  	return controller.ContinueProcessing()
   292  }
   293  
   294  // EnsureReleaseProcessingResourcesAreCleanedUp is an operation that will ensure that the resources created for the Release
   295  // Processing step are cleaned up once processing is finished.
   296  func (a *adapter) EnsureReleaseProcessingResourcesAreCleanedUp() (controller.OperationResult, error) {
   297  	if !a.release.HasProcessingFinished() {
   298  		return controller.ContinueProcessing()
   299  	}
   300  
   301  	pipelineRun, err := a.loader.GetManagedReleasePipelineRun(a.ctx, a.client, a.release)
   302  	if err != nil && !errors.IsNotFound(err) {
   303  		return controller.RequeueWithError(err)
   304  	}
   305  
   306  	roleBinding, err := a.loader.GetRoleBindingFromReleaseStatus(a.ctx, a.client, a.release)
   307  	if err != nil && !errors.IsNotFound(err) && !strings.Contains(err.Error(), "valid reference to a RoleBinding") {
   308  		return controller.RequeueWithError(err)
   309  	}
   310  
   311  	return controller.RequeueOnErrorOrContinue(a.cleanupProcessingResources(pipelineRun, roleBinding))
   312  }
   313  
   314  // cleanupProcessingResources cleans up the PipelineRun created for the Release Processing
   315  // and all resources that were created in order for the PipelineRun to succeed.
   316  func (a *adapter) cleanupProcessingResources(pipelineRun *tektonv1.PipelineRun, roleBinding *rbac.RoleBinding) error {
   317  	if roleBinding != nil {
   318  		err := a.client.Delete(a.ctx, roleBinding)
   319  		if err != nil {
   320  			return err
   321  		}
   322  	}
   323  
   324  	if pipelineRun != nil {
   325  		if controllerutil.ContainsFinalizer(pipelineRun, metadata.ReleaseFinalizer) {
   326  			patch := client.MergeFrom(pipelineRun.DeepCopy())
   327  			controllerutil.RemoveFinalizer(pipelineRun, metadata.ReleaseFinalizer)
   328  			return a.client.Patch(a.ctx, pipelineRun, patch)
   329  		}
   330  	}
   331  
   332  	return nil
   333  }
   334  
   335  // createManagedPipelineRun creates and returns a new managed Release PipelineRun. The new PipelineRun will include owner
   336  // annotations, so it triggers Release reconciles whenever it changes. The Pipeline information and the parameters to it
   337  // will be extracted from the given ReleaseStrategy. The Release's Snapshot will also be passed to the release
   338  // PipelineRun.
   339  func (a *adapter) createManagedPipelineRun(resources *loader.ProcessingResources) (*tektonv1.PipelineRun, error) {
   340  	pipelineRun, err := utils.NewPipelineRunBuilder("managed", resources.ReleasePlanAdmission.Namespace).
   341  		WithAnnotations(metadata.GetAnnotationsWithPrefix(a.release, integrationgitops.PipelinesAsCodePrefix)).
   342  		WithFinalizer(metadata.ReleaseFinalizer).
   343  		WithLabels(map[string]string{
   344  			metadata.ApplicationNameLabel:  resources.ReleasePlan.Spec.Application,
   345  			metadata.PipelinesTypeLabel:    metadata.ManagedPipelineType,
   346  			metadata.ReleaseNameLabel:      a.release.Name,
   347  			metadata.ReleaseNamespaceLabel: a.release.Namespace,
   348  			metadata.ReleaseSnapshotLabel:  a.release.Spec.Snapshot,
   349  		}).
   350  		WithObjectReferences(a.release, resources.ReleasePlan, resources.ReleasePlanAdmission, a.releaseServiceConfig,
   351  			resources.Snapshot).
   352  		WithObjectSpecsAsJson(resources.EnterpriseContractPolicy).
   353  		WithOwner(a.release).
   354  		WithParamsFromConfigMap(resources.EnterpriseContractConfigMap, []string{"verify_ec_task_bundle"}).
   355  		WithPipelineRef(resources.ReleasePlanAdmission.Spec.Pipeline.PipelineRef.ToTektonPipelineRef()).
   356  		WithServiceAccount(resources.ReleasePlanAdmission.Spec.Pipeline.ServiceAccount).
   357  		WithTimeouts(&resources.ReleasePlanAdmission.Spec.Pipeline.Timeouts, &a.releaseServiceConfig.Spec.DefaultTimeouts).
   358  		WithWorkspaceFromVolumeTemplate(
   359  			os.Getenv("DEFAULT_RELEASE_WORKSPACE_NAME"),
   360  			os.Getenv("DEFAULT_RELEASE_WORKSPACE_SIZE"),
   361  		).
   362  		Build()
   363  
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  
   368  	err = a.client.Create(a.ctx, pipelineRun)
   369  	if err != nil {
   370  		return nil, err
   371  	}
   372  
   373  	return pipelineRun, nil
   374  }
   375  
   376  // createRoleBindingForClusterRole creates a RoleBinding that binds the serviceAccount from the passed
   377  // ReleasePlanAdmission to the passed ClusterRole. If the creation fails, the error is returned. If the creation
   378  // is successful, the RoleBinding is returned.
   379  func (a *adapter) createRoleBindingForClusterRole(clusterRole string, releasePlanAdmission *v1alpha1.ReleasePlanAdmission) (*rbac.RoleBinding, error) {
   380  	roleBinding := &rbac.RoleBinding{
   381  		ObjectMeta: metav1.ObjectMeta{
   382  			GenerateName: fmt.Sprintf("%s-rolebinding-for-%s-", a.release.Name, clusterRole),
   383  			Namespace:    releasePlanAdmission.Spec.Origin,
   384  		},
   385  		RoleRef: rbac.RoleRef{
   386  			APIGroup: rbac.GroupName,
   387  			Kind:     "ClusterRole",
   388  			Name:     clusterRole,
   389  		},
   390  		Subjects: []rbac.Subject{
   391  			{
   392  				Kind:      "ServiceAccount",
   393  				Name:      releasePlanAdmission.Spec.Pipeline.ServiceAccount,
   394  				Namespace: releasePlanAdmission.Namespace,
   395  			},
   396  		},
   397  	}
   398  
   399  	// Set ownerRef so it is deleted if the Release is deleted
   400  	err := ctrl.SetControllerReference(a.release, roleBinding, a.client.Scheme())
   401  	if err != nil {
   402  		return nil, err
   403  	}
   404  
   405  	err = a.client.Create(a.ctx, roleBinding)
   406  	if err != nil {
   407  		return nil, err
   408  	}
   409  
   410  	return roleBinding, nil
   411  }
   412  
   413  // finalizeRelease will finalize the Release being processed, removing the associated resources.
   414  func (a *adapter) finalizeRelease() error {
   415  	pipelineRun, err := a.loader.GetManagedReleasePipelineRun(a.ctx, a.client, a.release)
   416  	if err != nil {
   417  		return err
   418  	}
   419  
   420  	if pipelineRun != nil {
   421  		// The finalizer could still exist at this point in the case of the PipelineRun not having succeeded at the time
   422  		// of finalizing the Release.
   423  		if controllerutil.ContainsFinalizer(pipelineRun, metadata.ReleaseFinalizer) {
   424  			patch := client.MergeFrom(pipelineRun.DeepCopy())
   425  			removedFinalizer := controllerutil.RemoveFinalizer(pipelineRun, metadata.ReleaseFinalizer)
   426  			if !removedFinalizer {
   427  				return fmt.Errorf("finalizer not removed")
   428  			}
   429  			err := a.client.Patch(a.ctx, pipelineRun, patch)
   430  			if err != nil {
   431  				return err
   432  			}
   433  		}
   434  
   435  		err = a.client.Delete(a.ctx, pipelineRun)
   436  		if err != nil && !errors.IsNotFound(err) {
   437  			return err
   438  		}
   439  	}
   440  
   441  	a.logger.Info("Successfully finalized Release")
   442  
   443  	return nil
   444  }
   445  
   446  // getEmptyReleaseServiceConfig creates and returns an empty ReleaseServiceConfig resource.
   447  func (a *adapter) getEmptyReleaseServiceConfig(namespace string) *v1alpha1.ReleaseServiceConfig {
   448  	releaseServiceConfig := &v1alpha1.ReleaseServiceConfig{
   449  		ObjectMeta: metav1.ObjectMeta{
   450  			Name:      v1alpha1.ReleaseServiceConfigResourceName,
   451  			Namespace: namespace,
   452  		},
   453  	}
   454  	releaseServiceConfig.Kind = "ReleaseServiceConfig"
   455  	return releaseServiceConfig
   456  }
   457  
   458  // registerProcessingData adds all the Release processing information to its Status and marks it as processing.
   459  func (a *adapter) registerProcessingData(releasePipelineRun *tektonv1.PipelineRun, roleBinding *rbac.RoleBinding) error {
   460  	if releasePipelineRun == nil {
   461  		return nil
   462  	}
   463  
   464  	patch := client.MergeFrom(a.release.DeepCopy())
   465  
   466  	a.release.Status.Processing.PipelineRun = fmt.Sprintf("%s%c%s",
   467  		releasePipelineRun.Namespace, types.Separator, releasePipelineRun.Name)
   468  	if roleBinding != nil {
   469  		a.release.Status.Processing.RoleBinding = fmt.Sprintf("%s%c%s",
   470  			roleBinding.Namespace, types.Separator, roleBinding.Name)
   471  	}
   472  	a.release.Status.Target = releasePipelineRun.Namespace
   473  
   474  	a.release.MarkProcessing("")
   475  
   476  	return a.client.Status().Patch(a.ctx, a.release, patch)
   477  }
   478  
   479  // registerProcessingStatus updates the status of the Release being processed by monitoring the status of the
   480  // associated managed Release PipelineRun and setting the appropriate state in the Release. If the PipelineRun hasn't
   481  // started/succeeded, no action will be taken.
   482  func (a *adapter) registerProcessingStatus(pipelineRun *tektonv1.PipelineRun) error {
   483  	if pipelineRun != nil && pipelineRun.IsDone() {
   484  		patch := client.MergeFrom(a.release.DeepCopy())
   485  
   486  		condition := pipelineRun.Status.GetCondition(apis.ConditionSucceeded)
   487  		if condition.IsTrue() {
   488  			a.release.MarkProcessed()
   489  		} else {
   490  			a.release.MarkProcessingFailed(condition.Message)
   491  			a.release.MarkReleaseFailed("Release processing failed")
   492  		}
   493  
   494  		return a.client.Status().Patch(a.ctx, a.release, patch)
   495  	}
   496  
   497  	return nil
   498  }
   499  
   500  // syncResources sync all the resources needed to trigger the deployment of the Release being processed.
   501  func (a *adapter) syncResources() error {
   502  	releasePlanAdmission, err := a.loader.GetActiveReleasePlanAdmissionFromRelease(a.ctx, a.client, a.release)
   503  	if err != nil {
   504  		return err
   505  	}
   506  
   507  	snapshot, err := a.loader.GetSnapshot(a.ctx, a.client, a.release)
   508  	if err != nil {
   509  		return err
   510  	}
   511  
   512  	return a.syncer.SyncSnapshot(snapshot, releasePlanAdmission.Namespace)
   513  }
   514  
   515  // validateAuthor will ensure that a valid author exists for the Release and add it to its status. If the Release
   516  // has the automated label but doesn't have automated set in its status, this function will return an error so the
   517  // operation knows to requeue the Release.
   518  func (a *adapter) validateAuthor() *controller.ValidationResult {
   519  	if a.release.IsAttributed() {
   520  		return &controller.ValidationResult{Valid: true}
   521  	}
   522  
   523  	if a.release.Labels[metadata.AutomatedLabel] == "true" && !a.release.IsAutomated() {
   524  		err := fmt.Errorf("automated not set in status for automated release")
   525  		a.release.MarkValidationFailed(err.Error())
   526  		if a.release.CreationTimestamp.Add(5 * time.Minute).Before(time.Now()) {
   527  			return &controller.ValidationResult{Valid: false}
   528  		}
   529  		return &controller.ValidationResult{Err: err}
   530  	}
   531  
   532  	releasePlan, err := a.loader.GetReleasePlan(a.ctx, a.client, a.release)
   533  	if err != nil {
   534  		if errors.IsNotFound(err) {
   535  			a.release.MarkValidationFailed(err.Error())
   536  			return &controller.ValidationResult{Valid: false}
   537  		}
   538  		return &controller.ValidationResult{Err: err}
   539  	}
   540  
   541  	var author string
   542  
   543  	if a.release.Labels[metadata.AutomatedLabel] == "true" {
   544  		author = releasePlan.Labels[metadata.AuthorLabel]
   545  		if author == "" {
   546  			a.release.MarkValidationFailed("no author in the ReleasePlan found for automated release")
   547  			return &controller.ValidationResult{Valid: false}
   548  		}
   549  		a.release.Status.Attribution.StandingAuthorization = true
   550  	} else {
   551  		author = a.release.Labels[metadata.AuthorLabel]
   552  		if author == "" { // webhooks prevent this from happening but they could be disabled in some scenarios
   553  			a.release.MarkValidationFailed("no author found for manual release")
   554  			return &controller.ValidationResult{Valid: false}
   555  		}
   556  	}
   557  
   558  	a.release.Status.Attribution.Author = author
   559  	return &controller.ValidationResult{Valid: true}
   560  }
   561  
   562  // validateProcessingResources will ensure that all the resources needed to process the Release exist.
   563  func (a *adapter) validateProcessingResources() *controller.ValidationResult {
   564  	resources, err := a.loader.GetProcessingResources(a.ctx, a.client, a.release)
   565  	if err != nil {
   566  		if resources == nil || resources.ReleasePlanAdmission == nil || errors.IsNotFound(err) {
   567  			a.release.MarkValidationFailed(err.Error())
   568  			return &controller.ValidationResult{Valid: false}
   569  		}
   570  
   571  		return &controller.ValidationResult{Err: err}
   572  	}
   573  	return &controller.ValidationResult{Valid: true}
   574  }
   575  
   576  // validatePipelineRef checks that the managed Release PipelineRun ref passes the checks from the ReleaseServiceConfig.
   577  func (a *adapter) validatePipelineRef() *controller.ValidationResult {
   578  	releasePlanAdmission, err := a.loader.GetActiveReleasePlanAdmissionFromRelease(a.ctx, a.client, a.release)
   579  	if err != nil {
   580  		if errors.IsNotFound(err) {
   581  			a.release.MarkValidationFailed(err.Error())
   582  			return &controller.ValidationResult{Valid: false}
   583  		}
   584  		return &controller.ValidationResult{Err: err}
   585  	}
   586  
   587  	if !a.releaseServiceConfig.Spec.Debug && releasePlanAdmission.Spec.Pipeline.PipelineRef.IsClusterScoped() {
   588  		a.release.MarkValidationFailed("tried using debug only options while debug mode is disabled in the ReleaseServiceConfig")
   589  		return &controller.ValidationResult{Valid: false}
   590  	}
   591  
   592  	return &controller.ValidationResult{Valid: true}
   593  }