diff --git a/DESIGN-HIERARCHICAL-LOADER.md b/DESIGN-HIERARCHICAL-LOADER.md index ca36bf9..c8ed47d 100644 --- a/DESIGN-HIERARCHICAL-LOADER.md +++ b/DESIGN-HIERARCHICAL-LOADER.md @@ -1,97 +1,34 @@ # hierarchical locader -1. load upstream refs in a hierarchical way - - crd refs - - child choreoinstance refs - -2. we load the apis from all of them from the beginning - - TODO: need to check conflict (different upstream refs use the same api object with a different reference) - - We load the libraries in the same way as the CRDs - - -3. running the instances - - we can run the instances in sequence as per priority field -> first focus - - we can run them simulteniously - - -current approach: - -- assumption is that the library and input data is rendered to input directory -- at server start: for all root instances and child instances - - load API -- at runner start: - - get api(s) - - load input data - - look at inventory/garbage collect (non choreo resources) - - load reconcilers and libs from the apis - - start the runner - -new approach: - -1. build the parent child relationships -2. load the apis + reconcilers + libraries in memory per child root - -> we do this at the runner since things can change along the path -3. snapshot should keep track of this context - - -root (priority 100) -data for child -+- childroot1 (priority 10) - +- child crd - +- child crd -+- childroot2 (priority 20) - +- child crd - +- child crd -+- child crd -+- child crd - -root (priority 100) -+- child crd -+- child crd +1. load the upstream ref in a hierarchical way + -> we create child choreo instances +we allow for : +- root -> childroot (crd/reconcilers/data) -> childcrd +- root -> childcrd (crd/lib) +- root -> data/reconcilers/libs/crds +kubenet ++- childroot1 topologyref priority 10 + +- childinstance (crd) + +- childinstance (crd) + +- reconcilers + +- data (templates) ++- reconcilers ++- in ++- refs -examples +2. init apis globally (we assume no api overlap right now or inconsistencies) -topology -- data for childroot1 (topology) -+- childroot1 topologyref - +- child crd - +- child crd - data templates +3. libraries are loaded and stored per rootinstance or childrootinstance -sequence -- load apis, libraries and reconcilers per choreoInstance hierarchy -- run childroot topologyref - - load data - - run reconcilers when data is available -- run root - - load data - - run topology child reconcilers since data is attached to the child reconcilers +4. reconcilers are loaded and stored per rootinstance or childrootinstance -kubenet -- data for childroot1 (topology) -+- childroot1 topologyref priority 10 - +- child crd - +- child crd - data templates -- data for kubenet (ipindex, network design) -+- childroot1 kubenet - +- child crd - +- child crd +5. data is loaded globally -sequence -- load apis, libraries and reconcilers per choreoInstance hierarchy -- run childroot topologyref - - load data - - run reconcilers when data is available -- run childroot kubenet - - load data - - don't reconcilers since no data is available -- run root - - load data - - run kubenet child reconcilers since data is attached to the kubenet child reconciler +6. run garbage collection +## Running -run data globallay +we run the instances in hierarchy diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..36bc42e --- /dev/null +++ b/TODO.md @@ -0,0 +1,33 @@ +# TODO + +## choreo + +- update choreo docs +- garbage collector: do we ignore the version -> currently we do a special trick in kuid to change the ownereference to v1alpha1 in the backend apis( as, vlan, genid, etc) +- fetch repo assumes main branch -> need to check the actual used branch +- snapshots are stored in memory. Is this the right approach ? +- snapshots add the detailed result. +- do we need to add reconcilers, libraries to the api or not ? + - right now we dont +- how to handle secrets? Vault ? +- k8s API versus grpc API ?? +- project scaffold + +## kubenet +- config generation +- config-diff integration + +## choreo controller + +- Variant controller +- Approval controller + +## kuid + +- rework the generic backend in the same way as IPAM -> allows for real claims +- proto generation +- reconcilers (non IPAM) + +## main + +- align storage backend kuid and chorio \ No newline at end of file diff --git a/apis/choreo/v1alpha1/reconciler_types.go b/apis/choreo/v1alpha1/reconciler_types.go index 79d7593..667a234 100644 --- a/apis/choreo/v1alpha1/reconciler_types.go +++ b/apis/choreo/v1alpha1/reconciler_types.go @@ -27,20 +27,22 @@ import ( type ReconcilerSpec struct { // ConditionType defines the condition used by this reconciler to reflect the status of its operation ConditionType *string `json:"conditionType,omitempty" protobuf:"bytes,1,opt,name=conditionType"` + // SpecUpdate indicates the reconciler is updating the spec with additional data + SpecUpdate *bool `json:"specUpdate,omitempty" protobuf:"bytes,2,opt,name=specUpdate"` // For defines the resource and business logic of the reconciler for this Reconciler. - For ReconcilerResource `json:"for" protobuf:"bytes,2,opt,name=for"` + For ReconcilerResource `json:"for" protobuf:"bytes,3,opt,name=for"` // Owns define the child resources this Reconciler generates as part of its business logic. // The For resource of this Reconciler owns the derived child resources. // The OwnerReferences are set by the internal reconciler logic. Changes to any of these resources // will trigger the Reconciler reconciler - Owns []*ReconcilerResource `json:"owns,omitempty" protobuf:"bytes,3,rep,name=owns"` + Owns []*ReconcilerResource `json:"owns,omitempty" protobuf:"bytes,4,rep,name=owns"` // Watches defines the resources on which the main reconciler can be retriggered. The pipeline/business logic // determines if the reconciler is to be retriggered. - Watches []*ReconcilerResource `json:"watches,omitempty" protobuf:"bytes,4,opt,name=watches"` + Watches []*ReconcilerResource `json:"watches,omitempty" protobuf:"bytes,5,opt,name=watches"` // Type defines the software technology this library contains - Type *SoftwardTechnologyType `json:"type,omitempty" protobuf:"bytes,5,opt,name=type"` + Type *SoftwardTechnologyType `json:"type,omitempty" protobuf:"bytes,6,opt,name=type"` // Code supporting the reconciler - Code map[string]string `json:"code,omitempty" protobuf:"bytes,6,rep,name=code"` + Code map[string]string `json:"code,omitempty" protobuf:"bytes,7,rep,name=code"` } type ReconcilerResource struct { diff --git a/apis/choreo/v1alpha1/zz_generated.deepcopy.go b/apis/choreo/v1alpha1/zz_generated.deepcopy.go index 62c4843..766a76e 100644 --- a/apis/choreo/v1alpha1/zz_generated.deepcopy.go +++ b/apis/choreo/v1alpha1/zz_generated.deepcopy.go @@ -585,6 +585,11 @@ func (in *ReconcilerSpec) DeepCopyInto(out *ReconcilerSpec) { *out = new(string) **out = **in } + if in.SpecUpdate != nil { + in, out := &in.SpecUpdate, &out.SpecUpdate + *out = new(bool) + **out = **in + } in.For.DeepCopyInto(&out.For) if in.Owns != nil { in, out := &in.Owns, &out.Owns diff --git a/artifacts/choreo.kform.dev_reconcilers.yaml b/artifacts/choreo.kform.dev_reconcilers.yaml index 481c50d..cb675c1 100644 --- a/artifacts/choreo.kform.dev_reconcilers.yaml +++ b/artifacts/choreo.kform.dev_reconcilers.yaml @@ -181,6 +181,10 @@ spec: - version type: object type: array + specUpdate: + description: SpecUpdate indicates the reconciler is updating the spec + with additional data + type: boolean type: description: Type defines the software technology this library contains type: string diff --git a/cmd/choreoctl/commands/runcmd/oncecmd/command.go b/cmd/choreoctl/commands/runcmd/oncecmd/command.go index fa9f97e..db02e51 100644 --- a/cmd/choreoctl/commands/runcmd/oncecmd/command.go +++ b/cmd/choreoctl/commands/runcmd/oncecmd/command.go @@ -20,7 +20,7 @@ import ( "context" "fmt" "sort" - "time" + "strings" "github.com/kform-dev/choreo/pkg/cli/genericclioptions" "github.com/kform-dev/choreo/pkg/client/go/runnerclient" @@ -115,107 +115,161 @@ func (r *OnceOptions) Run(ctx context.Context, args []string) error { } return nil } - /* - for _, result := range rsp.Results { - if len(result.Results) > 0 { - fmt.Println("execution success, time(msec)", result.Results[len(result.Results)-1].EventTime.AsTime().Sub(result.Results[0].EventTime.AsTime())) - } - switch r.ResultOutputFormat { - case "reconciler": - printReconcilerResultSummary(calculateReconcilerSummary(result)) - case "resource": - printReconcilerResourceResultSummary(calculateReconcilerResourceSummary(result)) - case "raw": - printResultRaw(result) - } + var p SummaryPrinter + switch r.ResultOutputFormat { + case "reconciler": + p = NewReconcilerPrinter() + case "resource": + p = NewResourcePrinter() + case "raw": + p = NewRawPrinter() + default: + return fmt.Errorf("invalid output format, got: %s", r.ResultOutputFormat) + } + // first calculate overall max idth + for _, result := range rsp.Results { + p.CollectData(result) + } + // print the result using the overall max width + for _, result := range rsp.Results { + fmt.Printf("Run %s summary\n", result.ReconcilerRunner) + if len(result.Results) > 0 { + fmt.Println("execution success, time(msec)", result.Results[len(result.Results)-1].EventTime.AsTime().Sub(result.Results[0].EventTime.AsTime())) } - */ - printResults(rsp, r.ResultOutputFormat) - + p.CollectData(result) + p.PrintSummary() + } + return nil } return nil } -func printResults(rsp *runnerpb.Once_Response, resultOutputFormat string) error { - // Pre-calculate maximum widths for all results - maxWidths := map[string]int{ - "EventTime": len("EventTime"), - "Reconciler": len("Reconciler"), - "Resource": len("Resource"), - "Operation": len("Operation"), - "Message": len("Message"), - } - - // Accumulate rows for each type of summary - reconcilerRows := [][]string{} - resourceRows := [][]string{} - rawRows := [][]string{} +type SummaryPrinter interface { + CollectData(result *runnerpb.Once_Result) + PrintSummary() +} - for _, result := range rsp.Results { - if len(result.Results) > 0 { - fmt.Println("execution success, time(msec)", result.Results[len(result.Results)-1].EventTime.AsTime().Sub(result.Results[0].EventTime.AsTime())) - } +type BasePrinter struct { + maxWidths []int + header []string + rows [][]string +} - switch resultOutputFormat { - case "reconciler": - reconcilerSummary := calculateReconcilerSummary(result) - for name, ops := range reconcilerSummary { - row := []string{name, fmt.Sprint(ops[runnerpb.Operation_START]), fmt.Sprint(ops[runnerpb.Operation_STOP]), fmt.Sprint(ops[runnerpb.Operation_REQUEUE]), fmt.Sprint(ops[runnerpb.Operation_ERROR])} - reconcilerRows = append(reconcilerRows, row) - updateMaxWidths(row, maxWidths) - } - case "resource": - resourceSummary := calculateReconcilerResourceSummary(result) - for res, ops := range resourceSummary { - row := []string{res.Reconcilername, res.ResourceNameString(), fmt.Sprint(ops[runnerpb.Operation_START]), fmt.Sprint(ops[runnerpb.Operation_STOP]), fmt.Sprint(ops[runnerpb.Operation_REQUEUE]), fmt.Sprint(ops[runnerpb.Operation_ERROR])} - resourceRows = append(resourceRows, row) - updateMaxWidths(row, maxWidths) - } - case "raw": - for _, res := range result.Results { - row := []string{res.EventTime.AsTime().Format(time.RFC3339), res.ReconcilerName, getReconcilerResource(res).ResourceNameString(), res.Operation.String(), res.Message} - rawRows = append(rawRows, row) - updateMaxWidths(row, maxWidths) +func (bp *BasePrinter) updateMaxWidths() { + for _, row := range bp.rows { + for i, value := range row { + if len(value) > bp.maxWidths[i] { + bp.maxWidths[i] = len(value) } } } +} - // Print summaries based on the format - if resultOutputFormat == "reconciler" { - printSummary("Reconciler Summary", maxWidths, reconcilerRows) - } else if resultOutputFormat == "resource" { - printSummary("Resource Summary", maxWidths, resourceRows) - } else if resultOutputFormat == "raw" { - printSummary("Raw Events Summary", maxWidths, rawRows) +func (bp *BasePrinter) PrintSummary() { + headerFormat := "" + for i := range bp.header { + headerFormat += fmt.Sprintf("%%-%ds ", bp.maxWidths[i]) } + headerFormat = strings.TrimSpace(headerFormat) + "\n" - return nil + fmt.Printf(headerFormat, interfaceSlice(bp.header)...) + for _, row := range bp.rows { + fmt.Printf(headerFormat, interfaceSlice(row)...) + } +} + +type ReconcilerPrinter struct { + BasePrinter +} + +func NewReconcilerPrinter() SummaryPrinter { + header := []string{"Reconciler", "Start", "Stop", "Requeue", "Error"} + maxWidths := make([]int, len(header)) + for i, head := range header { + maxWidths[i] = len(head) + } + return &ReconcilerPrinter{BasePrinter{header: header, maxWidths: maxWidths}} } -// Updates the maximum widths based on the content of each row -func updateMaxWidths(row []string, maxWidths map[string]int) { - keys := []string{"EventTime", "Reconciler", "Resource", "Operation", "Message"} - for i, value := range row { - if len(value) > maxWidths[keys[i]] { - maxWidths[keys[i]] = len(value) +func (rp *ReconcilerPrinter) CollectData(result *runnerpb.Once_Result) { + rp.rows = [][]string{} + reconcilerOperations := calculateReconcilerSummary(result) + for name, operations := range reconcilerOperations { + row := []string{ + name, + fmt.Sprint(operations[runnerpb.Operation_START]), + fmt.Sprint(operations[runnerpb.Operation_STOP]), + fmt.Sprint(operations[runnerpb.Operation_REQUEUE]), + fmt.Sprint(operations[runnerpb.Operation_ERROR]), + } + rp.rows = append(rp.rows, row) + } + rp.updateMaxWidths() + + sort.Slice(rp.rows, func(i, j int) bool { + return strings.ToLower(fmt.Sprintf("%q.%q", rp.rows[i][0], rp.rows[i][1])) < strings.ToLower(fmt.Sprintf("%q.%q", rp.rows[j][0], rp.rows[j][1])) + }) +} + +type ResourcePrinter struct { + BasePrinter +} + +func NewResourcePrinter() SummaryPrinter { + header := []string{"Reconciler", "Resource", "Start", "Stop", "Requeue", "Error"} + maxWidths := make([]int, len(header)) + for i, head := range header { + maxWidths[i] = len(head) + } + return &ResourcePrinter{BasePrinter{header: header, maxWidths: maxWidths}} +} + +func (rp *ResourcePrinter) CollectData(result *runnerpb.Once_Result) { + resourceOperations := calculateReconcilerResourceSummary(result) + for res, ops := range resourceOperations { + row := []string{ + res.Reconcilername, + res.ResourceNameString(), + fmt.Sprint(ops[runnerpb.Operation_START]), + fmt.Sprint(ops[runnerpb.Operation_STOP]), + fmt.Sprint(ops[runnerpb.Operation_REQUEUE]), + fmt.Sprint(ops[runnerpb.Operation_ERROR]), } + rp.rows = append(rp.rows, row) } + rp.updateMaxWidths() + sort.Slice(rp.rows, func(i, j int) bool { + return strings.ToLower(fmt.Sprintf("%q.%q", rp.rows[i][0], rp.rows[i][1])) < strings.ToLower(fmt.Sprintf("%q.%q", rp.rows[j][0], rp.rows[j][1])) + }) +} + +type RawPrinter struct { + BasePrinter } -// Prints summary with headers adjusted to maximum widths -func printSummary(title string, maxWidths map[string]int, rows [][]string) { - fmt.Println(title) - headers := []string{"EventTime", "Reconciler", "Resource", "Operation", "Message"} - headerFmt := "" - for _, h := range headers { - headerFmt += fmt.Sprintf("%%-%ds ", maxWidths[h]) +func NewRawPrinter() SummaryPrinter { + header := []string{"EventTime", "Reconciler", "Resource", "Operation", "Message"} + maxWidths := make([]int, len(header)) + for i, head := range header { + maxWidths[i] = len(head) } - headerFmt += "\n" + return &RawPrinter{BasePrinter{header: header, maxWidths: maxWidths}} +} - fmt.Printf(headerFmt, interfaceSlice(headers)...) - for _, row := range rows { - fmt.Printf(headerFmt, interfaceSlice(row)...) +func (rp *RawPrinter) CollectData(result *runnerpb.Once_Result) { + timeFormat := "2006-01-02 15:04:05.000000 UTC" + rp.rows = [][]string{} + for _, result := range result.Results { + row := []string{ + result.EventTime.AsTime().Format(timeFormat), + result.ReconcilerName, + getReconcilerResource(result).ResourceNameString(), + result.Operation.String(), + result.Message, + } + rp.rows = append(rp.rows, row) } + rp.updateMaxWidths() } type Operations map[runnerpb.Operation]int @@ -241,6 +295,15 @@ func calculateReconcilerSummary(rsp *runnerpb.Once_Result) map[string]Operations return reconcilerOperations } +func getReconcilers(rsp *runnerpb.Once_Result) []string { + reconcilers := []string{} + for _, result := range rsp.Results { + reconcilers = append(reconcilers, result.ReconcilerName) + } + sort.Strings(reconcilers) + return reconcilers +} + type ReconcilerResource struct { Reconcilername string Group string @@ -259,15 +322,6 @@ func getReconcilerResource(result *runnerpb.ReconcileResult) ReconcilerResource } } -func getReconcilers(rsp *runnerpb.Once_Result) []string { - reconcilers := []string{} - for _, result := range rsp.Results { - reconcilers = append(reconcilers, result.ReconcilerName) - } - sort.Strings(reconcilers) - return reconcilers -} - func (r ReconcilerResource) ResourceNameString() string { return fmt.Sprintf("%s.%s.%s.%s", r.Group, r.Kind, r.Namespace, r.Name) } @@ -293,98 +347,3 @@ func interfaceSlice(slice []string) []interface{} { } return result } - -/* - - - -func printResultRaw(rsp *runnerpb.Once_Result) { - timeFormat := "2006-01-02 15:04:05.000000 UTC" - rows := make([][]string, 0) - for _, result := range rsp.Results { - row := []string{ - result.EventTime.AsTime().Format(timeFormat), - result.ReconcilerName, - getReconcilerResource(result).ResourceNameString(), - result.Operation.String(), - result.Message, - } - rows = append(rows, row) - } - printSummary("Raw Summary", []string{"EventTime", "Reconciler", "Resource", "Operation", "Message"}, rows) -} - -// Example usage within your original functions -func printReconcilerResourceResultSummary(reconcilerResourceOperations map[ReconcilerResource]Operations) { - rows := make([][]string, 0) - for reconcilerResource, operations := range reconcilerResourceOperations { - row := []string{ - reconcilerResource.Reconcilername, - reconcilerResource.ResourceNameString(), - fmt.Sprint(operations[runnerpb.Operation_START]), - fmt.Sprint(operations[runnerpb.Operation_STOP]), - fmt.Sprint(operations[runnerpb.Operation_REQUEUE]), - fmt.Sprint(operations[runnerpb.Operation_ERROR]), - } - rows = append(rows, row) - } - sort.Slice(rows, func(i, j int) bool { - return strings.ToLower(fmt.Sprintf("%q.%q", rows[i][0], rows[i][1])) < strings.ToLower(fmt.Sprintf("%q.%q", rows[j][0], rows[j][1])) - }) - printSummary("Reconciler Resource Operations Summary", []string{"Reconciler", "resource", "Start", "Stop", "Requeue", "Error"}, rows) -} - -func printReconcilerResultSummary(resourceOperations map[string]Operations) { - rows := make([][]string, 0) - for name, operations := range resourceOperations { - row := []string{ - name, - fmt.Sprint(operations[runnerpb.Operation_START]), - fmt.Sprint(operations[runnerpb.Operation_STOP]), - fmt.Sprint(operations[runnerpb.Operation_REQUEUE]), - fmt.Sprint(operations[runnerpb.Operation_ERROR]), - } - rows = append(rows, row) - } - sort.Slice(rows, func(i, j int) bool { - return strings.ToLower(rows[i][0]) < strings.ToLower(rows[j][0]) - }) - printSummary("Reconciler Operations Summary", []string{"Reconciler", "Start", "Stop", "Requeue", "Error"}, rows) -} - -func printSummary(title string, headers []string, rows [][]string) { - maxLengths := make([]int, len(headers)) - for i, header := range headers { - maxLengths[i] = len(header) - } - - // Determine the maximum length of each column - for _, row := range rows { - for i, field := range row { - if len(field) > maxLengths[i] { - maxLengths[i] = len(field) - } - } - } - - // Prepare format string for headers and rows - var formatBuilder strings.Builder - for _, length := range maxLengths { - formatBuilder.WriteString(fmt.Sprintf("%%-%ds ", length)) - } - formatBuilder.WriteString("\n") - format := formatBuilder.String() - - // Print title - fmt.Println(title) - - // Print header - fmt.Printf(format, interfaceSlice(headers)...) - - // Print rows - for _, row := range rows { - fmt.Printf(format, interfaceSlice(row)...) - } -} - -*/ diff --git a/docs/rn/0.0.15.md b/docs/rn/0.0.15.md index c77e271..3c4a22a 100644 --- a/docs/rn/0.0.15.md +++ b/docs/rn/0.0.15.md @@ -28,5 +28,33 @@ When you run choreoctl run once the output format can be specified - all reconciler files are located in that directory -## hierarchical loading - +### hierarchical loading + +1. load the upstream ref in a hierarchical way + -> we create child choreo instances + +we allow for : +- root -> childroot (crd/reconcilers/data) -> childcrd +- root -> childcrd (crd/lib) +- root -> data/reconcilers/libs/crds + +example kubenet ++- childroot1 topologyref priority 10 + +- childinstance (crd) + +- childinstance (crd) + +- reconcilers + +- data (templates) ++- reconcilers ++- in ++- refs + +sequence +- init apis globally (we assume no api overlap right now or inconsistencies) +- libraries are loaded and stored per rootinstance or childrootinstance +- reconcilers are loaded and stored per rootinstance or childrootinstance +- data is loaded globally to the root instance api server +- run garbage collection + +2. Running + +we run the instances in hierarchy in the once mode \ No newline at end of file diff --git a/go.mod b/go.mod index 77994e2..b1882a7 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,6 @@ go 1.23.0 toolchain go1.23.2 -//replace github.com/kuidio/kuid => /Users/henderiw/code/kuidio/kuid - require ( github.com/adrg/xdg v0.5.2 github.com/flosch/pongo2/v6 v6.0.0 @@ -18,7 +16,7 @@ require ( github.com/henderiw/logger v0.0.0-20230911123436-8655829b1abe github.com/henderiw/store v0.0.2-0.20241030044529-f6baff74eab3 github.com/kform-dev/kform v0.0.16-0.20241029050934-f462791a4045 - github.com/kuidio/kuid v0.0.8-0.20241106103200-fd12843baffe + github.com/kuidio/kuid v0.0.8 github.com/pkg/errors v0.9.1 github.com/rivo/tview v0.0.0-20240818110301-fd649dbf1223 github.com/spf13/cobra v1.8.1 @@ -31,7 +29,7 @@ require ( google.golang.org/protobuf v1.35.1 k8s.io/api v0.31.1 k8s.io/apiextensions-apiserver v0.31.1 - k8s.io/apimachinery v0.31.2 + k8s.io/apimachinery v0.32.0-alpha.1 k8s.io/apiserver v0.31.1 k8s.io/client-go v0.31.1 k8s.io/kube-openapi v0.0.0-20240827152857-f7e401e7b4c2 diff --git a/go.sum b/go.sum index 3248e7a..26d1964 100644 --- a/go.sum +++ b/go.sum @@ -231,8 +231,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kuidio/kuid v0.0.8-0.20241106103200-fd12843baffe h1:tj16Ivwjpx1DEILv5bH2ImDji62CKyLrSABL7fMUCTM= -github.com/kuidio/kuid v0.0.8-0.20241106103200-fd12843baffe/go.mod h1:Q0Q0iKVWX91IpvthbOjlRCIuvG2CfgKjcFpKKptp1NU= +github.com/kuidio/kuid v0.0.8 h1:o9pwvypn6dot0pZ8eMM4cEXrbsKByQE9AM6c8v5DUVM= +github.com/kuidio/kuid v0.0.8/go.mod h1:QHi8VZD6A112hhKSFunAhisFkfCSqQNtGG+BWXg4An8= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= @@ -575,8 +575,8 @@ k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/N6E40= k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ= -k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= -k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apimachinery v0.32.0-alpha.1 h1:tDR19SzOmCOKVWtNhFbUtz1Axrt/1JJu7MRFiaEEhF4= +k8s.io/apimachinery v0.32.0-alpha.1/go.mod h1:5rKPDwwN9qm//xASFCZ83nyYEanHxxhc7pZ8AC4lukY= k8s.io/apiserver v0.31.1 h1:Sars5ejQDCRBY5f7R3QFHdqN3s61nhkpaX8/k1iEw1c= k8s.io/apiserver v0.31.1/go.mod h1:lzDhpeToamVZJmmFlaLwdYZwd7zB+WYRYIboqA1kGxM= k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= diff --git a/pkg/controller/reconciler/starlark/reconciler.go b/pkg/controller/reconciler/starlark/reconciler.go index 05f0d7c..31deaf7 100644 --- a/pkg/controller/reconciler/starlark/reconciler.go +++ b/pkg/controller/reconciler/starlark/reconciler.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "reflect" + "strings" "time" "github.com/henderiw/iputil" @@ -47,6 +48,7 @@ func NewReconcilerFn(client resourceclient.Client, reconcileConfig *choreov1alph name: reconcileConfig.Name, client: client, conditionType: reconcileConfig.Spec.ConditionType, + specUpdate: reconcileConfig.Spec.SpecUpdate, forgvk: reconcileConfig.GetForGVK(), owns: reconcileConfig.GetOwnsGVKs(), branch: branch, @@ -100,6 +102,7 @@ type reconciler struct { startlarkReconciler starlark.StringDict client resourceclient.Client conditionType *string + specUpdate *bool forgvk schema.GroupVersionKind owns sets.Set[schema.GroupVersionKind] branch string @@ -130,6 +133,26 @@ func (r *reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco // reinitialize the resource on each reconcile r.resources = resources.New(r.name, r.client, u, r.owns, r.branch) if u.GetDeletionTimestamp() != nil { + if r.specUpdate != nil && *r.specUpdate { + // call the python code; it will call various hooks we build + // returns an error message + obj, err := util.UnstructuredToStarlarkValue(u) + if err != nil { + return reconcile.Result{}, err + } + reconciler := r.startlarkReconciler["reconcile"] + thread := &starlark.Thread{Name: "main"} + v, err := starlark.Call(thread, reconciler, starlark.Tuple{starlark.Value(obj)}, nil) + if err != nil { + // this is a starlark execution runtime failure + return reconcile.Result{}, fmt.Errorf("starlark execution runtime failure: %s", err.Error()) + } + reconcileResult, err := r.handleResult(ctx, u, v) + if err != nil { + return reconcileResult, err + } + } + if err := r.resources.Delete(ctx); err != nil { return reconcile.Result{}, fmt.Errorf("starlark reconciler %s cannot delete child resource, err: %s", r.name, err.Error()) } @@ -190,12 +213,18 @@ func (r *reconciler) handleResult(ctx context.Context, oldu *unstructured.Unstru } // this is the happy path, we apply the child resources to the api if err := r.resources.Apply(ctx); err != nil { - log.Error("apply resources failed requeue", "reconciler", r.name, "err", err) - return reconcile.Result{ - Requeue: true, - RequeueAfter: requeue, - Message: fmt.Errorf("starlark reconciler %s apply resources failed, err: %s", r.name, err.Error()).Error(), - }, nil + // when we get not initialized we continue and requeue + // other errors are returned as fatal + if strings.Contains(err.Error(), "not initialized") { + log.Error("apply resources failed requeue", "reconciler", r.name, "err", err) + + return reconcile.Result{ + Requeue: true, + RequeueAfter: requeue, + Message: fmt.Errorf("starlark reconciler %s apply resources failed, err: %s", r.name, err.Error()).Error(), + }, nil + } + return reconcile.Result{}, fmt.Errorf("apply resources failed reconciler %s err: %v", r.name, err) } if uerr := r.updateForResourceStatus(ctx, newu, oldu, ""); uerr != nil { return reconcile.Result{}, fmt.Errorf("starlark reconciler %s cannot update resource: err: %v", r.name, uerr) diff --git a/pkg/repository/git/git.go b/pkg/repository/git/git.go index f793e88..63fd276 100644 --- a/pkg/repository/git/git.go +++ b/pkg/repository/git/git.go @@ -91,6 +91,8 @@ func Open2(ctx context.Context, path, url string) (*git.Repository, error) { // Open open the git repo and either clones or fecthes the remote info func Open(ctx context.Context, path string, url, refName string) (*git.Repository, *object.Commit, error) { + log := log.FromContext(ctx) + log.Info("opening repo", "url", url, "ref", refName, "path", path) cleanup := "" defer func() { if cleanup != "" { @@ -100,41 +102,36 @@ func Open(ctx context.Context, path string, url, refName string) (*git.Repositor repo, err := git.PlainOpen(path) if err == git.ErrRepositoryNotExists { + log.Info("Repository does not exist, cloning...", "url", url) // Repository does not exist, perform a clone - repo, err := cloneAll(ctx, path, url) + repo, err = cloneAll(ctx, path, url) if err != nil { + log.Error("failed to open repo", "url", url, "ref", refName, "path", path, "error", err) return nil, nil, err } - commit, err := ResolveToCommit(repo, refName) - if err != nil { - return nil, nil, err - } - cleanup = "" - return repo, commit, nil - } else if err != nil { - return nil, nil, fmt.Errorf("failed to open existing repo: %v", err) } // Repository exists, check if the specific commit is available // if so return; if not fetch latest updates and check if commit exists. commit, err := ResolveToCommit(repo, refName) if err != nil { + log.Info("failed to resolve commit, fetching repo", "url", url, "ref", refName, "path", path) // Commit is not present, fetch it if err := fetchAll(ctx, repo); err != nil { return nil, nil, err } - commit, err := ResolveToCommit(repo, refName) + commit, err = ResolveToCommit(repo, refName) if err != nil { + log.Error("failed to resolve commit after fetching repo", "url", url, "ref", refName, "path", path, "error", err) return nil, nil, err } - cleanup = "" - return repo, commit, nil } + // TODO need to see how we can reference the proper branch if err := resetToRemoteHead(ctx, repo, MainBranch.BranchInRemote()); err != nil { return nil, nil, err } - + log.Info("repo opened", "url", url, "ref", refName, "path", path) // Commit is already present return repo, commit, nil } @@ -385,3 +382,42 @@ func ResolveToCommit(repo *git.Repository, refName string) (*object.Commit, erro return tag.Commit() } + +/* +// findBranchesContainingCommit lists all branches that contain the given commit hash. +func findBranchesContainingCommit(repo *git.Repository, commitHash plumbing.Hash) ([]string, error) { + var branchesContainingCommit []string + + refs, err := repo.References() + if err != nil { + return nil, err + } + + err = refs.ForEach(func(ref *plumbing.Reference) error { + if ref.Name().IsBranch() { + // Check if the commit is in the history of the branch + commitIter, err := repo.Log(&git.LogOptions{From: ref.Hash()}) + if err != nil { + return err + } + defer commitIter.Close() + + // Iterate through the commits of the branch + return commitIter.ForEach(func(c *object.Commit) error { + if c.Hash == commitHash { + branchesContainingCommit = append(branchesContainingCommit, ref.Name().Short()) + return storer.ErrStop + } + return nil + }) + } + return nil + }) + + if err != nil { + return nil, err + } + + return branchesContainingCommit, nil +} +*/ diff --git a/pkg/server/choreo/crdloader/embedded/choreo.kform.dev_reconcilers.yaml b/pkg/server/choreo/crdloader/embedded/choreo.kform.dev_reconcilers.yaml index 481c50d..cb675c1 100644 --- a/pkg/server/choreo/crdloader/embedded/choreo.kform.dev_reconcilers.yaml +++ b/pkg/server/choreo/crdloader/embedded/choreo.kform.dev_reconcilers.yaml @@ -181,6 +181,10 @@ spec: - version type: object type: array + specUpdate: + description: SpecUpdate indicates the reconciler is updating the spec + with additional data + type: boolean type: description: Type defines the software technology this library contains type: string diff --git a/pkg/server/choreo/loader/loader_upstream.go b/pkg/server/choreo/loader/loader_upstream.go index 866d576..6715512 100644 --- a/pkg/server/choreo/loader/loader_upstream.go +++ b/pkg/server/choreo/loader/loader_upstream.go @@ -56,6 +56,7 @@ func (r *UpstreamLoader) Load(ctx context.Context) error { choreov1alpha1.SchemeGroupVersion.WithKind(choreov1alpha1.UpstreamRefKind), } abspath := filepath.Join(r.RepoPath, r.PathInRepo, *r.Cfg.ServerFlags.RefsPath) + fmt.Println("upstream loader", abspath) if !fsys.PathExists(abspath) { return nil @@ -73,6 +74,8 @@ func (r *UpstreamLoader) Load(ctx context.Context) error { errs = errors.Join(errs, fmt.Errorf("invalid upstreamref %s, err: %v", k.Name, err)) return } + fmt.Println("upstream loader upstreamRef", upstreamRef.Name) + // upload the upstream to the apiserver //r.NewChoreoRef.Insert(k.Name) obj, err := uobject.GetUnstructructered(upstreamRef) diff --git a/pkg/server/choreo/runner.go b/pkg/server/choreo/runner.go index 8e6539c..37b4770 100644 --- a/pkg/server/choreo/runner.go +++ b/pkg/server/choreo/runner.go @@ -113,7 +113,7 @@ func (r *run) Start(ctx context.Context, bctx *BranchCtx) (*runnerpb.Start_Respo return default: // use runctx since the ctx is from the cmd and it will be cancelled upon completion - r.runReconciler(runctx, bctx, reconcilers, libraries, false) // false -> run continuously, not once + r.runReconciler(runctx, "root", bctx, reconcilers, libraries, false) // false -> run continuously, not once } }() return &runnerpb.Start_Response{}, nil @@ -251,7 +251,7 @@ func (r *run) Load(ctx context.Context, branchCtx *BranchCtx) error { u.SetName(ref.Name) u.SetNamespace(ref.Namespace) - //fmt.Println("delete garbage", u.GetAPIVersion(), u.GetKind(), u.GetName()) + fmt.Println("delete garbage", u.GetAPIVersion(), u.GetKind(), u.GetName()) if err := r.choreo.GetClient().Delete(ctx, u, &resourceclient.DeleteOptions{ Branch: branchCtx.Branch, @@ -430,8 +430,10 @@ func (r *run) runReconcilers(ctx context.Context, branchCtx *BranchCtx, once boo rootLibraries = append(rootLibraries, libraries...) // only run the reconciler when there are reconcilers if len(childChoreoInstance.GetReconcilers()) > 0 { + upstreamRefName := childChoreoInstance.GetUpstreamRef().GetName() runrsp, err := r.runReconciler( ctx, + upstreamRefName, branchCtx, childChoreoInstance.GetReconcilers(), libraries, @@ -452,6 +454,7 @@ func (r *run) runReconcilers(ctx context.Context, branchCtx *BranchCtx, once boo if len(rootChoreoInstance.GetReconcilers()) > 0 { runrsp, err := r.runReconciler( ctx, + "root", branchCtx, rootChoreoInstance.GetReconcilers(), rootLibraries, @@ -471,7 +474,7 @@ func (r *run) runReconcilers(ctx context.Context, branchCtx *BranchCtx, once boo } -func (r *run) runReconciler(ctx context.Context, branchCtx *BranchCtx, reconcilers []*choreov1alpha1.Reconciler, libraries []*choreov1alpha1.Library, once bool) (*runnerpb.Once_Result, error) { +func (r *run) runReconciler(ctx context.Context, ref string, branchCtx *BranchCtx, reconcilers []*choreov1alpha1.Reconciler, libraries []*choreov1alpha1.Library, once bool) (*runnerpb.Once_Result, error) { reconcilerGVKs := sets.New[schema.GroupVersionKind]() for _, reconciler := range reconcilers { reconcilerGVKs.Insert(reconciler.GetGVKs().UnsortedList()...) @@ -522,6 +525,7 @@ func (r *run) runReconciler(ctx context.Context, branchCtx *BranchCtx, reconcile if !ok { return nil, nil } + result.ReconcilerRunner = ref return result, nil case <-ctx.Done(): diff --git a/pkg/util/inventory/inventory.go b/pkg/util/inventory/inventory.go index a764114..e37e139 100644 --- a/pkg/util/inventory/inventory.go +++ b/pkg/util/inventory/inventory.go @@ -156,6 +156,7 @@ func (r Inventory) CollectGarbage() sets.Set[corev1.ObjectReference] { g := newGarbageCollector() for _, node := range r { if node.Resource == nil { + fmt.Println("CollectGarbage", node.Resource) g.collect(node) } } diff --git a/pkg/util/object/delete.go b/pkg/util/object/delete.go index 99758e3..b9e8c47 100644 --- a/pkg/util/object/delete.go +++ b/pkg/util/object/delete.go @@ -18,6 +18,7 @@ package object import ( "context" + "reflect" "github.com/henderiw/logger/log" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -26,6 +27,10 @@ import ( func RemoveManagedFieldsFromUnstructured(ctx context.Context, obj runtime.Unstructured) { log := log.FromContext(ctx) + if obj == nil || reflect.ValueOf(obj).IsNil() { + return + } + // Access the unstructured content unstructuredContent := obj.UnstructuredContent()