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 }