github.com/redhat-appstudio/release-service@v0.0.0-20240507143925-083712697924/loader/loader.go (about)

     1  package loader
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  
     9  	"k8s.io/utils/strings/slices"
    10  
    11  	toolkit "github.com/konflux-ci/operator-toolkit/loader"
    12  
    13  	ecapiv1alpha1 "github.com/enterprise-contract/enterprise-contract-controller/api/v1alpha1"
    14  	applicationapiv1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1"
    15  	"github.com/redhat-appstudio/release-service/api/v1alpha1"
    16  	"github.com/redhat-appstudio/release-service/metadata"
    17  	tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
    18  	corev1 "k8s.io/api/core/v1"
    19  	rbac "k8s.io/api/rbac/v1"
    20  	"k8s.io/apimachinery/pkg/types"
    21  	"sigs.k8s.io/controller-runtime/pkg/client"
    22  )
    23  
    24  type ObjectLoader interface {
    25  	GetActiveReleasePlanAdmission(ctx context.Context, cli client.Client, releasePlan *v1alpha1.ReleasePlan) (*v1alpha1.ReleasePlanAdmission, error)
    26  	GetActiveReleasePlanAdmissionFromRelease(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*v1alpha1.ReleasePlanAdmission, error)
    27  	GetApplication(ctx context.Context, cli client.Client, releasePlan *v1alpha1.ReleasePlan) (*applicationapiv1alpha1.Application, error)
    28  	GetEnterpriseContractConfigMap(ctx context.Context, cli client.Client) (*corev1.ConfigMap, error)
    29  	GetEnterpriseContractPolicy(ctx context.Context, cli client.Client, releasePlanAdmission *v1alpha1.ReleasePlanAdmission) (*ecapiv1alpha1.EnterpriseContractPolicy, error)
    30  	GetMatchingReleasePlanAdmission(ctx context.Context, cli client.Client, releasePlan *v1alpha1.ReleasePlan) (*v1alpha1.ReleasePlanAdmission, error)
    31  	GetMatchingReleasePlans(ctx context.Context, cli client.Client, releasePlanAdmission *v1alpha1.ReleasePlanAdmission) (*v1alpha1.ReleasePlanList, error)
    32  	GetRelease(ctx context.Context, cli client.Client, name, namespace string) (*v1alpha1.Release, error)
    33  	GetRoleBindingFromReleaseStatus(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*rbac.RoleBinding, error)
    34  	GetManagedReleasePipelineRun(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*tektonv1.PipelineRun, error)
    35  	GetReleasePlan(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*v1alpha1.ReleasePlan, error)
    36  	GetReleaseServiceConfig(ctx context.Context, cli client.Client, name, namespace string) (*v1alpha1.ReleaseServiceConfig, error)
    37  	GetSnapshot(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*applicationapiv1alpha1.Snapshot, error)
    38  	GetProcessingResources(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*ProcessingResources, error)
    39  }
    40  
    41  type loader struct{}
    42  
    43  func NewLoader() ObjectLoader {
    44  	return &loader{}
    45  }
    46  
    47  // GetActiveReleasePlanAdmission returns the ReleasePlanAdmission targeted by the given ReleasePlan.
    48  // Only ReleasePlanAdmissions with the 'auto-release' label set to true (or missing the label, which is
    49  // treated the same as having the label and it being set to true) will be searched for. If a matching
    50  // ReleasePlanAdmission is not found or the List operation fails, an error will be returned.
    51  func (l *loader) GetActiveReleasePlanAdmission(ctx context.Context, cli client.Client, releasePlan *v1alpha1.ReleasePlan) (*v1alpha1.ReleasePlanAdmission, error) {
    52  	releasePlanAdmission, err := l.GetMatchingReleasePlanAdmission(ctx, cli, releasePlan)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	labelValue, found := releasePlanAdmission.GetLabels()[metadata.AutoReleaseLabel]
    58  	if found && labelValue == "false" {
    59  		return nil, fmt.Errorf("found ReleasePlanAdmission '%s' with auto-release label set to false",
    60  			releasePlanAdmission.Name)
    61  	}
    62  
    63  	return releasePlanAdmission, nil
    64  }
    65  
    66  // GetActiveReleasePlanAdmissionFromRelease returns the ReleasePlanAdmission targeted by the ReleasePlan referenced by
    67  // the given Release. Only ReleasePlanAdmissions with the 'auto-release' label set to true (or missing the label, which
    68  // is treated the same as having the label and it being set to true) will be searched for. If a matching
    69  // ReleasePlanAdmission is not found or the List operation fails, an error will be returned.
    70  func (l *loader) GetActiveReleasePlanAdmissionFromRelease(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*v1alpha1.ReleasePlanAdmission, error) {
    71  	releasePlan, err := l.GetReleasePlan(ctx, cli, release)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	return l.GetActiveReleasePlanAdmission(ctx, cli, releasePlan)
    77  }
    78  
    79  // GetApplication returns the Application referenced by the ReleasePlan. If the Application is not found or
    80  // the Get operation fails, an error will be returned.
    81  func (l *loader) GetApplication(ctx context.Context, cli client.Client, releasePlan *v1alpha1.ReleasePlan) (*applicationapiv1alpha1.Application, error) {
    82  	application := &applicationapiv1alpha1.Application{}
    83  	return application, toolkit.GetObject(releasePlan.Spec.Application, releasePlan.Namespace, cli, ctx, application)
    84  }
    85  
    86  // GetEnterpriseContractPolicy returns the EnterpriseContractPolicy referenced by the given ReleasePlanAdmission. If the
    87  // EnterpriseContractPolicy is not found or the Get operation fails, an error is returned.
    88  func (l *loader) GetEnterpriseContractPolicy(ctx context.Context, cli client.Client, releasePlanAdmission *v1alpha1.ReleasePlanAdmission) (*ecapiv1alpha1.EnterpriseContractPolicy, error) {
    89  	enterpriseContractPolicy := &ecapiv1alpha1.EnterpriseContractPolicy{}
    90  	return enterpriseContractPolicy, toolkit.GetObject(releasePlanAdmission.Spec.Policy, releasePlanAdmission.Namespace, cli, ctx, enterpriseContractPolicy)
    91  }
    92  
    93  // GetEnterpriseContractConfigMap returns the defaults ConfigMap in the Enterprise Contract namespace . If the ENTERPRISE_CONTRACT_CONFIG_MAP
    94  // value is invalid or not set, nil is returned. If the ConfigMap is not found or the Get operation fails, an error is returned.
    95  func (l *loader) GetEnterpriseContractConfigMap(ctx context.Context, cli client.Client) (*corev1.ConfigMap, error) {
    96  	enterpriseContractConfigMap := &corev1.ConfigMap{}
    97  	namespacedName := os.Getenv("ENTERPRISE_CONTRACT_CONFIG_MAP")
    98  
    99  	if index := strings.IndexByte(namespacedName, '/'); index >= 0 {
   100  		return enterpriseContractConfigMap, toolkit.GetObject(namespacedName[index+1:], namespacedName[:index],
   101  			cli, ctx, enterpriseContractConfigMap)
   102  	}
   103  
   104  	return nil, nil
   105  
   106  }
   107  
   108  // GetMatchingReleasePlanAdmission returns the ReleasePlanAdmission targeted by the given ReleasePlan.
   109  // If a matching ReleasePlanAdmission is not found or the List operation fails, an error will be returned.
   110  // If more than one matching ReleasePlanAdmission objects are found, an error will be returned.
   111  func (l *loader) GetMatchingReleasePlanAdmission(ctx context.Context, cli client.Client, releasePlan *v1alpha1.ReleasePlan) (*v1alpha1.ReleasePlanAdmission, error) {
   112  	designatedReleasePlanAdmissionName := releasePlan.GetLabels()[metadata.ReleasePlanAdmissionLabel]
   113  	if designatedReleasePlanAdmissionName != "" {
   114  		releasePlanAdmission := &v1alpha1.ReleasePlanAdmission{}
   115  		return releasePlanAdmission, toolkit.GetObject(designatedReleasePlanAdmissionName, releasePlan.Spec.Target, cli, ctx, releasePlanAdmission)
   116  	}
   117  
   118  	releasePlanAdmissions := &v1alpha1.ReleasePlanAdmissionList{}
   119  	err := cli.List(ctx, releasePlanAdmissions,
   120  		client.InNamespace(releasePlan.Spec.Target),
   121  		client.MatchingFields{"spec.origin": releasePlan.Namespace})
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	var foundReleasePlanAdmission *v1alpha1.ReleasePlanAdmission
   127  
   128  	for i, releasePlanAdmission := range releasePlanAdmissions.Items {
   129  		if !slices.Contains(releasePlanAdmission.Spec.Applications, releasePlan.Spec.Application) {
   130  			continue
   131  		}
   132  
   133  		if foundReleasePlanAdmission != nil {
   134  			return nil, fmt.Errorf("multiple ReleasePlanAdmissions found in namespace (%+s) with the origin (%+s) for application '%s'",
   135  				releasePlan.Spec.Target, releasePlan.Namespace, releasePlan.Spec.Application)
   136  		}
   137  
   138  		foundReleasePlanAdmission = &releasePlanAdmissions.Items[i]
   139  	}
   140  
   141  	if foundReleasePlanAdmission == nil {
   142  		return nil, fmt.Errorf("no ReleasePlanAdmission found in namespace (%+s) with the origin (%+s) for application '%s'",
   143  			releasePlan.Spec.Target, releasePlan.Namespace, releasePlan.Spec.Application)
   144  	}
   145  
   146  	return foundReleasePlanAdmission, nil
   147  }
   148  
   149  // GetMatchingReleasePlans returns a list of all ReleasePlans that target the given ReleasePlanAdmission's
   150  // namespace, specify an application that is included in the ReleasePlanAdmission's application list, and
   151  // are in the namespace specified by the ReleasePlanAdmission's origin. If the List operation fails, an
   152  // error will be returned.
   153  func (l *loader) GetMatchingReleasePlans(ctx context.Context, cli client.Client, releasePlanAdmission *v1alpha1.ReleasePlanAdmission) (*v1alpha1.ReleasePlanList, error) {
   154  	releasePlans := &v1alpha1.ReleasePlanList{}
   155  	err := cli.List(ctx, releasePlans,
   156  		client.InNamespace(releasePlanAdmission.Spec.Origin),
   157  		client.MatchingFields{"spec.target": releasePlanAdmission.Namespace})
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	for i := len(releasePlans.Items) - 1; i >= 0; i-- {
   163  		if !slices.Contains(releasePlanAdmission.Spec.Applications, releasePlans.Items[i].Spec.Application) {
   164  			// Remove ReleasePlans that do not have matching applications from the list
   165  			releasePlans.Items = append(releasePlans.Items[:i], releasePlans.Items[i+1:]...)
   166  		}
   167  	}
   168  
   169  	return releasePlans, nil
   170  }
   171  
   172  // GetRelease returns the Release with the given name and namespace. If the Release is not found or the Get operation
   173  // fails, an error will be returned.
   174  func (l *loader) GetRelease(ctx context.Context, cli client.Client, name, namespace string) (*v1alpha1.Release, error) {
   175  	release := &v1alpha1.Release{}
   176  	return release, toolkit.GetObject(name, namespace, cli, ctx, release)
   177  }
   178  
   179  // GetRoleBindingFromReleaseStatus returns the RoleBinding associated with the given Release. That association is defined
   180  // by the namespaced name stored in the Release's status.
   181  func (l *loader) GetRoleBindingFromReleaseStatus(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*rbac.RoleBinding, error) {
   182  	roleBinding := &rbac.RoleBinding{}
   183  	roleBindingNamespacedName := strings.Split(release.Status.Processing.RoleBinding, string(types.Separator))
   184  	if len(roleBindingNamespacedName) != 2 {
   185  		return nil, fmt.Errorf("release doesn't contain a valid reference to a RoleBinding ('%s')",
   186  			release.Status.Processing.RoleBinding)
   187  	}
   188  
   189  	err := cli.Get(ctx, types.NamespacedName{
   190  		Namespace: roleBindingNamespacedName[0],
   191  		Name:      roleBindingNamespacedName[1],
   192  	}, roleBinding)
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  
   197  	return roleBinding, nil
   198  }
   199  
   200  // GetManagedReleasePipelineRun returns the managed Release PipelineRun referenced by the given Release or nil if it's not found. In the case
   201  // the List operation fails, an error will be returned.
   202  func (l *loader) GetManagedReleasePipelineRun(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*tektonv1.PipelineRun, error) {
   203  	pipelineRuns := &tektonv1.PipelineRunList{}
   204  	err := cli.List(ctx, pipelineRuns,
   205  		client.Limit(1),
   206  		client.MatchingLabels{
   207  			metadata.ReleaseNameLabel:      release.Name,
   208  			metadata.ReleaseNamespaceLabel: release.Namespace,
   209  		})
   210  	if err == nil && len(pipelineRuns.Items) > 0 {
   211  		return &pipelineRuns.Items[0], nil
   212  	}
   213  
   214  	return nil, err
   215  }
   216  
   217  // GetReleasePlan returns the ReleasePlan referenced by the given Release. If the ReleasePlan is not found or
   218  // the Get operation fails, an error will be returned.
   219  func (l *loader) GetReleasePlan(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*v1alpha1.ReleasePlan, error) {
   220  	releasePlan := &v1alpha1.ReleasePlan{}
   221  	return releasePlan, toolkit.GetObject(release.Spec.ReleasePlan, release.Namespace, cli, ctx, releasePlan)
   222  }
   223  
   224  // GetReleaseServiceConfig returns the ReleaseServiceConfig with the given name and namespace. If the ReleaseServiceConfig is not
   225  // found or the Get operation fails, an error will be returned.
   226  func (l *loader) GetReleaseServiceConfig(ctx context.Context, cli client.Client, name, namespace string) (*v1alpha1.ReleaseServiceConfig, error) {
   227  	releaseServiceConfig := &v1alpha1.ReleaseServiceConfig{}
   228  	return releaseServiceConfig, toolkit.GetObject(name, namespace, cli, ctx, releaseServiceConfig)
   229  }
   230  
   231  // GetSnapshot returns the Snapshot referenced by the given Release. If the Snapshot is not found or the Get
   232  // operation fails, an error is returned.
   233  func (l *loader) GetSnapshot(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*applicationapiv1alpha1.Snapshot, error) {
   234  	snapshot := &applicationapiv1alpha1.Snapshot{}
   235  	return snapshot, toolkit.GetObject(release.Spec.Snapshot, release.Namespace, cli, ctx, snapshot)
   236  }
   237  
   238  // ProcessingResources contains the required resources to process the Release.
   239  type ProcessingResources struct {
   240  	EnterpriseContractConfigMap *corev1.ConfigMap
   241  	EnterpriseContractPolicy    *ecapiv1alpha1.EnterpriseContractPolicy
   242  	ReleasePlan                 *v1alpha1.ReleasePlan
   243  	ReleasePlanAdmission        *v1alpha1.ReleasePlanAdmission
   244  	Snapshot                    *applicationapiv1alpha1.Snapshot
   245  }
   246  
   247  // GetProcessingResources returns all the resources required to process the Release. If any of those resources cannot
   248  // be retrieved from the cluster, an error will be returned.
   249  func (l *loader) GetProcessingResources(ctx context.Context, cli client.Client, release *v1alpha1.Release) (*ProcessingResources, error) {
   250  	var err error
   251  	resources := &ProcessingResources{}
   252  
   253  	resources.ReleasePlan, err = l.GetReleasePlan(ctx, cli, release)
   254  	if err != nil {
   255  		return resources, err
   256  	}
   257  
   258  	resources.ReleasePlanAdmission, err = l.GetActiveReleasePlanAdmissionFromRelease(ctx, cli, release)
   259  	if err != nil {
   260  		return resources, err
   261  	}
   262  
   263  	resources.EnterpriseContractConfigMap, err = l.GetEnterpriseContractConfigMap(ctx, cli)
   264  	if err != nil {
   265  		return resources, err
   266  	}
   267  
   268  	resources.EnterpriseContractPolicy, err = l.GetEnterpriseContractPolicy(ctx, cli, resources.ReleasePlanAdmission)
   269  	if err != nil {
   270  		return resources, err
   271  	}
   272  
   273  	resources.Snapshot, err = l.GetSnapshot(ctx, cli, release)
   274  	if err != nil {
   275  		return resources, err
   276  	}
   277  
   278  	return resources, nil
   279  }