diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9f7f5b43c..9e99967c4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,88 +12,75 @@ jobs: fmt: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v3 with: go-version: '1.19' - run: make fmt + vet: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v3 with: go-version: '1.19' - run: make vet + build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v3 with: go-version: '1.19' - run: make cmd - build-image: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - run: make podman-build test-unit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v3 with: go-version: '1.19' - run: make test test-api: + needs: + - fmt + - vet + - build + - test-unit runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v3 with: go-version: '1.19' - run: | - make vet DISCONNECTED=1 make run & sleep 15 # probably a dirty solution DISCONNECTED=1 HUB_BASE_URL=http://localhost:8080 make test-api DISCONNECTED=1 HUB_BASE_URL=http://localhost:8080 make test-api # Intentionaly run 2x to catch data left in Hub DB. - test-e2e: + build-image: + needs: + - fmt + - vet + - build + - test-unit + - test-api runs-on: ubuntu-latest + env: + IMG: ttl.sh/konveyor-hub-${{ github.sha }}:2h steps: - - uses: actions/checkout@v3 - - name: start-minikube - uses: konveyor/tackle2-operator/.github/actions/start-minikube@main - - name: Build image in minikube - run: | - export SHELL=/bin/bash - eval $(minikube -p minikube docker-env) - make docker-build - - name: install-tackle - uses: konveyor/tackle2-operator/.github/actions/install-tackle@main - with: - tackle-hub-image: tackle2-hub:latest - tackle-image-pull-policy: IfNotPresent - - - name: save image - run: | - IMG=quay.io/konveyor/tackle2-hub:latest make docker-build - docker save -o /tmp/tackle2-hub.tar quay.io/konveyor/tackle2-hub:latest - - - name: Upload image as artifact - uses: actions/upload-artifact@v3 - with: - name: tackle2-hub - path: /tmp/tackle2-hub.tar - retention-days: 1 + - uses: actions/checkout@v4 + - run: make docker-build + - run: docker push ${IMG} test-integration: - needs: test-e2e - uses: konveyor/ci/.github/workflows/global-ci.yml@main + needs: build-image + uses: konveyor/ci/.github/workflows/global-ci-bundle.yml@main with: - component_name: tackle2-hub + tackle_hub: ttl.sh/konveyor-hub-${{ github.sha }}:2h api_hub_tests_ref: ${{ github.ref }} diff --git a/.github/workflows/march-image-build-push.yml b/.github/workflows/march-image-build-push.yml index 8729d4cda..25bbcf065 100644 --- a/.github/workflows/march-image-build-push.yml +++ b/.github/workflows/march-image-build-push.yml @@ -19,15 +19,9 @@ jobs: runs-on: ubuntu-20.04 strategy: fail-fast: false - steps: - - name: Checkout Push to Registry action - uses: konveyor/release-tools/build-push-quay@main - with: - architectures: "amd64, arm64" - containerfile: "./Dockerfile" - image_name: "tackle2-hub" - image_namespace: "konveyor" - image_registry: "quay.io" - quay_publish_robot: ${{ secrets.QUAY_PUBLISH_ROBOT }} - quay_publish_token: ${{ secrets.QUAY_PUBLISH_TOKEN }} - ref: ${{ github.ref }} + uses: konveyor/release-tools/build-push-images.yaml@main + with: + registry: "quay.io/konveyor" + image_name: "tackle2-hub" + containerfile: "./Dockerfile" + architectures: '[ "amd64", "arm64" ]' diff --git a/api/addon.go b/api/addon.go index 152fa57ef..d08d5cb1a 100644 --- a/api/addon.go +++ b/api/addon.go @@ -6,7 +6,7 @@ import ( "net/http" "github.com/gin-gonic/gin" - crd "github.com/konveyor/tackle2-hub/k8s/api/tackle/v1alpha2" + crd "github.com/konveyor/tackle2-hub/k8s/api/tackle/v1alpha1" core "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" k8s "sigs.k8s.io/controller-runtime/pkg/client" diff --git a/api/analysis.go b/api/analysis.go index 547040f52..eb115113e 100644 --- a/api/analysis.go +++ b/api/analysis.go @@ -89,7 +89,7 @@ func (h AnalysisHandler) AddRoutes(e *gin.Engine) { // Application routeGroup = e.Group("/") routeGroup.Use(Required("applications.analyses")) - routeGroup.POST(AppAnalysesRoot, h.AppCreate) + routeGroup.POST(AppAnalysesRoot, Transaction, h.AppCreate) routeGroup.GET(AppAnalysesRoot, h.AppList) routeGroup.GET(AppAnalysisRoot, h.AppLatest) routeGroup.GET(AppAnalysisReportRoot, h.AppLatestReport) @@ -1300,7 +1300,7 @@ func (h AnalysisHandler) IssueAppReports(ctx *gin.Context) { "app.Name", "app.Description", "b.Name BusinessService", - "a.Effort", + "i.Effort", "COUNT(n.ID) Incidents", "COUNT(distinct n.File) Files", "i.ID IssueID", @@ -1350,7 +1350,7 @@ func (h AnalysisHandler) IssueAppReports(ctx *gin.Context) { r.Name = m.Name r.Description = m.Description r.BusinessService = m.BusinessService - r.Effort = m.Effort + r.Effort = m.Effort * m.Incidents r.Incidents = m.Incidents r.Files = m.Files r.Issue.ID = m.IssueID diff --git a/api/application.go b/api/application.go index 7169d1c2e..3d4940c89 100644 --- a/api/application.go +++ b/api/application.go @@ -58,22 +58,22 @@ func (h ApplicationHandler) AddRoutes(e *gin.Engine) { routeGroup.DELETE(ApplicationRoot, h.Delete) // Tags routeGroup = e.Group("/") - routeGroup.Use(Required("applications")) + routeGroup.Use(Required("applications"), Transaction) routeGroup.GET(ApplicationTagsRoot, h.TagList) routeGroup.GET(ApplicationTagsRoot+"/", h.TagList) routeGroup.POST(ApplicationTagsRoot, h.TagAdd) routeGroup.DELETE(ApplicationTagRoot, h.TagDelete) - routeGroup.PUT(ApplicationTagsRoot, h.TagReplace, Transaction) + routeGroup.PUT(ApplicationTagsRoot, h.TagReplace) // Facts routeGroup = e.Group("/") - routeGroup.Use(Required("applications.facts")) + routeGroup.Use(Required("applications.facts"), Transaction) routeGroup.GET(ApplicationFactsRoot, h.FactGet) routeGroup.GET(ApplicationFactsRoot+"/", h.FactGet) routeGroup.POST(ApplicationFactsRoot, h.FactCreate) routeGroup.GET(ApplicationFactRoot, h.FactGet) routeGroup.PUT(ApplicationFactRoot, h.FactPut) routeGroup.DELETE(ApplicationFactRoot, h.FactDelete) - routeGroup.PUT(ApplicationFactsRoot, h.FactPut, Transaction) + routeGroup.PUT(ApplicationFactsRoot, h.FactPut) // Bucket routeGroup = e.Group("/") routeGroup.Use(Required("applications.bucket")) @@ -84,11 +84,11 @@ func (h ApplicationHandler) AddRoutes(e *gin.Engine) { routeGroup.DELETE(AppBucketContentRoot, h.BucketDelete) // Stakeholders routeGroup = e.Group("/") - routeGroup.Use(Required("applications.stakeholders")) + routeGroup.Use(Required("applications.stakeholders"), Transaction) routeGroup.PUT(AppStakeholdersRoot, h.StakeholdersUpdate) // Assessments routeGroup = e.Group("/") - routeGroup.Use(Required("applications.assessments")) + routeGroup.Use(Required("applications.assessments"), Transaction) routeGroup.GET(AppAssessmentsRoot, h.AssessmentList) routeGroup.POST(AppAssessmentsRoot, h.AssessmentCreate) } @@ -210,11 +210,23 @@ func (h ApplicationHandler) Create(ctx *gin.Context) { } m := r.Model() m.CreateUser = h.BaseHandler.CurrentUser(ctx) - result := h.DB(ctx).Omit("Tags").Create(m) + result := h.DB(ctx).Omit(clause.Associations).Create(m) if result.Error != nil { _ = ctx.Error(result.Error) return } + db := h.DB(ctx).Model(m) + err = db.Association("Identities").Replace(m.Identities) + if err != nil { + _ = ctx.Error(err) + return + } + db = h.DB(ctx).Model(m) + err = db.Association("Contributors").Replace(m.Contributors) + if err != nil { + _ = ctx.Error(err) + return + } tags := []model.ApplicationTag{} if len(r.Tags) > 0 { @@ -1078,11 +1090,21 @@ func (h ApplicationHandler) AssessmentCreate(ctx *gin.Context) { assessment.PrepareForApplication(resolver, application, m) newAssessment = true } - result = h.DB(ctx).Create(m) + result = h.DB(ctx).Omit(clause.Associations).Create(m) if result.Error != nil { _ = ctx.Error(result.Error) return } + err = h.DB(ctx).Model(m).Association("Stakeholders").Replace("Stakeholders", m.Stakeholders) + if err != nil { + _ = ctx.Error(err) + return + } + err = h.DB(ctx).Model(m).Association("StakeholderGroups").Replace("StakeholderGroups", m.StakeholderGroups) + if err != nil { + _ = ctx.Error(err) + return + } if newAssessment { metrics.AssessmentsInitiated.Inc() } @@ -1138,6 +1160,7 @@ func (r *Application) With(m *model.Application, tags []model.ApplicationTag) { r.Identities, ref) } + r.Tags = []TagRef{} for i := range tags { ref := TagRef{} ref.With(tags[i].TagID, tags[i].Tag.Name, tags[i].Source, false) diff --git a/api/archetype.go b/api/archetype.go index d62d88a13..bcadf05cd 100644 --- a/api/archetype.go +++ b/api/archetype.go @@ -136,12 +136,33 @@ func (h ArchetypeHandler) Create(ctx *gin.Context) { } m := r.Model() m.CreateUser = h.CurrentUser(ctx) - result := h.DB(ctx).Create(m) + result := h.DB(ctx).Omit(clause.Associations).Create(m) if result.Error != nil { _ = ctx.Error(result.Error) return } + err = h.DB(ctx).Model(m).Association("Stakeholders").Replace("Stakeholders", m.Stakeholders) + if err != nil { + _ = ctx.Error(err) + return + } + err = h.DB(ctx).Model(m).Association("StakeholderGroups").Replace("StakeholderGroups", m.StakeholderGroups) + if err != nil { + _ = ctx.Error(err) + return + } + err = h.DB(ctx).Model(m).Association("CriteriaTags").Replace("CriteriaTags", m.CriteriaTags) + if err != nil { + _ = ctx.Error(err) + return + } + err = h.DB(ctx).Model(m).Association("Tags").Replace("Tags", m.Tags) + if err != nil { + _ = ctx.Error(err) + return + } + archetypes := []model.Archetype{} db := h.preLoad(h.DB(ctx), "Tags", "CriteriaTags") result = db.Find(&archetypes) @@ -319,11 +340,21 @@ func (h ArchetypeHandler) AssessmentCreate(ctx *gin.Context) { assessment.PrepareForArchetype(resolver, archetype, m) newAssessment = true } - result = h.DB(ctx).Create(m) + result = h.DB(ctx).Omit(clause.Associations).Create(m) if result.Error != nil { _ = ctx.Error(result.Error) return } + err = h.DB(ctx).Model(m).Association("Stakeholders").Replace("Stakeholders", m.Stakeholders) + if err != nil { + _ = ctx.Error(err) + return + } + err = h.DB(ctx).Model(m).Association("StakeholderGroups").Replace("StakeholderGroups", m.StakeholderGroups) + if err != nil { + _ = ctx.Error(err) + return + } if newAssessment { metrics.AssessmentsInitiated.Inc() } diff --git a/api/base.go b/api/base.go index 0b8ca1dfa..602ac3be4 100644 --- a/api/base.go +++ b/api/base.go @@ -14,10 +14,10 @@ import ( "github.com/gin-gonic/gin/binding" liberr "github.com/jortel/go-utils/error" "github.com/jortel/go-utils/logr" - "github.com/konveyor/tackle2-hub/api/reflect" "github.com/konveyor/tackle2-hub/api/sort" "github.com/konveyor/tackle2-hub/auth" "github.com/konveyor/tackle2-hub/model" + "github.com/konveyor/tackle2-hub/reflect" "gopkg.in/yaml.v2" "gorm.io/gorm" "sigs.k8s.io/controller-runtime/pkg/client" diff --git a/api/group.go b/api/group.go index 4e92337b3..dee59ff97 100644 --- a/api/group.go +++ b/api/group.go @@ -97,11 +97,21 @@ func (h StakeholderGroupHandler) Create(ctx *gin.Context) { } m := r.Model() m.CreateUser = h.BaseHandler.CurrentUser(ctx) - result := h.DB(ctx).Create(m) + result := h.DB(ctx).Omit(clause.Associations).Create(m) if result.Error != nil { _ = ctx.Error(result.Error) return } + err = h.DB(ctx).Model(m).Association("Stakeholders").Replace(m.Stakeholders) + if err != nil { + _ = ctx.Error(err) + return + } + err = h.DB(ctx).Model(m).Association("MigrationWaves").Replace(m.MigrationWaves) + if err != nil { + _ = ctx.Error(err) + return + } r.With(m) h.Respond(ctx, http.StatusCreated, r) diff --git a/api/identity.go b/api/identity.go index dbaf606dd..8c631c6f7 100644 --- a/api/identity.go +++ b/api/identity.go @@ -34,7 +34,7 @@ func (h IdentityHandler) AddRoutes(e *gin.Engine) { routeGroup.GET(IdentitiesRoot+"/", h.setDecrypted, h.List) routeGroup.POST(IdentitiesRoot, h.Create) routeGroup.GET(IdentityRoot, h.setDecrypted, h.Get) - routeGroup.PUT(IdentityRoot, h.Update, Transaction) + routeGroup.PUT(IdentityRoot, Transaction, h.Update) routeGroup.DELETE(IdentityRoot, h.Delete) } diff --git a/api/migrationwave.go b/api/migrationwave.go index 9bc36c58c..537108917 100644 --- a/api/migrationwave.go +++ b/api/migrationwave.go @@ -98,8 +98,7 @@ func (h MigrationWaveHandler) Create(ctx *gin.Context) { } m := r.Model() m.CreateUser = h.CurrentUser(ctx) - db := h.DB(ctx).Omit(clause.Associations) - result := db.Create(m) + result := h.DB(ctx).Omit(clause.Associations).Create(m) if result.Error != nil { _ = ctx.Error(result.Error) return diff --git a/api/sort/sort.go b/api/sort/sort.go index 7654ae6d5..2e577a13a 100644 --- a/api/sort/sort.go +++ b/api/sort/sort.go @@ -4,7 +4,7 @@ import ( "strings" "github.com/gin-gonic/gin" - "github.com/konveyor/tackle2-hub/api/reflect" + "github.com/konveyor/tackle2-hub/reflect" "gorm.io/gorm" ) diff --git a/api/stakeholder.go b/api/stakeholder.go index 4cb659310..1a551cf4d 100644 --- a/api/stakeholder.go +++ b/api/stakeholder.go @@ -97,11 +97,31 @@ func (h StakeholderHandler) Create(ctx *gin.Context) { } m := r.Model() m.CreateUser = h.BaseHandler.CurrentUser(ctx) - result := h.DB(ctx).Create(m) + result := h.DB(ctx).Omit(clause.Associations).Create(m) if result.Error != nil { _ = ctx.Error(result.Error) return } + err = h.DB(ctx).Model(m).Association("Groups").Replace(m.Groups) + if err != nil { + _ = ctx.Error(err) + return + } + err = h.DB(ctx).Model(m).Association("Owns").Replace(m.Owns) + if err != nil { + _ = ctx.Error(err) + return + } + err = h.DB(ctx).Model(m).Association("Contributes").Replace(m.Contributes) + if err != nil { + _ = ctx.Error(err) + return + } + err = h.DB(ctx).Model(m).Association("MigrationWaves").Replace(m.MigrationWaves) + if err != nil { + _ = ctx.Error(err) + return + } r.With(m) h.Respond(ctx, http.StatusCreated, r) diff --git a/api/task.go b/api/task.go index 2f4aaec95..886efa41a 100644 --- a/api/task.go +++ b/api/task.go @@ -934,4 +934,7 @@ func (r *TaskDashboard) With(m *model.Task) { r.Started = m.Started r.Terminated = m.Terminated r.Errors = len(m.Errors) + if m.Report != nil { + r.Errors += len(m.Report.Errors) + } } diff --git a/api/taskgroup.go b/api/taskgroup.go index 58a007a4c..2423d304b 100644 --- a/api/taskgroup.go +++ b/api/taskgroup.go @@ -5,7 +5,7 @@ import ( "net/http" "github.com/gin-gonic/gin" - crd "github.com/konveyor/tackle2-hub/k8s/api/tackle/v1alpha2" + crd "github.com/konveyor/tackle2-hub/k8s/api/tackle/v1alpha1" "github.com/konveyor/tackle2-hub/model" tasking "github.com/konveyor/tackle2-hub/task" "gorm.io/gorm/clause" diff --git a/auth/roles.yaml b/auth/roles.yaml index a46df37a2..aa65e7a77 100644 --- a/auth/roles.yaml +++ b/auth/roles.yaml @@ -130,6 +130,7 @@ - get - post - put + - patch - name: tasks.bucket verbs: - delete @@ -327,6 +328,7 @@ - get - post - put + - patch - name: tasks.bucket verbs: - delete @@ -468,6 +470,7 @@ - get - post - put + - patch - name: tasks.bucket verbs: - delete diff --git a/controller/addon.go b/controller/addon.go index 0e5e4705b..35f12f71e 100644 --- a/controller/addon.go +++ b/controller/addon.go @@ -2,13 +2,15 @@ package controller import ( "context" + "strings" "github.com/go-logr/logr" logr2 "github.com/jortel/go-utils/logr" - api "github.com/konveyor/tackle2-hub/k8s/api/tackle/v1alpha2" + api "github.com/konveyor/tackle2-hub/k8s/api/tackle/v1alpha1" "github.com/konveyor/tackle2-hub/settings" "gorm.io/gorm" k8serr "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/storage/names" "k8s.io/client-go/tools/record" k8s "sigs.k8s.io/controller-runtime/pkg/client" @@ -32,9 +34,10 @@ var Settings = &settings.Settings // Add the controller. func Add(mgr manager.Manager, db *gorm.DB) error { reconciler := &Reconciler{ - Client: mgr.GetClient(), - Log: log, - DB: db, + history: make(map[string]byte), + Client: mgr.GetClient(), + Log: log, + DB: db, } cnt, err := controller.New( Name, @@ -59,15 +62,18 @@ func Add(mgr manager.Manager, db *gorm.DB) error { } // Reconciler reconciles addon CRs. +// The history is used to ensure resources are reconciled +// at least once at startup. type Reconciler struct { record.EventRecorder k8s.Client - DB *gorm.DB - Log logr.Logger + DB *gorm.DB + Log logr.Logger + history map[string]byte } // Reconcile a Addon CR. -// Note: Must not a pointer receiver to ensure that the +// Note: Must not be a pointer receiver to ensure that the // logger and other state is not shared. func (r Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (result reconcile.Result, err error) { r.Log = logr2.WithName( @@ -86,18 +92,23 @@ func (r Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (r } return } - // migrate - migrated, err := r.alpha2Migration(addon) - if migrated || err != nil { + _, found := r.history[addon.Name] + if found && addon.Reconciled() { return } - // changed. - err = r.addonChanged(addon) - if err != nil { + r.history[addon.Name] = 1 + addon.Status.Conditions = nil + addon.Status.ObservedGeneration = addon.Generation + // Changed + migrated, err := r.addonChanged(addon) + if migrated || err != nil { return } + // Ready condition. + addon.Status.Conditions = append( + addon.Status.Conditions, + r.ready(addon)) // Apply changes. - addon.Status.ObservedGeneration = addon.Generation err = r.Status().Update(context.TODO(), addon) if err != nil { return @@ -106,47 +117,50 @@ func (r Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (r return } -// addonChanged an addon has been created/updated. -func (r *Reconciler) addonChanged(addon *api.Addon) (err error) { - return -} - -// addonDeleted an addon has been deleted. -func (r *Reconciler) addonDeleted(name string) (err error) { - return -} - -// alpha2Migration migrates to alpha2. -func (r *Reconciler) alpha2Migration(addon *api.Addon) (migrated bool, err error) { - if addon.Spec.Image != nil { - if addon.Spec.Container.Image == "" { - addon.Spec.Container.Image = *addon.Spec.Image +// ready returns the ready condition. +func (r *Reconciler) ready(addon *api.Addon) (ready v1.Condition) { + ready = api.Ready + ready.LastTransitionTime = v1.Now() + ready.ObservedGeneration = addon.Status.ObservedGeneration + err := make([]string, 0) + for i := range addon.Status.Conditions { + cnd := &addon.Status.Conditions[i] + if cnd.Type == api.ValidationError { + err = append(err, cnd.Message) } - addon.Spec.Image = nil - migrated = true } - if addon.Spec.Resources != nil { - if len(addon.Spec.Container.Resources.Limits) == 0 { - addon.Spec.Container.Resources.Limits = (*addon.Spec.Resources).Limits - } - if len(addon.Spec.Container.Resources.Requests) == 0 { - addon.Spec.Container.Resources.Requests = (*addon.Spec.Resources).Requests - } - addon.Spec.Resources = nil - migrated = true - } - if addon.Spec.ImagePullPolicy != nil { - if addon.Spec.Container.ImagePullPolicy == "" { - addon.Spec.Container.ImagePullPolicy = *addon.Spec.ImagePullPolicy - } - addon.Spec.ImagePullPolicy = nil - migrated = true + if len(err) == 0 { + ready.Status = v1.ConditionTrue + ready.Reason = api.Validated + ready.Message = strings.Join(err, ";") + } else { + ready.Status = v1.ConditionFalse + ready.Reason = api.ValidationError } + return +} + +// addonChanged an addon has been created/updated. +func (r *Reconciler) addonChanged(addon *api.Addon) (migrated bool, err error) { + migrated = addon.Migrate() if migrated { err = r.Update(context.TODO(), addon) if err != nil { return } } + if addon.Spec.Container.Image == "" { + cnd := api.ImageNotDefined + cnd.LastTransitionTime = v1.Now() + cnd.ObservedGeneration = addon.Status.ObservedGeneration + addon.Status.Conditions = append( + addon.Status.Conditions, + cnd) + } + return +} + +// addonDeleted an addon has been deleted. +func (r *Reconciler) addonDeleted(name string) (err error) { return } diff --git a/generated/crd/tackle.konveyor.io_addons.yaml b/generated/crd/tackle.konveyor.io_addons.yaml index 55b94c779..8e2771521 100644 --- a/generated/crd/tackle.konveyor.io_addons.yaml +++ b/generated/crd/tackle.konveyor.io_addons.yaml @@ -16,79 +16,6 @@ spec: scope: Namespaced versions: - name: v1alpha1 - schema: - openAPIV3Schema: - description: Addon defines an addon. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of the resource. - properties: - image: - description: Addon fqin. - type: string - imagePullPolicy: - default: IfNotPresent - description: ImagePullPolicy an optional image pull policy. - enum: - - IfNotPresent - - Always - - Never - type: string - resources: - description: Resource requirements. - properties: - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute resources - allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, otherwise - to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - required: - - image - type: object - status: - description: Status defines the observed state of the resource. - properties: - observedGeneration: - description: The most recent generation observed by the controller. - format: int64 - type: integer - type: object - type: object - served: false - storage: false - subresources: - status: {} - - name: v1alpha2 schema: openAPIV3Schema: properties: @@ -1347,12 +1274,79 @@ spec: task: description: Task declares task (kind) compatibility. type: string - required: - - container type: object status: description: Status defines the observed state of the resource. properties: + conditions: + description: Resource conditions. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array observedGeneration: description: The most recent generation observed by the controller. format: int64 diff --git a/generated/crd/tackle.konveyor.io_extensions.yaml b/generated/crd/tackle.konveyor.io_extensions.yaml index 9d9ab9cc3..8d893b9e2 100644 --- a/generated/crd/tackle.konveyor.io_extensions.yaml +++ b/generated/crd/tackle.konveyor.io_extensions.yaml @@ -16,28 +16,6 @@ spec: scope: Namespaced versions: - name: v1alpha1 - schema: - openAPIV3Schema: - description: Extension defines an addon extension. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - type: object - served: false - storage: false - subresources: - status: {} - - name: v1alpha2 schema: openAPIV3Schema: description: Extension defines an addon extension. diff --git a/generated/crd/tackle.konveyor.io_tackles.yaml b/generated/crd/tackle.konveyor.io_tackles.yaml index c1b8af719..00987585c 100644 --- a/generated/crd/tackle.konveyor.io_tackles.yaml +++ b/generated/crd/tackle.konveyor.io_tackles.yaml @@ -16,36 +16,6 @@ spec: scope: Namespaced versions: - name: v1alpha1 - schema: - openAPIV3Schema: - description: Tackle defines a tackle application. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of the resource. - type: object - x-kubernetes-preserve-unknown-fields: true - status: - description: Status defines the observed state of the resource. - type: object - x-kubernetes-preserve-unknown-fields: true - type: object - served: true - storage: false - subresources: - status: {} - - name: v1alpha2 schema: openAPIV3Schema: description: Tackle defines a tackle application. diff --git a/generated/crd/tackle.konveyor.io_tasks.yaml b/generated/crd/tackle.konveyor.io_tasks.yaml index 9f4258947..3e0c175ff 100644 --- a/generated/crd/tackle.konveyor.io_tasks.yaml +++ b/generated/crd/tackle.konveyor.io_tasks.yaml @@ -16,28 +16,6 @@ spec: scope: Namespaced versions: - name: v1alpha1 - schema: - openAPIV3Schema: - description: Task defines a hub task. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - type: object - served: false - storage: false - subresources: - status: {} - - name: v1alpha2 schema: openAPIV3Schema: description: Task defines a hub task. diff --git a/k8s/api/all.go b/k8s/api/all.go index 83ccee910..8e3aa14b3 100644 --- a/k8s/api/all.go +++ b/k8s/api/all.go @@ -6,7 +6,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - +` Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,7 +18,6 @@ package api import ( "github.com/konveyor/tackle2-hub/k8s/api/tackle/v1alpha1" - "github.com/konveyor/tackle2-hub/k8s/api/tackle/v1alpha2" "k8s.io/apimachinery/pkg/runtime" ) @@ -27,8 +26,7 @@ var AddToSchemes runtime.SchemeBuilder func init() { AddToSchemes = append( AddToSchemes, - v1alpha1.SchemeBuilder.AddToScheme, - v1alpha2.SchemeBuilder.AddToScheme) + v1alpha1.SchemeBuilder.AddToScheme) } func AddToScheme(s *runtime.Scheme) error { diff --git a/k8s/api/tackle/v1alpha1/addon.go b/k8s/api/tackle/v1alpha1/addon.go index 9dbbd2ea4..c06e64e70 100644 --- a/k8s/api/tackle/v1alpha1/addon.go +++ b/k8s/api/tackle/v1alpha1/addon.go @@ -19,42 +19,103 @@ package v1alpha1 import ( core "k8s.io/api/core/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) -// AddonSpec defines the desired state of an Addon. +// AddonSpec defines the desired state of the resource. type AddonSpec struct { - // Addon fqin. - Image string `json:"image"` - // ImagePullPolicy an optional image pull policy. - // +kubebuilder:default=IfNotPresent - // +kubebuilder:validation:Enum=IfNotPresent;Always;Never - ImagePullPolicy core.PullPolicy `json:"imagePullPolicy,omitempty"` - // Resource requirements. - Resources core.ResourceRequirements `json:"resources,omitempty"` + // Deprecated: Addon is deprecated. + // +kubebuilder:validation:Optional + Image *string `json:"image,omitempty"` + // Deprecated: ImagePullPolicy is deprecated. + // +kubebuilder:validation:Optional + ImagePullPolicy *core.PullPolicy `json:"imagePullPolicy,omitempty"` + // Deprecated: Resources is deprecated. + // +kubebuilder:validation:Optional + Resources *core.ResourceRequirements `json:"resources,omitempty"` + // + // Task declares task (kind) compatibility. + Task string `json:"task,omitempty"` + // Selector defines criteria to be selected for a task. + Selector string `json:"selector,omitempty"` + // Container defines the addon container. + Container core.Container `json:"container,omitempty"` + // Metadata details. + Metadata runtime.RawExtension `json:"metadata,omitempty"` } -// AddonStatus defines the observed state of an Addon. +// AddonStatus defines the observed state of the resource. type AddonStatus struct { // The most recent generation observed by the controller. // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty"` + // Resource conditions. + Conditions []meta.Condition `json:"conditions,omitempty"` } -// Addon defines an addon. // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:openapi-gen=true -// +kubebuilder:unservedversion +// +kubebuilder:storageversion // +kubebuilder:subresource:status type Addon struct { meta.TypeMeta `json:",inline"` meta.ObjectMeta `json:"metadata,omitempty"` // Spec defines the desired state of the resource. - Spec AddonSpec `json:"spec,omitempty"` + Spec AddonSpec `json:"spec"` // Status defines the observed state of the resource. Status AddonStatus `json:"status,omitempty"` } +// Reconciled returns true when the resource has been reconciled. +func (r *Addon) Reconciled() (b bool) { + return r.Generation == r.Status.ObservedGeneration +} + +// Ready returns true when resource has the ready condition. +func (r *Addon) Ready() (ready bool) { + for _, cnd := range r.Status.Conditions { + if cnd.Type == Ready.Type && cnd.Status == meta.ConditionTrue { + ready = true + break + } + } + return +} + +// Migrate specification as needed. +func (r *Addon) Migrate() (updated bool) { + if r.Spec.Image != nil { + if r.Spec.Container.Image == "" { + r.Spec.Container.Image = *r.Spec.Image + } + r.Spec.Image = nil + updated = true + } + if r.Spec.Resources != nil { + if len(r.Spec.Container.Resources.Limits) == 0 { + r.Spec.Container.Resources.Limits = (*r.Spec.Resources).Limits + } + if len(r.Spec.Container.Resources.Requests) == 0 { + r.Spec.Container.Resources.Requests = (*r.Spec.Resources).Requests + } + r.Spec.Resources = nil + updated = true + } + if r.Spec.ImagePullPolicy != nil { + if r.Spec.Container.ImagePullPolicy == "" { + r.Spec.Container.ImagePullPolicy = *r.Spec.ImagePullPolicy + } + r.Spec.ImagePullPolicy = nil + updated = true + } + if r.Spec.Container.Name == "" { + r.Spec.Container.Name = "addon" + updated = true + } + return +} + // AddonList is a list of Addon. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type AddonList struct { diff --git a/k8s/api/tackle/v1alpha1/extension.go b/k8s/api/tackle/v1alpha1/extension.go index 03a3fb367..c6826b586 100644 --- a/k8s/api/tackle/v1alpha1/extension.go +++ b/k8s/api/tackle/v1alpha1/extension.go @@ -17,18 +17,43 @@ limitations under the License. package v1alpha1 import ( + core "k8s.io/api/core/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) +// ExtensionSpec defines the desired state of the resource. +type ExtensionSpec struct { + // Addon (name) declares addon compatibility. + Addon string `json:"addon"` + // Container defines the extension container. + Container core.Container `json:"container"` + // Selector defines criteria to be included in the addon pod. + Selector string `json:"selector,omitempty"` + // Metadata details. + Metadata runtime.RawExtension `json:"metadata,omitempty"` +} + +// ExtensionStatus defines the observed state of the resource. +type ExtensionStatus struct { + // The most recent generation observed by the controller. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` +} + // Extension defines an addon extension. // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:openapi-gen=true -// +kubebuilder:unservedversion +// +kubebuilder:storageversion // +kubebuilder:subresource:status type Extension struct { meta.TypeMeta `json:",inline"` meta.ObjectMeta `json:"metadata,omitempty"` + // pec defines the desired state of the resource. + Spec ExtensionSpec `json:"spec"` + // Status defines the observed state of the resource. + Status ExtensionStatus `json:"status,omitempty"` } // ExtensionList is a list of Extension. diff --git a/k8s/api/tackle/v1alpha1/pkg.go b/k8s/api/tackle/v1alpha1/pkg.go new file mode 100644 index 000000000..9f7a3982b --- /dev/null +++ b/k8s/api/tackle/v1alpha1/pkg.go @@ -0,0 +1,23 @@ +package v1alpha1 + +import ( + meta "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + ValidationError = "ValidationError" + Validated = "Validated" +) + +var ( + Ready = meta.Condition{ + Type: "Ready", + Status: meta.ConditionTrue, + } + ImageNotDefined = meta.Condition{ + Type: ValidationError, + Status: meta.ConditionTrue, + Reason: "ImageNotDefined", + Message: "Either image or container.image must be specified.", + } +) diff --git a/k8s/api/tackle/v1alpha1/tackle.go b/k8s/api/tackle/v1alpha1/tackle.go index b237e1475..ea3e1666f 100644 --- a/k8s/api/tackle/v1alpha1/tackle.go +++ b/k8s/api/tackle/v1alpha1/tackle.go @@ -25,6 +25,7 @@ import ( // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:openapi-gen=true +// +kubebuilder:storageversion // +kubebuilder:subresource:status type Tackle struct { meta.TypeMeta `json:",inline"` diff --git a/k8s/api/tackle/v1alpha1/task.go b/k8s/api/tackle/v1alpha1/task.go index 4751b639f..fd92b9fe7 100644 --- a/k8s/api/tackle/v1alpha1/task.go +++ b/k8s/api/tackle/v1alpha1/task.go @@ -17,18 +17,64 @@ limitations under the License. package v1alpha1 import ( + "encoding/json" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) +// TaskSpec defines the desired state the resource. +type TaskSpec struct { + // Priority defines the task priority (0-n). + Priority int `json:"priority,omitempty"` + // Dependencies defines a list of task names on which this task depends. + Dependencies []string `json:"dependencies,omitempty"` + // Data object passed to the addon. + Data runtime.RawExtension `json:"data,omitempty"` +} + +// TaskStatus defines the observed state the resource. +type TaskStatus struct { + // The most recent generation observed by the controller. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` +} + // Task defines a hub task. // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:openapi-gen=true -// +kubebuilder:unservedversion +// +kubebuilder:storageversion // +kubebuilder:subresource:status type Task struct { meta.TypeMeta `json:",inline"` meta.ObjectMeta `json:"metadata,omitempty"` + // Spec defines the desired state the resource. + Spec TaskSpec `json:"spec,omitempty"` + // Status defines the observed state the resource. + Status TaskStatus `json:"status,omitempty"` +} + +// HasDep return true if the task has the dependency. +func (r *Task) HasDep(name string) (found bool) { + for i := range r.Spec.Dependencies { + n := r.Spec.Dependencies[i] + if n == name { + found = true + break + } + } + return +} + +// Data returns the task Data as map[string]any. +func (r *Task) Data() (mp map[string]any) { + b := r.Spec.Data.Raw + if b == nil { + return + } + _ = json.Unmarshal(b, &mp) + return } // TaskList is a list of Task. diff --git a/k8s/api/tackle/v1alpha1/zz_generated.deepcopy.go b/k8s/api/tackle/v1alpha1/zz_generated.deepcopy.go index ec96a5bfa..abc30ac83 100644 --- a/k8s/api/tackle/v1alpha1/zz_generated.deepcopy.go +++ b/k8s/api/tackle/v1alpha1/zz_generated.deepcopy.go @@ -22,6 +22,8 @@ limitations under the License. package v1alpha1 import ( + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -31,7 +33,7 @@ func (in *Addon) DeepCopyInto(out *Addon) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Addon. @@ -87,7 +89,23 @@ func (in *AddonList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AddonSpec) DeepCopyInto(out *AddonSpec) { *out = *in - in.Resources.DeepCopyInto(&out.Resources) + if in.Image != nil { + in, out := &in.Image, &out.Image + *out = new(string) + **out = **in + } + if in.ImagePullPolicy != nil { + in, out := &in.ImagePullPolicy, &out.ImagePullPolicy + *out = new(v1.PullPolicy) + **out = **in + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new(v1.ResourceRequirements) + (*in).DeepCopyInto(*out) + } + in.Container.DeepCopyInto(&out.Container) + in.Metadata.DeepCopyInto(&out.Metadata) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonSpec. @@ -103,6 +121,13 @@ func (in *AddonSpec) DeepCopy() *AddonSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AddonStatus) DeepCopyInto(out *AddonStatus) { *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonStatus. @@ -120,6 +145,8 @@ func (in *Extension) DeepCopyInto(out *Extension) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Extension. @@ -172,6 +199,38 @@ func (in *ExtensionList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtensionSpec) DeepCopyInto(out *ExtensionSpec) { + *out = *in + in.Container.DeepCopyInto(&out.Container) + in.Metadata.DeepCopyInto(&out.Metadata) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionSpec. +func (in *ExtensionSpec) DeepCopy() *ExtensionSpec { + if in == nil { + return nil + } + out := new(ExtensionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtensionStatus) DeepCopyInto(out *ExtensionStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionStatus. +func (in *ExtensionStatus) DeepCopy() *ExtensionStatus { + if in == nil { + return nil + } + out := new(ExtensionStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Tackle) DeepCopyInto(out *Tackle) { *out = *in @@ -236,6 +295,8 @@ func (in *Task) DeepCopyInto(out *Task) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Task. @@ -287,3 +348,39 @@ func (in *TaskList) DeepCopyObject() runtime.Object { } return nil } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TaskSpec) DeepCopyInto(out *TaskSpec) { + *out = *in + if in.Dependencies != nil { + in, out := &in.Dependencies, &out.Dependencies + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Data.DeepCopyInto(&out.Data) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskSpec. +func (in *TaskSpec) DeepCopy() *TaskSpec { + if in == nil { + return nil + } + out := new(TaskSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TaskStatus) DeepCopyInto(out *TaskStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskStatus. +func (in *TaskStatus) DeepCopy() *TaskStatus { + if in == nil { + return nil + } + out := new(TaskStatus) + in.DeepCopyInto(out) + return out +} diff --git a/k8s/api/tackle/v1alpha2/addon.go b/k8s/api/tackle/v1alpha2/addon.go deleted file mode 100644 index e7689f221..000000000 --- a/k8s/api/tackle/v1alpha2/addon.go +++ /dev/null @@ -1,78 +0,0 @@ -/* -Copyright 2019 Red Hat Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha2 - -import ( - core "k8s.io/api/core/v1" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -// AddonSpec defines the desired state of the resource. -type AddonSpec struct { - // Deprecated: Addon is deprecated. - // +kubebuilder:validation:Optional - Image *string `json:"image,omitempty"` - // Deprecated: ImagePullPolicy is deprecated. - // +kubebuilder:validation:Optional - ImagePullPolicy *core.PullPolicy `json:"imagePullPolicy,omitempty"` - // Deprecated: Resources is deprecated. - // +kubebuilder:validation:Optional - Resources *core.ResourceRequirements `json:"resources,omitempty"` - // - // Task declares task (kind) compatibility. - Task string `json:"task,omitempty"` - // Selector defines criteria to be selected for a task. - Selector string `json:"selector,omitempty"` - // Container defines the addon container. - Container core.Container `json:"container"` - // Metadata details. - Metadata runtime.RawExtension `json:"metadata,omitempty"` -} - -// AddonStatus defines the observed state of the resource. -type AddonStatus struct { - // The most recent generation observed by the controller. - // +optional - ObservedGeneration int64 `json:"observedGeneration,omitempty"` -} - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +k8s:openapi-gen=true -// +kubebuilder:storageversion -// +kubebuilder:subresource:status -type Addon struct { - meta.TypeMeta `json:",inline"` - meta.ObjectMeta `json:"metadata,omitempty"` - // Spec defines the desired state of the resource. - Spec AddonSpec `json:"spec"` - // Status defines the observed state of the resource. - Status AddonStatus `json:"status,omitempty"` -} - -// AddonList is a list of Addon. -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type AddonList struct { - meta.TypeMeta `json:",inline"` - meta.ListMeta `json:"metadata,omitempty"` - Items []Addon `json:"items"` -} - -func init() { - SchemeBuilder.Register(&Addon{}, &AddonList{}) -} diff --git a/k8s/api/tackle/v1alpha2/extension.go b/k8s/api/tackle/v1alpha2/extension.go deleted file mode 100644 index c2857fdc8..000000000 --- a/k8s/api/tackle/v1alpha2/extension.go +++ /dev/null @@ -1,69 +0,0 @@ -/* -Copyright 2019 Red Hat Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha2 - -import ( - core "k8s.io/api/core/v1" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -// ExtensionSpec defines the desired state of the resource. -type ExtensionSpec struct { - // Addon (name) declares addon compatibility. - Addon string `json:"addon"` - // Container defines the extension container. - Container core.Container `json:"container"` - // Selector defines criteria to be included in the addon pod. - Selector string `json:"selector,omitempty"` - // Metadata details. - Metadata runtime.RawExtension `json:"metadata,omitempty"` -} - -// ExtensionStatus defines the observed state of the resource. -type ExtensionStatus struct { - // The most recent generation observed by the controller. - // +optional - ObservedGeneration int64 `json:"observedGeneration,omitempty"` -} - -// Extension defines an addon extension. -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +k8s:openapi-gen=true -// +kubebuilder:storageversion -// +kubebuilder:subresource:status -type Extension struct { - meta.TypeMeta `json:",inline"` - meta.ObjectMeta `json:"metadata,omitempty"` - // pec defines the desired state of the resource. - Spec ExtensionSpec `json:"spec"` - // Status defines the observed state of the resource. - Status ExtensionStatus `json:"status,omitempty"` -} - -// ExtensionList is a list of Extension. -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type ExtensionList struct { - meta.TypeMeta `json:",inline"` - meta.ListMeta `json:"metadata,omitempty"` - Items []Extension `json:"items"` -} - -func init() { - SchemeBuilder.Register(&Extension{}, &ExtensionList{}) -} diff --git a/k8s/api/tackle/v1alpha2/register.go b/k8s/api/tackle/v1alpha2/register.go deleted file mode 100644 index 4d8dd0a30..000000000 --- a/k8s/api/tackle/v1alpha2/register.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2019 Red Hat Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package v1alpha1 contains API Schema definitions for the migration v1alpha1 API group. -// +k8s:openapi-gen=true -// +k8s:deepcopy-gen=package,register -// +k8s:conversion-gen=github.com/konveyor/tackle2-controller/pkg/apis/migration -// +k8s:defaulter-gen=TypeMeta -// +groupName=tackle.konveyor.io -package v1alpha2 - -import ( - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/scheme" -) - -var SchemeGroupVersion = schema.GroupVersion{ - Group: "tackle.konveyor.io", - Version: "v1alpha2", -} - -var SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} diff --git a/k8s/api/tackle/v1alpha2/tackle.go b/k8s/api/tackle/v1alpha2/tackle.go deleted file mode 100644 index 92cdcf12b..000000000 --- a/k8s/api/tackle/v1alpha2/tackle.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2019 Red Hat Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha2 - -import ( - "github.com/konveyor/tackle2-hub/k8s/api/tackle/v1alpha1" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Tackle defines a tackle application. -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +k8s:openapi-gen=true -// +kubebuilder:storageversion -// +kubebuilder:subresource:status -type Tackle v1alpha1.Tackle - -// TackleList is a list of Tackle. -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type TackleList struct { - meta.TypeMeta `json:",inline"` - meta.ListMeta `json:"metadata,omitempty"` - Items []Tackle `json:"items"` -} - -func init() { - SchemeBuilder.Register(&TackleList{}, &Tackle{}) -} diff --git a/k8s/api/tackle/v1alpha2/task.go b/k8s/api/tackle/v1alpha2/task.go deleted file mode 100644 index 2e5180dcf..000000000 --- a/k8s/api/tackle/v1alpha2/task.go +++ /dev/null @@ -1,90 +0,0 @@ -/* -Copyright 2019 Red Hat Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha2 - -import ( - "encoding/json" - - meta "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -// TaskSpec defines the desired state the resource. -type TaskSpec struct { - // Priority defines the task priority (0-n). - Priority int `json:"priority,omitempty"` - // Dependencies defines a list of task names on which this task depends. - Dependencies []string `json:"dependencies,omitempty"` - // Data object passed to the addon. - Data runtime.RawExtension `json:"data,omitempty"` -} - -// TaskStatus defines the observed state the resource. -type TaskStatus struct { - // The most recent generation observed by the controller. - // +optional - ObservedGeneration int64 `json:"observedGeneration,omitempty"` -} - -// Task defines a hub task. -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +k8s:openapi-gen=true -// +kubebuilder:storageversion -// +kubebuilder:subresource:status -type Task struct { - meta.TypeMeta `json:",inline"` - meta.ObjectMeta `json:"metadata,omitempty"` - // Spec defines the desired state the resource. - Spec TaskSpec `json:"spec,omitempty"` - // Status defines the observed state the resource. - Status TaskStatus `json:"status,omitempty"` -} - -// HasDep return true if the task has the dependency. -func (r *Task) HasDep(name string) (found bool) { - for i := range r.Spec.Dependencies { - n := r.Spec.Dependencies[i] - if n == name { - found = true - break - } - } - return -} - -// Data returns the task Data as map[string]any. -func (r *Task) Data() (mp map[string]any) { - b := r.Spec.Data.Raw - if b == nil { - return - } - _ = json.Unmarshal(b, &mp) - return -} - -// TaskList is a list of Task. -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type TaskList struct { - meta.TypeMeta `json:",inline"` - meta.ListMeta `json:"metadata,omitempty"` - Items []Task `json:"items"` -} - -func init() { - SchemeBuilder.Register(&Task{}, &TaskList{}) -} diff --git a/k8s/api/tackle/v1alpha2/zz_generated.deepcopy.go b/k8s/api/tackle/v1alpha2/zz_generated.deepcopy.go deleted file mode 100644 index 16db1de34..000000000 --- a/k8s/api/tackle/v1alpha2/zz_generated.deepcopy.go +++ /dev/null @@ -1,378 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 2019 Red Hat Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by controller-gen. DO NOT EDIT. - -package v1alpha2 - -import ( - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Addon) DeepCopyInto(out *Addon) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Addon. -func (in *Addon) DeepCopy() *Addon { - if in == nil { - return nil - } - out := new(Addon) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Addon) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AddonList) DeepCopyInto(out *AddonList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Addon, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonList. -func (in *AddonList) DeepCopy() *AddonList { - if in == nil { - return nil - } - out := new(AddonList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *AddonList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AddonSpec) DeepCopyInto(out *AddonSpec) { - *out = *in - if in.Image != nil { - in, out := &in.Image, &out.Image - *out = new(string) - **out = **in - } - if in.ImagePullPolicy != nil { - in, out := &in.ImagePullPolicy, &out.ImagePullPolicy - *out = new(v1.PullPolicy) - **out = **in - } - if in.Resources != nil { - in, out := &in.Resources, &out.Resources - *out = new(v1.ResourceRequirements) - (*in).DeepCopyInto(*out) - } - in.Container.DeepCopyInto(&out.Container) - in.Metadata.DeepCopyInto(&out.Metadata) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonSpec. -func (in *AddonSpec) DeepCopy() *AddonSpec { - if in == nil { - return nil - } - out := new(AddonSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AddonStatus) DeepCopyInto(out *AddonStatus) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AddonStatus. -func (in *AddonStatus) DeepCopy() *AddonStatus { - if in == nil { - return nil - } - out := new(AddonStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Extension) DeepCopyInto(out *Extension) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Extension. -func (in *Extension) DeepCopy() *Extension { - if in == nil { - return nil - } - out := new(Extension) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Extension) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExtensionList) DeepCopyInto(out *ExtensionList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Extension, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionList. -func (in *ExtensionList) DeepCopy() *ExtensionList { - if in == nil { - return nil - } - out := new(ExtensionList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ExtensionList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExtensionSpec) DeepCopyInto(out *ExtensionSpec) { - *out = *in - in.Container.DeepCopyInto(&out.Container) - in.Metadata.DeepCopyInto(&out.Metadata) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionSpec. -func (in *ExtensionSpec) DeepCopy() *ExtensionSpec { - if in == nil { - return nil - } - out := new(ExtensionSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExtensionStatus) DeepCopyInto(out *ExtensionStatus) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtensionStatus. -func (in *ExtensionStatus) DeepCopy() *ExtensionStatus { - if in == nil { - return nil - } - out := new(ExtensionStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Tackle) DeepCopyInto(out *Tackle) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Tackle. -func (in *Tackle) DeepCopy() *Tackle { - if in == nil { - return nil - } - out := new(Tackle) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Tackle) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TackleList) DeepCopyInto(out *TackleList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Tackle, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TackleList. -func (in *TackleList) DeepCopy() *TackleList { - if in == nil { - return nil - } - out := new(TackleList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TackleList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Task) DeepCopyInto(out *Task) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Task. -func (in *Task) DeepCopy() *Task { - if in == nil { - return nil - } - out := new(Task) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Task) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TaskList) DeepCopyInto(out *TaskList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Task, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskList. -func (in *TaskList) DeepCopy() *TaskList { - if in == nil { - return nil - } - out := new(TaskList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TaskList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TaskSpec) DeepCopyInto(out *TaskSpec) { - *out = *in - if in.Dependencies != nil { - in, out := &in.Dependencies, &out.Dependencies - *out = make([]string, len(*in)) - copy(*out, *in) - } - in.Data.DeepCopyInto(&out.Data) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskSpec. -func (in *TaskSpec) DeepCopy() *TaskSpec { - if in == nil { - return nil - } - out := new(TaskSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TaskStatus) DeepCopyInto(out *TaskStatus) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskStatus. -func (in *TaskStatus) DeepCopy() *TaskStatus { - if in == nil { - return nil - } - out := new(TaskStatus) - in.DeepCopyInto(out) - return out -} diff --git a/reaper/task.go b/reaper/task.go index b78f06a17..3f73432f1 100644 --- a/reaper/task.go +++ b/reaper/task.go @@ -113,7 +113,7 @@ func (r *TaskReaper) Run() { if m.Terminated != nil { mark = *m.Terminated } - if m.TTL.Succeeded > 0 { + if m.TTL.Failed > 0 { d := time.Duration(m.TTL.Failed) * Unit if time.Since(mark) > d { r.delete(m) diff --git a/reflect/db.go b/reflect/db.go new file mode 100644 index 000000000..6b100ef3c --- /dev/null +++ b/reflect/db.go @@ -0,0 +1,27 @@ +package reflect + +import ( + "gorm.io/gorm" +) + +// Select returns DB.Select() with validated fields. +func Select(in *gorm.DB, m any, fields ...string) (out *gorm.DB) { + fields, err := HasFields(m, fields...) + out = in.Select(fields) + if err != nil { + out.Statement.Error = err + return + } + return +} + +// Omit returns DB.Omit() with validated fields. +func Omit(in *gorm.DB, m any, fields ...string) (out *gorm.DB) { + fields, err := HasFields(m, fields...) + out = in.Omit(fields...) + if err != nil { + out.Statement.Error = err + return + } + return +} diff --git a/reflect/error.go b/reflect/error.go new file mode 100644 index 000000000..c4a369c25 --- /dev/null +++ b/reflect/error.go @@ -0,0 +1,25 @@ +package reflect + +import ( + "errors" + "fmt" +) + +// FieldNotValid report field not valid. +type FieldNotValid struct { + Kind string + Name string +} + +func (e *FieldNotValid) Error() string { + return fmt.Sprintf( + "(%s) '%s' not valid.", + e.Kind, + e.Name) +} + +func (e *FieldNotValid) Is(err error) (matched bool) { + var inst *FieldNotValid + matched = errors.As(err, &inst) + return +} diff --git a/api/reflect/fields.go b/reflect/fields.go similarity index 53% rename from api/reflect/fields.go rename to reflect/fields.go index 933c7e73d..a220de448 100644 --- a/api/reflect/fields.go +++ b/reflect/fields.go @@ -1,11 +1,16 @@ package reflect import ( + "fmt" "reflect" "time" + + liberr "github.com/jortel/go-utils/error" ) // Fields returns a map of fields. +// Used for: +// - db.Updates() func Fields(m any) (mp map[string]any) { var inspect func(r any) inspect = func(r any) { @@ -82,3 +87,71 @@ func NameOf(m any) (name string) { } return } + +// HasFields returns the validated field names. +// Used for: +// - db.Omit() +// - db.Select() +func HasFields(m any, in ...string) (out []string, err error) { + defer func() { + p := recover() + if p != nil { + if pe, cast := p.(error); cast { + err = pe + } else { + err = fmt.Errorf( + "(paniced) failed: %#v", + p) + } + } + }() + mp := make(map[string]any) + var inspect func(r any) + inspect = func(r any) { + mt := reflect.TypeOf(r) + mv := reflect.ValueOf(r) + if mt.Kind() == reflect.Ptr { + mt = mt.Elem() + mv = mv.Elem() + } + for i := 0; i < mt.NumField(); i++ { + ft := mt.Field(i) + fv := mv.Field(i) + if !ft.IsExported() { + continue + } + switch fv.Kind() { + case reflect.Ptr: + if ft.Anonymous { + inspect(fv.Interface()) + continue + } + inst := fv.Interface() + mp[ft.Name] = inst + case reflect.Struct: + if ft.Anonymous { + inspect(fv.Addr().Interface()) + continue + } + inst := fv.Interface() + mp[ft.Name] = inst + default: + mp[ft.Name] = fv.Interface() + } + } + } + inspect(m) + for _, name := range in { + _, found := mp[name] + if !found { + err = &FieldNotValid{ + Kind: NameOf(m), + Name: name, + } + err = liberr.Wrap(err) + return + } + } + out = in + return +} diff --git a/reflect/reflect_test.go b/reflect/reflect_test.go new file mode 100644 index 000000000..7b2431042 --- /dev/null +++ b/reflect/reflect_test.go @@ -0,0 +1,72 @@ +package reflect + +import ( + "errors" + "testing" + + "github.com/onsi/gomega" +) + +func TestHasField(t *testing.T) { + g := gomega.NewGomegaWithT(t) + type B struct { + Name string + Age string + } + type B2 struct { + Name2 string + Age2 string + } + type M struct { + B + *B2 + Ptr *B + Object B + Int int + IntPtr *int + List []string + } + + // Test expected. + _, err := HasFields( + &M{B2: &B2{}}, + "Name", + "Age", + "Name2", + "Age2", + "Ptr", + "Object", + "Int", + "IntPtr", + "List") + g.Expect(err).To(gomega.BeNil()) + + // Test anonymous NIL pointer. + _, err = HasFields( + &M{}, // PROBLEM HERE. + "Name", + "Age", + "Name2", + "Age2", + "Ptr", + "Object", + "Int", + "IntPtr", + "List") + g.Expect(err).ToNot(gomega.BeNil()) + + // Invalid field. + _, err = HasFields( + &M{B2: &B2{}}, + "Name", + "Age", + "Name2", + "Age2", + "Ptr", + "NOT-VALID", // PROBLEM HERE + "Object", + "Int", + "IntPtr", + "List") + g.Expect(errors.Is(err, &FieldNotValid{})).To(gomega.BeTrue()) +} diff --git a/task/error.go b/task/error.go index b6786c86b..5063de5d1 100644 --- a/task/error.go +++ b/task/error.go @@ -110,6 +110,51 @@ func (e *AddonNotSelected) Retry() (r bool) { return } +// NotReady report that a resource does not have the ready condition. +type NotReady struct { + Kind string + Name string + Reason string +} + +func (e *NotReady) Error() string { + return fmt.Sprintf( + "(%s) '%s' not ready: %s.", + e.Kind, + e.Name, + e.Reason) +} + +func (e *NotReady) Is(err error) (matched bool) { + var inst *NotReady + matched = errors.As(err, &inst) + return +} + +func (e *NotReady) Retry() (r bool) { + return +} + +// NotReconciled report as resource has not been reconciled. +type NotReconciled struct { + Kind string + Name string +} + +func (e *NotReconciled) Error() string { + return fmt.Sprintf("(%s) '%s' not reconciled.", e.Kind, e.Name) +} + +func (e *NotReconciled) Is(err error) (matched bool) { + var inst *NotReconciled + matched = errors.As(err, &inst) + return +} + +func (e *NotReconciled) Retry() (r bool) { + return +} + // ExtensionNotFound used to report an extension referenced // by a task but cannot be found. type ExtensionNotFound struct { diff --git a/task/manager.go b/task/manager.go index 6d2f8a4c6..914c23999 100644 --- a/task/manager.go +++ b/task/manager.go @@ -2,6 +2,7 @@ package task import ( "context" + "errors" "fmt" "io" "os" @@ -17,9 +18,10 @@ import ( "github.com/jortel/go-utils/logr" "github.com/konveyor/tackle2-hub/auth" k8s2 "github.com/konveyor/tackle2-hub/k8s" - crd "github.com/konveyor/tackle2-hub/k8s/api/tackle/v1alpha2" + crd "github.com/konveyor/tackle2-hub/k8s/api/tackle/v1alpha1" "github.com/konveyor/tackle2-hub/metrics" "github.com/konveyor/tackle2-hub/model" + "github.com/konveyor/tackle2-hub/reflect" "github.com/konveyor/tackle2-hub/settings" "gopkg.in/yaml.v2" "gorm.io/gorm" @@ -124,7 +126,11 @@ func (m *Manager) Run(ctx context.Context) { m.startReady() m.pause() } else { - Log.Error(err, "") + if errors.Is(err, &NotReconciled{}) { + Log.Info(err.Error()) + } else { + Log.Error(err, "") + } m.pause() } } @@ -182,7 +188,9 @@ func (m *Manager) Update(db *gorm.DB, requested *Task) (err error) { } switch found.State { case Created: - db = db.Select( + db = reflect.Select( + db, + requested, "UpdateUser", "Name", "Kind", @@ -209,7 +217,9 @@ func (m *Manager) Update(db *gorm.DB, requested *Task) (err error) { Pending, QuotaBlocked, Postponed: - db = db.Select( + db = reflect.Select( + db, + requested, "UpdateUser", "Name", "Locator", @@ -518,6 +528,13 @@ func (m *Manager) selectAddon(task *Task) (addon *crd.Addon, err error) { err = &AddonNotSelected{} return } + if !selected.Ready() { + err = &NotReady{ + Kind: "Addon", + Name: selected.Name, + } + return + } task.Addon = selected.Name task.Event(AddonSelected, selected) return @@ -1643,7 +1660,9 @@ func (r *Task) containsAny(str string, substr ...string) (matched bool) { // update manager controlled fields. func (r *Task) update(db *gorm.DB) (err error) { - db = db.Select( + db = reflect.Select( + db, + r.Task, "Addon", "Extensions", "State", @@ -1651,7 +1670,7 @@ func (r *Task) update(db *gorm.DB) (err error) { "Started", "Terminated", "Events", - "Error", + "Errors", "Retries", "Attached", "Pod") @@ -1903,6 +1922,13 @@ func (k *Cluster) getAddons() (err error) { for i := range list.Items { r := &list.Items[i] k.addons[r.Name] = r + if !r.Reconciled() { + err = &NotReconciled{ + Kind: r.Kind, + Name: r.Name, + } + return + } } return } diff --git a/task/rule.go b/task/rule.go index a42568db1..f87545d53 100644 --- a/task/rule.go +++ b/task/rule.go @@ -77,9 +77,6 @@ type RulePreempted struct { // Postpone based on a duration after the last preempted event. func (r *RulePreempted) Match(ready, _ *Task) (matched bool, reason string) { preemption := Settings.Hub.Task.Preemption - if !preemption.Enabled { - return - } mark := time.Now() event, found := ready.LastEvent(Preempted) if found { diff --git a/task/task_test.go b/task/task_test.go index 80fe29bb3..e088769e0 100644 --- a/task/task_test.go +++ b/task/task_test.go @@ -3,7 +3,7 @@ package task import ( "testing" - crd "github.com/konveyor/tackle2-hub/k8s/api/tackle/v1alpha2" + crd "github.com/konveyor/tackle2-hub/k8s/api/tackle/v1alpha1" "github.com/konveyor/tackle2-hub/model" "github.com/onsi/gomega" ) diff --git a/test/api/proxy/api_test.go b/test/api/proxy/api_test.go index b4f003ec8..09066d1d6 100644 --- a/test/api/proxy/api_test.go +++ b/test/api/proxy/api_test.go @@ -15,11 +15,11 @@ func TestProxyGetUpdate(t *testing.T) { } // Update. - update := orig + update := *orig update.Host = "127.0.0.1" update.Port = 8081 update.Enabled = true - err = Proxy.Update(update) + err = Proxy.Update(&update) if err != nil { t.Errorf(err.Error()) } diff --git a/trigger/pkg.go b/trigger/pkg.go index 20bd344e5..189a598ad 100644 --- a/trigger/pkg.go +++ b/trigger/pkg.go @@ -4,7 +4,7 @@ import ( "context" liberr "github.com/jortel/go-utils/error" - crd "github.com/konveyor/tackle2-hub/k8s/api/tackle/v1alpha2" + crd "github.com/konveyor/tackle2-hub/k8s/api/tackle/v1alpha1" "github.com/konveyor/tackle2-hub/settings" tasking "github.com/konveyor/tackle2-hub/task" "gorm.io/gorm"