Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DEVHAS-380] Have the Application controller add/remove components from its model #363

Merged
merged 10 commits into from
Aug 1, 2023
53 changes: 51 additions & 2 deletions controllers/application_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import (
"context"
"fmt"
"reflect"
"time"

"github.com/prometheus/client_golang/prometheus"
Expand All @@ -32,12 +33,15 @@
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/util/workqueue"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
"sigs.k8s.io/yaml"

appstudiov1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1"
Expand Down Expand Up @@ -172,6 +176,15 @@
r.SetCreateConditionAndUpdateCR(ctx, req, &application, err)
return reconcile.Result{}, err
}

// Find all components owned by the application
err = r.getAndAddComponentApplicationsToModel(log, req, application.Name, devfileData.GetDevfileWorkspaceSpec())
if err != nil {
r.SetCreateConditionAndUpdateCR(ctx, req, &application, err)
log.Error(err, fmt.Sprintf("Unable to add components to application model for %v", req.NamespacedName))
return ctrl.Result{}, err
}

Check warning on line 186 in controllers/application_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/application_controller.go#L183-L186

Added lines #L183 - L186 were not covered by tests

yamlData, err := yaml.Marshal(devfileData)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to marshall Application devfile, exiting reconcile loop %v", req.NamespacedName))
Expand All @@ -197,11 +210,24 @@
return ctrl.Result{}, err
}

updateRequired := false
// nil out the attributes and projects for the application devfile
// The Attributes contain any image components for the application
// And the projects contains any git components for the application
devWorkspacesSpec := devfileData.GetDevfileWorkspaceSpec().DeepCopy()
devWorkspacesSpec.Attributes = nil
devWorkspacesSpec.Projects = nil
Comment on lines +218 to +219
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait why do we do this again

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make comparisons of the added git projects (under projects in the devfile spec) and container image projects (under attributes) easier in the devfile app model. Basically we create a copy of the devworkspace spec, nil out the attributes and projects, add the components to it, and compare with the existing devworkspace spec.


err = r.getAndAddComponentApplicationsToModel(log, req, application.Name, devWorkspacesSpec)
if err != nil {
r.SetUpdateConditionAndUpdateCR(ctx, req, &application, err)
log.Error(err, fmt.Sprintf("Unable to add components to application model for %v", req.NamespacedName))
return ctrl.Result{}, err
}

Check warning on line 226 in controllers/application_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/application_controller.go#L223-L226

Added lines #L223 - L226 were not covered by tests
// Update any specific fields that changed
displayName := application.Spec.DisplayName
description := application.Spec.Description
devfileMeta := devfileData.GetMetadata()
updateRequired := false
if devfileMeta.Name != displayName {
devfileMeta.Name = displayName
updateRequired = true
Expand All @@ -210,10 +236,17 @@
devfileMeta.Description = description
updateRequired = true
}

oldDevSpec := devfileData.GetDevfileWorkspaceSpec()
if !reflect.DeepEqual(oldDevSpec.Attributes, devWorkspacesSpec.Attributes) || !reflect.DeepEqual(oldDevSpec.Projects, devWorkspacesSpec.Projects) {
devfileData.SetDevfileWorkspaceSpec(*devWorkspacesSpec)
updateRequired = true
}

if updateRequired {
devfileData.SetMetadata(devfileMeta)

// Update the hasApp CR with the new devfile
// Update the Application CR with the new devfile
yamlData, err := yaml.Marshal(devfileData)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to marshall Application devfile, exiting reconcile loop %v", req.NamespacedName))
Expand All @@ -224,6 +257,7 @@
application.Status.Devfile = string(yamlData)
r.SetUpdateConditionAndUpdateCR(ctx, req, &application, nil)
}

}

log.Info(fmt.Sprintf("Finished reconcile loop for %v", req.NamespacedName))
Expand Down Expand Up @@ -257,5 +291,20 @@
return false
},
}).
// Watch Components (Create and Delete events only) as a secondary resource
Watches(&source.Kind{Type: &appstudiov1alpha1.Component{}}, handler.EnqueueRequestsFromMapFunc(MapComponentToApplication()), builder.WithPredicates(predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool {
return true
},
UpdateFunc: func(e event.UpdateEvent) bool {
return false
},
DeleteFunc: func(e event.DeleteEvent) bool {
return true
},
GenericFunc: func(e event.GenericEvent) bool {
return false
},

Check warning on line 307 in controllers/application_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/application_controller.go#L302-L307

Added lines #L302 - L307 were not covered by tests
})).
Complete(r)
}
24 changes: 0 additions & 24 deletions controllers/component_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,10 @@
return ctrl.Result{}, err
} else {
log.Info(fmt.Sprintf("GitOps re-generation successful for %s", component.Name))
err := r.SetGitOpsGeneratedConditionAndUpdateCR(ctx, req, &component, nil)
if err != nil {
return ctrl.Result{}, err
}

Check warning on line 209 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L206-L209

Added lines #L206 - L209 were not covered by tests
isGitOpsRegenSuccessful = true
}
} else if condition.Type == "Updated" && condition.Reason == "Error" && condition.Status == metav1.ConditionFalse {
Expand All @@ -215,16 +215,16 @@
}

if isGitOpsRegenSuccessful && isUpdateConditionPresent {
err = r.SetUpdateConditionAndUpdateCR(ctx, req, &component, nil)
if err != nil {
return ctrl.Result{}, err
}

Check warning on line 221 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L218-L221

Added lines #L218 - L221 were not covered by tests
return ctrl.Result{}, nil
} else if isGitOpsRegenSuccessful {
err = r.SetCreateConditionAndUpdateCR(ctx, req, &component, nil)
if err != nil {
return ctrl.Result{}, err
}

Check warning on line 227 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L224-L227

Added lines #L224 - L227 were not covered by tests
return ctrl.Result{}, nil
}

Expand Down Expand Up @@ -263,8 +263,8 @@
sourceURL := source.GitSource.URL
// If the repository URL ends in a forward slash, remove it to avoid issues with default branch lookup
if string(sourceURL[len(sourceURL)-1]) == "/" {
sourceURL = sourceURL[0 : len(sourceURL)-1]
}

Check warning on line 267 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L266-L267

Added lines #L266 - L267 were not covered by tests
log.Info(fmt.Sprintf("Look for default branch of repo %s... %v", source.GitSource.URL, req.NamespacedName))
metricsLabel := prometheus.Labels{"controller": cdqName, "tokenName": ghClient.TokenName, "operation": "GetDefaultBranchFromURL"}
metrics.ControllerGitRequest.With(metricsLabel).Inc()
Expand All @@ -282,8 +282,8 @@
_ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, retErr)
return ctrl.Result{}, retErr
} else {
source.GitSource.Revision = "main"
}

Check warning on line 286 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L285-L286

Added lines #L285 - L286 were not covered by tests
}
}

Expand All @@ -293,7 +293,7 @@
gitURL, err = util.ConvertGitHubURL(source.GitSource.URL, source.GitSource.Revision, context)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to convert Github URL to raw format, exiting reconcile loop %v", req.NamespacedName))
_ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err)

Check warning on line 296 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L296

Added line #L296 was not covered by tests
return ctrl.Result{}, err
}

Expand All @@ -310,7 +310,7 @@
devfileBytes, err = spi.DownloadDevfileUsingSPI(r.SPIClient, ctx, component.Namespace, source.GitSource.URL, source.GitSource.Revision, context)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to download from any known devfile locations from %s %v", source.GitSource.URL, req.NamespacedName))
_ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err)

Check warning on line 313 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L313

Added line #L313 was not covered by tests
return ctrl.Result{}, err
}
}
Expand All @@ -327,18 +327,18 @@
} else if source.GitSource.DockerfileURL != "" {
devfileData, err := devfile.CreateDevfileForDockerfileBuild(source.GitSource.DockerfileURL, "./", component.Name, component.Spec.Application)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to create devfile for Dockerfile build %v", req.NamespacedName))
_ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err)

Check warning on line 331 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L330-L331

Added lines #L330 - L331 were not covered by tests
return ctrl.Result{}, err
}

devfileBytes, err = yaml.Marshal(devfileData)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to marshal devfile, exiting reconcile loop %v", req.NamespacedName))
err = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err)
if err != nil {
return ctrl.Result{}, err
}

Check warning on line 341 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L338-L341

Added lines #L338 - L341 were not covered by tests
return ctrl.Result{}, nil
}
}
Expand All @@ -348,7 +348,7 @@
devfileData, err := devfile.ConvertImageComponentToDevfile(component)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to convert the Image Component to a devfile %v", req.NamespacedName))
_ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err)

Check warning on line 351 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L351

Added line #L351 was not covered by tests
return ctrl.Result{}, err
}
component.Status.ContainerImage = component.Spec.ContainerImage
Expand All @@ -356,10 +356,10 @@
devfileBytes, err = yaml.Marshal(devfileData)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to marshal devfile, exiting reconcile loop %v", req.NamespacedName))
err = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err)
if err != nil {
return ctrl.Result{}, err
}

Check warning on line 362 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L359-L362

Added lines #L359 - L362 were not covered by tests
return ctrl.Result{}, nil
}
}
Expand All @@ -383,7 +383,7 @@
compDevfileData, err = cdqanalysis.ParseDevfile(devfileSrc)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to parse the devfile from Component, exiting reconcile loop %v", req.NamespacedName))
_ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err)

Check warning on line 386 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L386

Added line #L386 was not covered by tests
return ctrl.Result{}, err
}
}
Expand All @@ -391,7 +391,7 @@
err = r.updateComponentDevfileModel(req, compDevfileData, component)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to update the Component Devfile model %v", req.NamespacedName))
_ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err)

Check warning on line 394 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L394

Added line #L394 was not covered by tests
return ctrl.Result{}, err
}

Expand All @@ -403,43 +403,19 @@
hasAppDevfileData, err := cdqanalysis.ParseDevfile(devfileSrc)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to parse the devfile from Application, exiting reconcile loop %v", req.NamespacedName))
_ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err)

Check warning on line 406 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L406

Added line #L406 was not covered by tests
return ctrl.Result{}, err
}

err = r.updateApplicationDevfileModel(hasAppDevfileData, component)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to update the HAS Application Devfile model %v", req.NamespacedName))
_ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err)
return ctrl.Result{}, err
}

yamlHASCompData, err := yaml.Marshal(compDevfileData)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to marshall the Component devfile, exiting reconcile loop %v", req.NamespacedName))
_ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err)

Check warning on line 413 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L413

Added line #L413 was not covered by tests
return ctrl.Result{}, err
}

component.Status.Devfile = string(yamlHASCompData)

// Update the HASApp CR with the new devfile
yamlHASAppData, err := yaml.Marshal(hasAppDevfileData)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to marshall the Application devfile, exiting reconcile loop %v", req.NamespacedName))
_ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err)
return ctrl.Result{}, err
}
hasApplication.Status.Devfile = string(yamlHASAppData)
err = r.Status().Update(ctx, &hasApplication)
if err != nil {
log.Error(err, "Unable to update Application")
// if we're unable to update the Application CR, then we need to err out
// since we need to save a reference of the Component in Application
_ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err)
return ctrl.Result{}, err
}

// Set the container image in the status
component.Status.ContainerImage = component.Spec.ContainerImage

Expand All @@ -462,15 +438,15 @@
} else {
err = r.SetGitOpsGeneratedConditionAndUpdateCR(ctx, req, &component, nil)
if err != nil {
return ctrl.Result{}, err
}

Check warning on line 442 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L441-L442

Added lines #L441 - L442 were not covered by tests
}
}

err = r.SetCreateConditionAndUpdateCR(ctx, req, &component, nil)
if err != nil {
return ctrl.Result{}, err
}

Check warning on line 449 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L448-L449

Added lines #L448 - L449 were not covered by tests
}
} else {

Expand All @@ -491,7 +467,7 @@
err = r.updateComponentDevfileModel(req, hasCompDevfileData, component)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to update the Component Devfile model %v", req.NamespacedName))
_ = r.SetUpdateConditionAndUpdateCR(ctx, req, &component, err)

Check warning on line 470 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L470

Added line #L470 was not covered by tests
return ctrl.Result{}, err
}

Expand All @@ -502,7 +478,7 @@
oldCompDevfileData, err := cdqanalysis.ParseDevfile(devfileSrc)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to parse the devfile from Component status, exiting reconcile loop %v", req.NamespacedName))
_ = r.SetUpdateConditionAndUpdateCR(ctx, req, &component, err)

Check warning on line 481 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L481

Added line #L481 was not covered by tests
return ctrl.Result{}, err
}

Expand All @@ -515,7 +491,7 @@
yamlHASCompData, err := yaml.Marshal(hasCompDevfileData)
if err != nil {
log.Error(err, fmt.Sprintf("Unable to marshall the Component devfile, exiting reconcile loop %v", req.NamespacedName))
_ = r.SetUpdateConditionAndUpdateCR(ctx, req, &component, err)

Check warning on line 494 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L494

Added line #L494 was not covered by tests
return ctrl.Result{}, err
}

Expand Down Expand Up @@ -553,14 +529,14 @@
} else {
err = r.SetGitOpsGeneratedConditionAndUpdateCR(ctx, req, &component, nil)
if err != nil {
return ctrl.Result{}, err
}

Check warning on line 533 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L532-L533

Added lines #L532 - L533 were not covered by tests
}
}
err = r.SetUpdateConditionAndUpdateCR(ctx, req, &component, nil)
if err != nil {
return ctrl.Result{}, err
}

Check warning on line 539 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L538-L539

Added lines #L538 - L539 were not covered by tests

} else {
log.Info(fmt.Sprintf("The Component devfile data was not updated %v", req.NamespacedName))
Expand Down Expand Up @@ -592,7 +568,7 @@
deployAssociatedComponents, err := devfileParser.GetDeployComponents(compDevfileData)
if err != nil {
log.Error(err, "unable to get deploy components")
ioutils.RemoveFolderAndLogError(log, r.AppFS, tempDir)

Check warning on line 571 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L571

Added line #L571 was not covered by tests
return err
}

Expand Down Expand Up @@ -620,7 +596,7 @@
err = r.Generator.CommitAndPush(tempDir, "", gitOpsURL, mappedGitOpsComponent.Name, gitOpsBranch, "Generating GitOps resources")
if err != nil {
log.Error(err, "unable to commit and push gitops resources due to error")
ioutils.RemoveFolderAndLogError(log, r.AppFS, tempDir)

Check warning on line 599 in controllers/component_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/component_controller.go#L599

Added line #L599 was not covered by tests
return err
}

Expand Down
20 changes: 20 additions & 0 deletions controllers/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,23 @@ func MapToBindingByBoundObjectName(cl client.Client, objectType, label string) f
return req
}
}

// MapComponentToApplication returns an event handler that will convert events on a Component CR to events on its parent Application
func MapComponentToApplication() func(object client.Object) []reconcile.Request {
return func(obj client.Object) []reconcile.Request {
component := obj.(*appstudiov1alpha1.Component)

if component != nil && component.Spec.Application != "" {
return []reconcile.Request{
{
NamespacedName: types.NamespacedName{
Namespace: component.Namespace,
Name: component.Spec.Application,
},
},
}
}
// the obj was not in the namespace or it did not have the required Application.
return []reconcile.Request{}
}
}
63 changes: 63 additions & 0 deletions controllers/mapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,69 @@ func TestMapToBindingByBoundObject(t *testing.T) {
})
}

func TestMapApplicationToComponent(t *testing.T) {

const (
HASAppName = "test-app"
HASCompName = "test-comp"
Namespace = "default"
DisplayName = "an application"
ComponentName = "backend"
SampleRepoLink = "https://github.com/devfile-samples/devfile-sample-java-springboot-basic"
)

applicationName := HASAppName + "1"
componentName := HASCompName + "1"
componentName2 := HASCompName + "2"

componentOne := appstudiov1alpha1.Component{
TypeMeta: metav1.TypeMeta{
Kind: "Component",
APIVersion: "appstudio.redhat.com/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: componentName,
Namespace: Namespace,
},
Spec: appstudiov1alpha1.ComponentSpec{
ComponentName: componentName,
Application: applicationName,
},
}
componentTwo := appstudiov1alpha1.Component{
TypeMeta: metav1.TypeMeta{
Kind: "Component",
APIVersion: "appstudio.redhat.com/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: componentName2,
Namespace: Namespace,
},
Spec: appstudiov1alpha1.ComponentSpec{
ComponentName: componentName2,
},
}

//fakeClient := NewFakeClient(t, componentOne, applicationOne)

t.Run("should return component's parent application", func(t *testing.T) {
// when
requests := MapComponentToApplication()(&componentOne)

// then
require.Len(t, requests, 1) // binding4 is not returned because binding4 does not have a label matching the staging env
assert.Contains(t, requests, newRequest(applicationName))
})

t.Run("should return no Application requests when Component app name is nil", func(t *testing.T) {
// when
requests := MapComponentToApplication()(&componentTwo)

// then
require.Empty(t, requests)
})
}

func newRequest(name string) reconcile.Request {
return reconcile.Request{
NamespacedName: types.NamespacedName{
Expand Down
120 changes: 73 additions & 47 deletions controllers/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

func (r *ComponentReconciler) updateComponentDevfileModel(req ctrl.Request, hasCompDevfileData data.DevfileData, component appstudiov1alpha1.Component) error {
Expand Down Expand Up @@ -216,62 +218,86 @@
return nil
}

func (r *ComponentReconciler) updateApplicationDevfileModel(hasAppDevfileData data.DevfileData, component appstudiov1alpha1.Component) error {

if component.Spec.Source.GitSource != nil {
newProject := devfileAPIV1.Project{
Name: component.Spec.ComponentName,
ProjectSource: devfileAPIV1.ProjectSource{
Git: &devfileAPIV1.GitProjectSource{
GitLikeProjectSource: devfileAPIV1.GitLikeProjectSource{
Remotes: map[string]string{
"origin": component.Spec.Source.GitSource.URL,
// addComponentsToApplicationDevfileModel updates the Application's devfile model to include all of the
func (r *ApplicationReconciler) addComponentsToApplicationDevfileModel(devSpec *devfileAPIV1.DevWorkspaceTemplateSpec, components []appstudiov1alpha1.Component) error {

for _, component := range components {
if component.Spec.Source.GitSource != nil {
newProject := devfileAPIV1.Project{
Name: component.Spec.ComponentName,
ProjectSource: devfileAPIV1.ProjectSource{
Git: &devfileAPIV1.GitProjectSource{
GitLikeProjectSource: devfileAPIV1.GitLikeProjectSource{
Remotes: map[string]string{
"origin": component.Spec.Source.GitSource.URL,
},
},
},
},
},
}
projects, err := hasAppDevfileData.GetProjects(common.DevfileOptions{})
if err != nil {
return err
}
for _, project := range projects {
if project.Name == newProject.Name {
return fmt.Errorf("application already has a component with name %s", newProject.Name)
}
}
err = hasAppDevfileData.AddProjects([]devfileAPIV1.Project{newProject})
if err != nil {
return err
}
} else if component.Spec.ContainerImage != "" {
var err error

// Initialize the attributes
devSpec := hasAppDevfileData.GetDevfileWorkspaceSpec()
projects := devSpec.Projects
for _, project := range projects {
if project.Name == newProject.Name {
return fmt.Errorf("application already has a component with name %s", newProject.Name)
}
}
devSpec.Projects = append(devSpec.Projects, newProject)
} else if component.Spec.ContainerImage != "" {
var err error

// Add the image as a top level attribute
devfileAttributes := devSpec.Attributes
if devfileAttributes == nil {
devfileAttributes = attributes.Attributes{}
devSpec.Attributes = devfileAttributes
hasAppDevfileData.SetDevfileWorkspaceSpec(*devSpec)
}
imageAttrString := fmt.Sprintf("containerImage/%s", component.Spec.ComponentName)
componentImage := devfileAttributes.GetString(imageAttrString, &err)
if err != nil {
if _, ok := err.(*attributes.KeyNotFoundError); !ok {
return err
// Add the image as a top level attribute
devfileAttributes := devSpec.Attributes
if devfileAttributes == nil {
devfileAttributes = attributes.Attributes{}
devSpec.Attributes = devfileAttributes
}
imageAttrString := fmt.Sprintf("containerImage/%s", component.Spec.ComponentName)
componentImage := devfileAttributes.GetString(imageAttrString, &err)
if err != nil {
if _, ok := err.(*attributes.KeyNotFoundError); !ok {
return err
}
}
if componentImage != "" {
return fmt.Errorf("application already has a component with name %s", component.Name)
}
devSpec.Attributes = devfileAttributes.PutString(imageAttrString, component.Spec.ContainerImage)

} else {
return fmt.Errorf("component source is nil")
}
if componentImage != "" {
return fmt.Errorf("application already has a component with name %s", component.Name)

}

return nil
}

// getAndAddComponentApplicationsToModel retrieves the list of components that belong to the application CR and adds them to the application's devfile model
func (r *ApplicationReconciler) getAndAddComponentApplicationsToModel(log logr.Logger, req reconcile.Request, applicationName string, devSpec *devfileAPIV1.DevWorkspaceTemplateSpec) error {

// Find all components owned by the application
var components []appstudiov1alpha1.Component
var componentList appstudiov1alpha1.ComponentList
var err error
err = r.Client.List(ctx, &componentList, &client.ListOptions{
Namespace: req.NamespacedName.Namespace,
})
if err != nil {
log.Error(err, fmt.Sprintf("Unable to list Components for %v", req.NamespacedName))
return err
}

Check warning on line 288 in controllers/update.go

View check run for this annotation

Codecov / codecov/patch

controllers/update.go#L286-L288

Added lines #L286 - L288 were not covered by tests

for _, component := range componentList.Items {
if component.Spec.Application == applicationName {
components = append(components, component)
}
devSpec.Attributes = devfileAttributes.PutString(imageAttrString, component.Spec.ContainerImage)
hasAppDevfileData.SetDevfileWorkspaceSpec(*devSpec)
}

} else {
return fmt.Errorf("component source is nil")
// Add the components to the Devfile model
err = r.addComponentsToApplicationDevfileModel(devSpec, components)
if err != nil {
log.Error(err, fmt.Sprintf("Error adding components to devfile for Application %v", req.NamespacedName))
return err
}

return nil
Expand Down
Loading
Loading