diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 000000000..3071f5dfd --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,6 @@ +version: "2" + +run: + skip-dirs: + - agent-v2 + - bin diff --git a/Makefile b/Makefile index b92bf3951..efef1c987 100644 --- a/Makefile +++ b/Makefile @@ -306,18 +306,30 @@ clean: - rm -f -r bin ##################### "make lint" support start ########################## -GOLANGCI_LINT_VERSION := v1.64.8 +GOLANGCI_LINT_VERSION := v2.10.1 GOLANGCI_LINT := $(GOBIN)/golangci-lint -# Download golangci-lint locally if not already present +# Run every time: if installed version != required, remove binary so $(GOLANGCI_LINT) will re-install +.PHONY: check-golangci-lint-version +check-golangci-lint-version: + @if [ -f '$(GOLANGCI_LINT)' ]; then \ + installed=$$('$(GOLANGCI_LINT)' version 2>/dev/null | sed -n 's/.*version \([0-9.]*\).*/\1/p' | head -1); \ + required=$$(echo '$(GOLANGCI_LINT_VERSION)' | sed 's/^v//'); \ + if [ -n "$$installed" ] && [ "$$installed" != "$$required" ]; then \ + echo "🔍 Installed golangci-lint $$installed != required $(GOLANGCI_LINT_VERSION), re-installing..."; \ + rm -f '$(GOLANGCI_LINT)'; \ + fi; \ + fi + +# Download golangci-lint if not present $(GOLANGCI_LINT): @echo "🔍 Installing golangci-lint $(GOLANGCI_LINT_VERSION)..." - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \ + @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \ sh -s -- -b $(CURDIR)/bin $(GOLANGCI_LINT_VERSION) @echo "✅ 'golangci-lint' installed successfully." # Run linter -lint: $(GOLANGCI_LINT) +lint: check-golangci-lint-version $(GOLANGCI_LINT) @echo "🔍 Running golangci-lint..." @$(GOLANGCI_LINT) run --timeout=5m @echo "✅ Lint passed successfully!" @@ -332,10 +344,10 @@ $(MOQ): @go install github.com/matryer/moq@latest @echo "✅ 'moq' installed successfully." -# Code generation +# Code generation (exclude agent-v2 submodule) generate: $(MOQ) @echo "⚙️ Running go generate..." - @PATH="$(GOBIN):$$PATH" go generate -v $(shell go list ./...) + @PATH="$(GOBIN):$$PATH" go generate -v $(shell go list ./... | grep -v 'agent-v2' || true) @echo "⚙️ Running mockgen script..." @hack/mockgen.sh @$(MAKE) format diff --git a/api/v1alpha1/openapi.yaml b/api/v1alpha1/openapi.yaml index 5155d4fce..078d42bad 100644 --- a/api/v1alpha1/openapi.yaml +++ b/api/v1alpha1/openapi.yaml @@ -377,7 +377,7 @@ paths: get: tags: - assessment - description: List assessments + description: List assessments with filtering, sorting, and pagination operationId: listAssessments parameters: - name: sourceId @@ -387,13 +387,56 @@ paths: schema: type: string format: uuid + - name: source + in: query + description: Filter assessments by source type (agent, inventory, rvtools) + required: false + schema: + type: string + - name: name + in: query + description: Filter assessments by name pattern (case-insensitive partial match) + required: false + schema: + type: string + - name: sort + in: query + description: "Sort fields (format: 'field:direction', e.g., 'name:asc', 'created_at:desc'). Multiple sort fields can be specified." + required: false + schema: + type: array + items: + type: string + - name: page + in: query + description: "Page number (default: 1)" + required: false + schema: + type: integer + minimum: 1 + default: 1 + - name: pageSize + in: query + description: "Items per page (default: 20, max: 100)" + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 responses: "200": description: OK content: application/json: schema: - $ref: "#/components/schemas/AssessmentList" + $ref: "#/components/schemas/AssessmentListResponse" + "400": + description: Bad Request + content: + application/json: + schema: + $ref: "#/components/schemas/Error" "401": description: Unauthorized content: @@ -1573,6 +1616,28 @@ components: items: $ref: "#/components/schemas/Assessment" + AssessmentListResponse: + type: object + required: + - assessments + - total + - page + - pageCount + properties: + assessments: + type: array + items: + $ref: "#/components/schemas/Assessment" + page: + type: integer + description: Current page number + pageCount: + type: integer + description: Total number of pages + total: + type: integer + description: Total number of assessments matching the filter + ClusterRequirementsRequest: type: object description: Request payload for calculating cluster requirements diff --git a/api/v1alpha1/spec.gen.go b/api/v1alpha1/spec.gen.go index b79ffe325..1dd12a43f 100644 --- a/api/v1alpha1/spec.gen.go +++ b/api/v1alpha1/spec.gen.go @@ -18,92 +18,97 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "aD707q7v/hsAAP//UyeUVCyKAAA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/v1alpha1/types.gen.go b/api/v1alpha1/types.gen.go index 892cc6825..b9e6bf9f4 100644 --- a/api/v1alpha1/types.gen.go +++ b/api/v1alpha1/types.gen.go @@ -114,6 +114,20 @@ type AssessmentForm struct { // AssessmentList defines model for AssessmentList. type AssessmentList = []Assessment +// AssessmentListResponse defines model for AssessmentListResponse. +type AssessmentListResponse struct { + Assessments []Assessment `json:"assessments"` + + // Page Current page number + Page int `json:"page"` + + // PageCount Total number of pages + PageCount int `json:"pageCount"` + + // Total Total number of assessments matching the filter + Total int `json:"total"` +} + // AssessmentRvtoolsForm defines model for AssessmentRvtoolsForm. type AssessmentRvtoolsForm struct { // File File upload for assessment data @@ -569,6 +583,21 @@ type PresignedUrl struct { type ListAssessmentsParams struct { // SourceId Filter assessments by source ID SourceId *openapi_types.UUID `form:"sourceId,omitempty" json:"sourceId,omitempty"` + + // Source Filter assessments by source type (agent, inventory, rvtools) + Source *string `form:"source,omitempty" json:"source,omitempty"` + + // Name Filter assessments by name pattern (case-insensitive partial match) + Name *string `form:"name,omitempty" json:"name,omitempty"` + + // Sort Sort fields (format: 'field:direction', e.g., 'name:asc', 'created_at:desc'). Multiple sort fields can be specified. + Sort *[]string `form:"sort,omitempty" json:"sort,omitempty"` + + // Page Page number (default: 1) + Page *int `form:"page,omitempty" json:"page,omitempty"` + + // PageSize Items per page (default: 20, max: 100) + PageSize *int `form:"pageSize,omitempty" json:"pageSize,omitempty"` } // CreateAssessmentJSONRequestBody defines body for CreateAssessment for application/json ContentType. diff --git a/cmd/planner-api/migrate.go b/cmd/planner-api/migrate.go index a8a195ebc..7b10d3159 100644 --- a/cmd/planner-api/migrate.go +++ b/cmd/planner-api/migrate.go @@ -34,7 +34,7 @@ var migrateCmd = &cobra.Command{ } store := store.NewStore(db) - defer store.Close() + defer func() { _ = store.Close() }() zap.S().Info("Running database migrations") if err := migrations.MigrateStore(db, cfg.Service.MigrationFolder); err != nil { diff --git a/cmd/planner-api/run.go b/cmd/planner-api/run.go index a1a8e5cf7..c75bff30c 100644 --- a/cmd/planner-api/run.go +++ b/cmd/planner-api/run.go @@ -63,7 +63,7 @@ var runCmd = &cobra.Command{ } store := store.NewStore(db) - defer store.Close() + defer func() { _ = store.Close() }() if err := migrations.MigrateStore(db, cfg.Service.MigrationFolder); err != nil { zap.S().Fatalw("running initial migration", "error", err) diff --git a/go.mod b/go.mod index 02863cccf..110652d40 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/kubev2v/migration-planner -go 1.24.9 +go 1.25.0 require ( github.com/MicahParks/jwkset v0.11.0 diff --git a/hack/mockgen.sh b/hack/mockgen.sh index 961e5bc5b..34e2d5c5b 100755 --- a/hack/mockgen.sh +++ b/hack/mockgen.sh @@ -10,8 +10,8 @@ fi # ensure mockgen is installed go install -v go.uber.org/mock/mockgen@v0.4.0 -# remove existing mocks -find . -name 'mock_*.go' -type f -not -path './vendor/*' -delete +# remove existing mocks (exclude vendor and agent-v2 submodule) +find . -name 'mock_*.go' -type f -not -path './vendor/*' -not -path './agent-v2/*' -delete # file format '=' delimited: source=destination mock_list_file="hack/mock.list.txt" diff --git a/internal/api/client/client.gen.go b/internal/api/client/client.gen.go index b66c1c48c..fd6debfb7 100644 --- a/internal/api/client/client.gen.go +++ b/internal/api/client/client.gen.go @@ -514,6 +514,86 @@ func NewListAssessmentsRequest(server string, params *ListAssessmentsParams) (*h } + if params.Source != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "source", runtime.ParamLocationQuery, *params.Source); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Name != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "name", runtime.ParamLocationQuery, *params.Name); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Sort != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "sort", runtime.ParamLocationQuery, *params.Sort); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Page != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "page", runtime.ParamLocationQuery, *params.Page); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.PageSize != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "pageSize", runtime.ParamLocationQuery, *params.PageSize); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + queryURL.RawQuery = queryValues.Encode() } @@ -1321,7 +1401,8 @@ type ClientWithResponsesInterface interface { type ListAssessmentsResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *AssessmentList + JSON200 *AssessmentListResponse + JSON400 *Error JSON401 *Error JSON500 *Error } @@ -2070,12 +2151,19 @@ func ParseListAssessmentsResponse(rsp *http.Response) (*ListAssessmentsResponse, switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AssessmentList + var dest AssessmentListResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } response.JSON200 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest Error if err := json.Unmarshal(bodyBytes, &dest); err != nil { diff --git a/internal/api/server/server.gen.go b/internal/api/server/server.gen.go index 9d4387260..1e795a3f5 100644 --- a/internal/api/server/server.gen.go +++ b/internal/api/server/server.gen.go @@ -211,6 +211,46 @@ func (siw *ServerInterfaceWrapper) ListAssessments(w http.ResponseWriter, r *htt return } + // ------------- Optional query parameter "source" ------------- + + err = runtime.BindQueryParameter("form", true, false, "source", r.URL.Query(), ¶ms.Source) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "source", Err: err}) + return + } + + // ------------- Optional query parameter "name" ------------- + + err = runtime.BindQueryParameter("form", true, false, "name", r.URL.Query(), ¶ms.Name) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "name", Err: err}) + return + } + + // ------------- Optional query parameter "sort" ------------- + + err = runtime.BindQueryParameter("form", true, false, "sort", r.URL.Query(), ¶ms.Sort) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "sort", Err: err}) + return + } + + // ------------- Optional query parameter "page" ------------- + + err = runtime.BindQueryParameter("form", true, false, "page", r.URL.Query(), ¶ms.Page) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "page", Err: err}) + return + } + + // ------------- Optional query parameter "pageSize" ------------- + + err = runtime.BindQueryParameter("form", true, false, "pageSize", r.URL.Query(), ¶ms.PageSize) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "pageSize", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.ListAssessments(w, r, params) })) @@ -824,7 +864,7 @@ type ListAssessmentsResponseObject interface { VisitListAssessmentsResponse(w http.ResponseWriter) error } -type ListAssessments200JSONResponse AssessmentList +type ListAssessments200JSONResponse AssessmentListResponse func (response ListAssessments200JSONResponse) VisitListAssessmentsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") @@ -833,6 +873,15 @@ func (response ListAssessments200JSONResponse) VisitListAssessmentsResponse(w ht return json.NewEncoder(w).Encode(response) } +type ListAssessments400JSONResponse Error + +func (response ListAssessments400JSONResponse) VisitListAssessmentsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + type ListAssessments401JSONResponse Error func (response ListAssessments401JSONResponse) VisitListAssessmentsResponse(w http.ResponseWriter) error { diff --git a/internal/api_server/server.go b/internal/api_server/server.go index 6b3df88c4..c73af058b 100644 --- a/internal/api_server/server.go +++ b/internal/api_server/server.go @@ -80,7 +80,7 @@ func detectOldSchemaMiddleware(next http.Handler) http.Handler { } body, err := io.ReadAll(r.Body) - r.Body.Close() + _ = r.Body.Close() if err != nil { http.Error(w, "Failed to read request body", http.StatusInternalServerError) return diff --git a/internal/auth/agent_authenticator_test.go b/internal/auth/agent_authenticator_test.go index 18b3ba7ed..5c66ad2f1 100644 --- a/internal/auth/agent_authenticator_test.go +++ b/internal/auth/agent_authenticator_test.go @@ -40,7 +40,7 @@ var _ = Describe("agent authentication", Ordered, func() { }) AfterEach(func() { - s.Close() + _ = s.Close() }) Context("authenticator", func() { diff --git a/internal/cli/create_assessment.go b/internal/cli/create_assessment.go index c4fdc96a2..52fe676ca 100644 --- a/internal/cli/create_assessment.go +++ b/internal/cli/create_assessment.go @@ -36,7 +36,7 @@ func NewCmdCreateAssessment() *cobra.Command { if err := o.Complete(cmd, args); err != nil { return err } - if err := o.GlobalOptions.Validate(args); err != nil { + if err := o.Validate(args); err != nil { return err } return o.Run(cmd.Context(), args) @@ -68,7 +68,7 @@ func (o *CreateAssessmentOptions) Run(ctx context.Context, args []string) error if err != nil { return fmt.Errorf("opening file: %w", err) } - defer file.Close() + defer func() { _ = file.Close() }() body := &bytes.Buffer{} writer := multipart.NewWriter(body) diff --git a/internal/cli/deploy.go b/internal/cli/deploy.go index f439b9dd8..0983f3b9c 100644 --- a/internal/cli/deploy.go +++ b/internal/cli/deploy.go @@ -166,9 +166,7 @@ func (o *DeployOptions) Run(ctx context.Context, args []string) error { if err != nil { return fmt.Errorf("failed to connect to hypervisor: %s", err) } - defer func() { - conn.Close() - }() + defer func() { _, _ = conn.Close() }() // try to find the storage pool storagePool, err := conn.LookupStoragePoolByName(o.StoragePool) diff --git a/internal/cli/generate.go b/internal/cli/generate.go index ba4079b94..7796c2ac9 100644 --- a/internal/cli/generate.go +++ b/internal/cli/generate.go @@ -62,7 +62,7 @@ func (o *GenerateOptions) Validate(args []string) error { if err != nil { return fmt.Errorf("failed to open rhcos base image file: %s", err) } - rhcosFile.Close() + _ = rhcosFile.Close() } switch o.ImageType { @@ -164,7 +164,7 @@ func getLocalIP() (net.IP, error) { if err != nil { return nil, err } - defer conn.Close() + defer func() { _ = conn.Close() }() localAddr := conn.LocalAddr().(*net.UDPAddr) diff --git a/internal/cli/get.go b/internal/cli/get.go index 243509fc7..cf6a032c3 100644 --- a/internal/cli/get.go +++ b/internal/cli/get.go @@ -159,17 +159,17 @@ func printTable(response interface{}, kind string, id *uuid.UUID) error { default: return fmt.Errorf("unknown resource type %s", kind) } - w.Flush() + _ = w.Flush() return nil } func printSourcesTable(w *tabwriter.Writer, sources ...api.Source) { - fmt.Fprintln(w, "ID\tName\tAgent ID\tAgent Status\tHas inventory") + _, _ = fmt.Fprintln(w, "ID\tName\tAgent ID\tAgent Status\tHas inventory") for _, s := range sources { if s.Agent != nil { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%v\n", s.Id, s.Name, s.Agent.Id, s.Agent.Status, s.Inventory != nil) + _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%v\n", s.Id, s.Name, s.Agent.Id, s.Agent.Status, s.Inventory != nil) continue } - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%v\n", s.Id, s.Name, "", "", s.Inventory != nil) + _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%v\n", s.Id, s.Name, "", "", s.Inventory != nil) } } diff --git a/internal/handlers/v1alpha1/agent.go b/internal/handlers/v1alpha1/agent.go index 427807c53..6e7f4a2fb 100644 --- a/internal/handlers/v1alpha1/agent.go +++ b/internal/handlers/v1alpha1/agent.go @@ -7,7 +7,6 @@ import ( v1alpha1 "github.com/kubev2v/migration-planner/api/v1alpha1/agent" agentServer "github.com/kubev2v/migration-planner/internal/api/server/agent" - server "github.com/kubev2v/migration-planner/internal/api/server/agent" apiMappers "github.com/kubev2v/migration-planner/internal/handlers/v1alpha1/mappers" "github.com/kubev2v/migration-planner/internal/handlers/validator" "github.com/kubev2v/migration-planner/internal/service" @@ -58,7 +57,7 @@ func (h *AgentHandler) UpdateSourceInventory(ctx context.Context, request agentS response, err := apiMappers.SourceToApi(*updatedSource) if err != nil { - return server.UpdateSourceInventory500JSONResponse{Message: fmt.Sprintf("failed to map source to api: %v", err)}, nil + return agentServer.UpdateSourceInventory500JSONResponse{Message: fmt.Sprintf("failed to map source to api: %v", err)}, nil } return agentServer.UpdateSourceInventory200JSONResponse(response), nil diff --git a/internal/handlers/v1alpha1/agent_test.go b/internal/handlers/v1alpha1/agent_test.go index e95594c39..9368ffb4f 100644 --- a/internal/handlers/v1alpha1/agent_test.go +++ b/internal/handlers/v1alpha1/agent_test.go @@ -37,7 +37,7 @@ var _ = Describe("agent service", Ordered, func() { }) AfterAll(func() { - s.Close() + _ = s.Close() }) Context("Update agent status", func() { diff --git a/internal/handlers/v1alpha1/assessment.go b/internal/handlers/v1alpha1/assessment.go index d9501fe92..21d9715b6 100644 --- a/internal/handlers/v1alpha1/assessment.go +++ b/internal/handlers/v1alpha1/assessment.go @@ -3,6 +3,7 @@ package v1alpha1 import ( "context" "fmt" + "strings" "github.com/kubev2v/migration-planner/api/v1alpha1" "github.com/kubev2v/migration-planner/internal/api/server" @@ -14,6 +15,18 @@ import ( "github.com/kubev2v/migration-planner/pkg/requestid" ) +const ( + defaultPageSize = 20 + maxPageSize = 100 +) + +var validSortFields = map[string]bool{ + "name": true, + "created_at": true, + "updated_at": true, + "source_type": true, +} + // (GET /api/v1/assessments) func (h *ServiceHandler) ListAssessments(ctx context.Context, request server.ListAssessmentsRequestObject) (server.ListAssessmentsResponseObject, error) { logger := log.NewDebugLogger("assessment_handler"). @@ -24,6 +37,20 @@ func (h *ServiceHandler) ListAssessments(ctx context.Context, request server.Lis user := auth.MustHaveUser(ctx) logger.Step("extract_user").WithString("org_id", user.Organization).WithString("username", user.Username).Log() + // Parse pagination + page := 1 + if request.Params.Page != nil && *request.Params.Page > 0 { + page = *request.Params.Page + } + pageSize := defaultPageSize + if request.Params.PageSize != nil && *request.Params.PageSize > 0 { + pageSize = *request.Params.PageSize + if pageSize > maxPageSize { + pageSize = maxPageSize + } + } + + // Build service filter filter := service.NewAssessmentFilter(user.Username, user.Organization) // Extract sourceId from query parameter if provided @@ -33,20 +60,75 @@ func (h *ServiceHandler) ListAssessments(ctx context.Context, request server.Lis logger.Step("filter_by_source_id").WithString("source_id", sourceIdStr).Log() } - assessments, err := h.assessmentSrv.ListAssessments(ctx, filter) + // Extract source type filter if provided + if request.Params.Source != nil && *request.Params.Source != "" { + filter = filter.WithSource(*request.Params.Source) + logger.Step("filter_by_source").WithString("source", *request.Params.Source).Log() + } + + // Extract name filter if provided + if request.Params.Name != nil && *request.Params.Name != "" { + filter = filter.WithNameLike(*request.Params.Name) + logger.Step("filter_by_name").WithString("name", *request.Params.Name).Log() + } + + // Parse and validate sort params + if request.Params.Sort != nil { + sortFields := make([]service.SortField, 0, len(*request.Params.Sort)) + for _, s := range *request.Params.Sort { + parts := strings.SplitN(s, ":", 2) + if len(parts) != 2 { + return server.ListAssessments400JSONResponse{ + Message: "invalid sort format, expected 'field:direction' (e.g., 'name:asc')", + RequestId: requestid.FromContextPtr(ctx), + }, nil + } + field, direction := parts[0], parts[1] + if !validSortFields[field] { + return server.ListAssessments400JSONResponse{ + Message: fmt.Sprintf("invalid sort field: %s", field), + RequestId: requestid.FromContextPtr(ctx), + }, nil + } + if direction != "asc" && direction != "desc" { + return server.ListAssessments400JSONResponse{ + Message: fmt.Sprintf("invalid sort direction: %s, must be 'asc' or 'desc'", direction), + RequestId: requestid.FromContextPtr(ctx), + }, nil + } + sortFields = append(sortFields, service.SortField{Field: field, Desc: direction == "desc"}) + } + filter = filter.WithSort(sortFields) + } + + // Set pagination + filter = filter.WithLimit(pageSize).WithOffset((page - 1) * pageSize) + + assessments, total, err := h.assessmentSrv.ListAssessments(ctx, filter) if err != nil { logger.Error(err).Log() return server.ListAssessments500JSONResponse{Message: fmt.Sprintf("failed to list assessments: %v", err), RequestId: requestid.FromContextPtr(ctx)}, nil } - logger.Success().WithInt("count", len(assessments)).Log() + // Calculate page count + pageCount := int((total + int64(pageSize) - 1) / int64(pageSize)) + if pageCount == 0 { + pageCount = 1 + } + + logger.Success().WithInt("count", len(assessments)).WithInt("total", int(total)).Log() apiAssessments, err := mappers.AssessmentListToApi(assessments) if err != nil { return server.ListAssessments500JSONResponse{Message: fmt.Sprintf("failed to list assessments: %v", err), RequestId: requestid.FromContextPtr(ctx)}, nil } - return server.ListAssessments200JSONResponse(apiAssessments), nil + return server.ListAssessments200JSONResponse{ + Assessments: apiAssessments, + Page: page, + PageCount: pageCount, + Total: int(total), + }, nil } // (POST /api/v1/assessments) diff --git a/internal/handlers/v1alpha1/assessment_test.go b/internal/handlers/v1alpha1/assessment_test.go index a49921d3c..f4cf32038 100644 --- a/internal/handlers/v1alpha1/assessment_test.go +++ b/internal/handlers/v1alpha1/assessment_test.go @@ -60,7 +60,7 @@ var _ = Describe("assessment handler", Ordered, func() { }) AfterAll(func() { - s.Close() + _ = s.Close() }) Context("list assessments", func() { @@ -100,11 +100,13 @@ var _ = Describe("assessment handler", Ordered, func() { Expect(err).To(BeNil()) Expect(reflect.TypeOf(resp).String()).To(Equal(reflect.TypeOf(server.ListAssessments200JSONResponse{}).String())) - assessmentList := resp.(server.ListAssessments200JSONResponse) - Expect(assessmentList).To(HaveLen(2)) // Only admin user's assessments + assessmentListResponse := resp.(server.ListAssessments200JSONResponse) + Expect(assessmentListResponse.Assessments).To(HaveLen(2)) // Only admin user's assessments + Expect(assessmentListResponse.Total).To(Equal(2)) + Expect(assessmentListResponse.Page).To(Equal(1)) // Verify owner fields are included in API responses - for _, assessment := range assessmentList { + for _, assessment := range assessmentListResponse.Assessments { Expect(assessment.OwnerFirstName).ToNot(BeNil()) Expect(*assessment.OwnerFirstName).To(Equal("John")) Expect(assessment.OwnerLastName).ToNot(BeNil()) @@ -125,8 +127,10 @@ var _ = Describe("assessment handler", Ordered, func() { Expect(err).To(BeNil()) Expect(reflect.TypeOf(resp).String()).To(Equal(reflect.TypeOf(server.ListAssessments200JSONResponse{}).String())) - assessmentList := resp.(server.ListAssessments200JSONResponse) - Expect(assessmentList).To(HaveLen(0)) + assessmentListResponse := resp.(server.ListAssessments200JSONResponse) + Expect(assessmentListResponse.Assessments).To(HaveLen(0)) + Expect(assessmentListResponse.Total).To(Equal(0)) + Expect(assessmentListResponse.Page).To(Equal(1)) }) It("filters assessments by sourceId query parameter", func() { @@ -178,12 +182,13 @@ var _ = Describe("assessment handler", Ordered, func() { Expect(err).To(BeNil()) Expect(reflect.TypeOf(resp).String()).To(Equal(reflect.TypeOf(server.ListAssessments200JSONResponse{}).String())) - assessmentList := resp.(server.ListAssessments200JSONResponse) + assessmentListResponse := resp.(server.ListAssessments200JSONResponse) // Should only return the assessment with the matching sourceID - Expect(assessmentList).To(HaveLen(1)) - Expect(assessmentList[0].Id).To(Equal(assessmentID1)) - Expect(assessmentList[0].SourceId).ToNot(BeNil()) - Expect(*assessmentList[0].SourceId).To(Equal(sourceID)) + Expect(assessmentListResponse.Assessments).To(HaveLen(1)) + Expect(assessmentListResponse.Total).To(Equal(1)) + Expect(assessmentListResponse.Assessments[0].Id).To(Equal(assessmentID1)) + Expect(assessmentListResponse.Assessments[0].SourceId).ToNot(BeNil()) + Expect(*assessmentListResponse.Assessments[0].SourceId).To(Equal(sourceID)) }) It("returns empty list when filtering by non-existent sourceId", func() { @@ -224,9 +229,10 @@ var _ = Describe("assessment handler", Ordered, func() { Expect(err).To(BeNil()) Expect(reflect.TypeOf(resp).String()).To(Equal(reflect.TypeOf(server.ListAssessments200JSONResponse{}).String())) - assessmentList := resp.(server.ListAssessments200JSONResponse) + assessmentListResponse := resp.(server.ListAssessments200JSONResponse) // Should return empty list when filtering by non-existent sourceID - Expect(assessmentList).To(HaveLen(0)) + Expect(assessmentListResponse.Assessments).To(HaveLen(0)) + Expect(assessmentListResponse.Total).To(Equal(0)) }) AfterEach(func() { diff --git a/internal/handlers/v1alpha1/job.go b/internal/handlers/v1alpha1/job.go index 85aee1a24..6b66161ec 100644 --- a/internal/handlers/v1alpha1/job.go +++ b/internal/handlers/v1alpha1/job.go @@ -37,7 +37,7 @@ func (h *ServiceHandler) CreateRVToolsAssessment(ctx context.Context, request se // Helper to process a single part with deferred cleanup processPart := func(part *multipart.Part) error { - defer part.Close() + defer func() { _ = part.Close() }() switch part.FormName() { case "name": diff --git a/internal/handlers/v1alpha1/job_test.go b/internal/handlers/v1alpha1/job_test.go index e063a9f9d..622053cbb 100644 --- a/internal/handlers/v1alpha1/job_test.go +++ b/internal/handlers/v1alpha1/job_test.go @@ -52,7 +52,7 @@ var _ = Describe("job handler", Ordered, func() { if testServer != nil { testServer.Close() } - s.Close() + _ = s.Close() }) Context("GetJob", func() { diff --git a/internal/handlers/v1alpha1/mappers/outbound.go b/internal/handlers/v1alpha1/mappers/outbound.go index e7cc22da8..50981b5a3 100644 --- a/internal/handlers/v1alpha1/mappers/outbound.go +++ b/internal/handlers/v1alpha1/mappers/outbound.go @@ -5,7 +5,6 @@ import ( "fmt" "slices" - "github.com/kubev2v/migration-planner/api/v1alpha1" api "github.com/kubev2v/migration-planner/api/v1alpha1" "github.com/kubev2v/migration-planner/internal/service/mappers" "github.com/kubev2v/migration-planner/internal/store/model" @@ -74,7 +73,7 @@ func SourceToApi(s model.Source) (api.Source, error) { v := util.GetInventoryVersion(s.Inventory) switch v { case model.SnapshotVersionV1: - i := v1alpha1.InventoryData{} + i := api.InventoryData{} if err := json.Unmarshal(s.Inventory, &i); err != nil { return api.Source{}, fmt.Errorf("failed to unmarshal v1 inventory: %w", err) } @@ -83,13 +82,13 @@ func SourceToApi(s model.Source) (api.Source, error) { } // Normalize to prevent null values from database normalizeInventoryData(&i) - source.Inventory = &v1alpha1.Inventory{ + source.Inventory = &api.Inventory{ Vcenter: &i, VcenterId: i.Vcenter.Id, Clusters: map[string]api.InventoryData{}, } default: - v2 := v1alpha1.Inventory{} + v2 := api.Inventory{} if err := json.Unmarshal(s.Inventory, &v2); err != nil { return api.Source{}, fmt.Errorf("failed to unmarshal v2 inventory: %w", err) } @@ -221,10 +220,10 @@ func AssessmentToApi(a model.Assessment) (api.Assessment, error) { CreatedAt: snapshot.CreatedAt, } if len(snapshot.Inventory) > 0 { - inventory := v1alpha1.Inventory{} + inventory := api.Inventory{} switch snapshot.Version { case 1: - invV1 := v1alpha1.InventoryData{} + invV1 := api.InventoryData{} if err := json.Unmarshal(snapshot.Inventory, &invV1); err != nil { return api.Assessment{}, err } @@ -284,28 +283,28 @@ func AssessmentListToApi(assessments []model.Assessment) (api.AssessmentList, er return assessmentList, nil } -func ClusterRequirementsResponseFormToAPI(form mappers.ClusterRequirementsResponseForm) v1alpha1.ClusterRequirementsResponse { - resourceConsumption := v1alpha1.SizingResourceConsumption{ +func ClusterRequirementsResponseFormToAPI(form mappers.ClusterRequirementsResponseForm) api.ClusterRequirementsResponse { + resourceConsumption := api.SizingResourceConsumption{ Cpu: form.ResourceConsumption.CPU, Memory: form.ResourceConsumption.Memory, } if form.ResourceConsumption.Limits.CPU != 0.0 || form.ResourceConsumption.Limits.Memory != 0.0 { - resourceConsumption.Limits = &v1alpha1.SizingResourceLimits{ + resourceConsumption.Limits = &api.SizingResourceLimits{ Cpu: form.ResourceConsumption.Limits.CPU, Memory: form.ResourceConsumption.Limits.Memory, } } if form.ResourceConsumption.OverCommitRatio.CPU != 0.0 || form.ResourceConsumption.OverCommitRatio.Memory != 0.0 { - resourceConsumption.OverCommitRatio = &v1alpha1.SizingOverCommitRatio{ + resourceConsumption.OverCommitRatio = &api.SizingOverCommitRatio{ Cpu: form.ResourceConsumption.OverCommitRatio.CPU, Memory: form.ResourceConsumption.OverCommitRatio.Memory, } } - return v1alpha1.ClusterRequirementsResponse{ - ClusterSizing: v1alpha1.ClusterSizing{ + return api.ClusterRequirementsResponse{ + ClusterSizing: api.ClusterSizing{ TotalNodes: form.ClusterSizing.TotalNodes, ControlPlaneNodes: form.ClusterSizing.ControlPlaneNodes, WorkerNodes: form.ClusterSizing.WorkerNodes, @@ -314,7 +313,7 @@ func ClusterRequirementsResponseFormToAPI(form mappers.ClusterRequirementsRespon TotalMemory: form.ClusterSizing.TotalMemory, }, ResourceConsumption: resourceConsumption, - InventoryTotals: v1alpha1.InventoryTotals{ + InventoryTotals: api.InventoryTotals{ TotalVMs: form.InventoryTotals.TotalVMs, TotalCPU: form.InventoryTotals.TotalCPU, TotalMemory: form.InventoryTotals.TotalMemory, diff --git a/internal/handlers/v1alpha1/sizer_test.go b/internal/handlers/v1alpha1/sizer_test.go index 88685cb42..1f261a7ed 100644 --- a/internal/handlers/v1alpha1/sizer_test.go +++ b/internal/handlers/v1alpha1/sizer_test.go @@ -89,10 +89,14 @@ func (m *MockAssessmentStore) Get(ctx context.Context, id uuid.UUID) (*model.Ass return assessment, nil } -func (m *MockAssessmentStore) List(ctx context.Context, filter *store.AssessmentQueryFilter) (model.AssessmentList, error) { +func (m *MockAssessmentStore) List(ctx context.Context, filter *store.AssessmentQueryFilter, options *store.AssessmentQueryOptions) (model.AssessmentList, error) { panic("List() not implemented in MockAssessmentStore for this test") } +func (m *MockAssessmentStore) Count(ctx context.Context, filter *store.AssessmentQueryFilter) (int64, error) { + panic("Count() not implemented in MockAssessmentStore for this test") +} + func (m *MockAssessmentStore) Create(ctx context.Context, assessment model.Assessment, inventory []byte) (*model.Assessment, error) { panic("Create() not implemented in MockAssessmentStore for this test") } diff --git a/internal/handlers/v1alpha1/source_test.go b/internal/handlers/v1alpha1/source_test.go index 9069c8de0..efad7c16f 100644 --- a/internal/handlers/v1alpha1/source_test.go +++ b/internal/handlers/v1alpha1/source_test.go @@ -65,7 +65,7 @@ var _ = Describe("source handler", Ordered, func() { }) AfterAll(func() { - s.Close() + _ = s.Close() }) Context("list", func() { diff --git a/internal/image/builder.go b/internal/image/builder.go index 8ee7698fd..4f51a861f 100644 --- a/internal/image/builder.go +++ b/internal/image/builder.go @@ -172,7 +172,7 @@ func (b *ImageBuilder) Generate(ctx context.Context, w io.Writer) error { return err } - tw.Flush() + _ = tw.Flush() return nil } @@ -288,7 +288,7 @@ func (b *ImageBuilder) ovfSize() (int, error) { if err != nil { return -1, err } - defer file.Close() + defer func() { _ = file.Close() }() fileInfo, err := file.Stat() if err != nil { return -1, err @@ -302,7 +302,7 @@ func (b *ImageBuilder) diskSize() (int, error) { if err != nil { return -1, err } - defer file.Close() + defer func() { _ = file.Close() }() fileInfo, err := file.Stat() if err != nil { return -1, err diff --git a/internal/rvtools/jobs/worker.go b/internal/rvtools/jobs/worker.go index 6363021e1..cb3b96c95 100644 --- a/internal/rvtools/jobs/worker.go +++ b/internal/rvtools/jobs/worker.go @@ -44,12 +44,12 @@ func (w *RVToolsWorker) createParser() (*duckdb_parser.Parser, *sql.DB, error) { } extensionDir := "/tmp/duckdb_extensions" if _, err := db.Exec(fmt.Sprintf("SET extension_directory='%s';", extensionDir)); err != nil { - db.Close() + _ = db.Close() return nil, nil, fmt.Errorf("setting duckdb extension directory: %w", err) } parser := duckdb_parser.New(db, w.validator) if err := parser.Init(); err != nil { - db.Close() + _ = db.Close() return nil, nil, fmt.Errorf("initializing duckdb schema: %w", err) } return parser, db, nil @@ -84,7 +84,7 @@ func (w *RVToolsWorker) Work(ctx context.Context, job *river.Job[RVToolsJobArgs] if err != nil { return w.failJob(ctx, logger, job.ID, "create_parser", err, fmt.Sprintf("failed to create DuckDB parser: %v", err)) } - defer duckDB.Close() + defer func() { _ = duckDB.Close() }() // Write file content to temp file for DuckDB ingestion tempFile, err := os.CreateTemp("", "rvtools-*.xlsx") @@ -92,13 +92,13 @@ func (w *RVToolsWorker) Work(ctx context.Context, job *river.Job[RVToolsJobArgs] return w.failJob(ctx, logger, job.ID, "create_temp_file", err, fmt.Sprintf("failed to create temp file: %v", err)) } tempFilePath := tempFile.Name() - defer os.Remove(tempFilePath) + defer func() { _ = os.Remove(tempFilePath) }() if _, err := tempFile.Write(job.Args.FileContent); err != nil { - tempFile.Close() + _ = tempFile.Close() return w.failJob(ctx, logger, job.ID, "write_temp_file", err, fmt.Sprintf("failed to write temp file: %v", err)) } - tempFile.Close() + _ = tempFile.Close() // Update status to validating before ingestion (which includes OPA validation) if err := w.updateJobStatus(ctx, job.ID, model.JobStatusValidating, "", nil); err != nil { diff --git a/internal/service/agent_test.go b/internal/service/agent_test.go index 19eef1c4e..2b5b00f3c 100644 --- a/internal/service/agent_test.go +++ b/internal/service/agent_test.go @@ -34,7 +34,7 @@ var _ = Describe("agent service", Ordered, func() { }) AfterAll(func() { - s.Close() + _ = s.Close() }) Context("Update agent status", func() { diff --git a/internal/service/assessment.go b/internal/service/assessment.go index 6a87b659d..8bb945c09 100644 --- a/internal/service/assessment.go +++ b/internal/service/assessment.go @@ -35,7 +35,7 @@ func NewAssessmentService(store store.Store, opaValidator *opa.Validator) *Asses } } -func (as *AssessmentService) ListAssessments(ctx context.Context, filter *AssessmentFilter) ([]model.Assessment, error) { +func (as *AssessmentService) ListAssessments(ctx context.Context, filter *AssessmentFilter) ([]model.Assessment, int64, error) { logger := as.logger.WithContext(ctx) tracer := logger.Operation("list_assessments"). WithString("username", filter.Username). @@ -59,13 +59,35 @@ func (as *AssessmentService) ListAssessments(ctx context.Context, filter *Assess storeFilter = storeFilter.WithNameLike(filter.NameLike) } - assessments, err := as.store.Assessment().List(ctx, storeFilter) + // Build query options for sorting and pagination + options := store.NewAssessmentQueryOptions() + if len(filter.Sort) > 0 { + sortParams := make([]store.SortParam, len(filter.Sort)) + for i, s := range filter.Sort { + sortParams[i] = store.SortParam{Field: s.Field, Desc: s.Desc} + } + options = options.WithSort(sortParams) + } + if filter.Limit > 0 { + options = options.WithLimit(filter.Limit) + } + if filter.Offset > 0 { + options = options.WithOffset(filter.Offset) + } + + assessments, err := as.store.Assessment().List(ctx, storeFilter, options) if err != nil { - return nil, fmt.Errorf("failed to list assessments: %w", err) + return nil, 0, fmt.Errorf("failed to list assessments: %w", err) } - tracer.Success().WithInt("count", len(assessments)).Log() - return assessments, nil + // Get total count for pagination + total, err := as.store.Assessment().Count(ctx, storeFilter) + if err != nil { + return nil, 0, fmt.Errorf("failed to count assessments: %w", err) + } + + tracer.Success().WithInt("count", len(assessments)).WithInt("total", int(total)).Log() + return assessments, total, nil } func (as *AssessmentService) GetAssessment(ctx context.Context, id uuid.UUID) (*model.Assessment, error) { @@ -283,6 +305,13 @@ type AssessmentFilter struct { NameLike string Limit int Offset int + Sort []SortField +} + +// SortField represents a single sort field and direction +type SortField struct { + Field string + Desc bool } func NewAssessmentFilter(username, orgID string) *AssessmentFilter { @@ -316,3 +345,8 @@ func (f *AssessmentFilter) WithOffset(offset int) *AssessmentFilter { f.Offset = offset return f } + +func (f *AssessmentFilter) WithSort(sort []SortField) *AssessmentFilter { + f.Sort = sort + return f +} diff --git a/internal/service/assessment_test.go b/internal/service/assessment_test.go index ba1bb807d..e7f813850 100644 --- a/internal/service/assessment_test.go +++ b/internal/service/assessment_test.go @@ -43,7 +43,7 @@ var _ = Describe("assessment service", Ordered, func() { }) AfterAll(func() { - s.Close() + _ = s.Close() }) Context("ListAssessments", func() { @@ -62,10 +62,11 @@ var _ = Describe("assessment service", Ordered, func() { It("lists all assessments for a user (private)", func() { filter := service.NewAssessmentFilter("user1", "org1") - assessments, err := svc.ListAssessments(context.TODO(), filter) + assessments, total, err := svc.ListAssessments(context.TODO(), filter) Expect(err).To(BeNil()) Expect(assessments).To(HaveLen(2)) + Expect(total).To(Equal(int64(2))) for _, assessment := range assessments { Expect(assessment.Username).To(Equal("user1")) } @@ -73,19 +74,21 @@ var _ = Describe("assessment service", Ordered, func() { It("filters assessments by source", func() { filter := service.NewAssessmentFilter("user1", "org1").WithSource(service.SourceTypeInventory) - assessments, err := svc.ListAssessments(context.TODO(), filter) + assessments, total, err := svc.ListAssessments(context.TODO(), filter) Expect(err).To(BeNil()) Expect(assessments).To(HaveLen(1)) + Expect(total).To(Equal(int64(1))) Expect(assessments[0].SourceType).To(Equal(service.SourceTypeInventory)) }) It("filters assessments by name pattern", func() { filter := service.NewAssessmentFilter("user1", "org1").WithNameLike("Test") - assessments, err := svc.ListAssessments(context.TODO(), filter) + assessments, total, err := svc.ListAssessments(context.TODO(), filter) Expect(err).To(BeNil()) Expect(assessments).To(HaveLen(2)) + Expect(total).To(Equal(int64(2))) for _, assessment := range assessments { Expect(assessment.Name).To(ContainSubstring("Test")) } @@ -107,10 +110,11 @@ var _ = Describe("assessment service", Ordered, func() { Expect(tx.Error).To(BeNil()) filter := service.NewAssessmentFilter("user1", "org1").WithSourceID(sourceID.String()) - assessments, err := svc.ListAssessments(context.TODO(), filter) + assessments, total, err := svc.ListAssessments(context.TODO(), filter) Expect(err).To(BeNil()) Expect(assessments).To(HaveLen(1)) + Expect(total).To(Equal(int64(1))) Expect(assessments[0].ID).To(Equal(assessment1ID)) Expect(assessments[0].SourceID).ToNot(BeNil()) Expect(*assessments[0].SourceID).To(Equal(sourceID)) @@ -128,10 +132,11 @@ var _ = Describe("assessment service", Ordered, func() { // Use a non-existent sourceID nonExistentSourceID := uuid.New() filter := service.NewAssessmentFilter("user1", "org1").WithSourceID(nonExistentSourceID.String()) - assessments, err := svc.ListAssessments(context.TODO(), filter) + assessments, total, err := svc.ListAssessments(context.TODO(), filter) Expect(err).To(BeNil()) Expect(assessments).To(HaveLen(0)) + Expect(total).To(Equal(int64(0))) }) AfterEach(func() { diff --git a/internal/service/sizer_test.go b/internal/service/sizer_test.go index 6ff697b8a..9755b097e 100644 --- a/internal/service/sizer_test.go +++ b/internal/service/sizer_test.go @@ -86,10 +86,14 @@ func (m *MockAssessmentStore) Get(ctx context.Context, id uuid.UUID) (*model.Ass return assessment, nil } -func (m *MockAssessmentStore) List(ctx context.Context, filter *store.AssessmentQueryFilter) (model.AssessmentList, error) { +func (m *MockAssessmentStore) List(ctx context.Context, filter *store.AssessmentQueryFilter, options *store.AssessmentQueryOptions) (model.AssessmentList, error) { return nil, nil } +func (m *MockAssessmentStore) Count(ctx context.Context, filter *store.AssessmentQueryFilter) (int64, error) { + return 0, nil +} + func (m *MockAssessmentStore) Create(ctx context.Context, assessment model.Assessment, inventory []byte) (*model.Assessment, error) { return nil, nil } diff --git a/internal/service/source_test.go b/internal/service/source_test.go index b4aaedf82..417fc1df0 100644 --- a/internal/service/source_test.go +++ b/internal/service/source_test.go @@ -47,7 +47,7 @@ var _ = Describe("source handler", Ordered, func() { }) AfterAll(func() { - s.Close() + _ = s.Close() }) Context("list", func() { diff --git a/internal/store/agent_test.go b/internal/store/agent_test.go index 9bf2f3786..5eae156c7 100644 --- a/internal/store/agent_test.go +++ b/internal/store/agent_test.go @@ -36,7 +36,7 @@ var _ = Describe("agent store", Ordered, func() { }) AfterAll(func() { - s.Close() + _ = s.Close() }) Context("list", func() { diff --git a/internal/store/assessment.go b/internal/store/assessment.go index e4a3ab8a7..07aa2860b 100644 --- a/internal/store/assessment.go +++ b/internal/store/assessment.go @@ -14,7 +14,8 @@ import ( ) type Assessment interface { - List(ctx context.Context, filter *AssessmentQueryFilter) (model.AssessmentList, error) + List(ctx context.Context, filter *AssessmentQueryFilter, options *AssessmentQueryOptions) (model.AssessmentList, error) + Count(ctx context.Context, filter *AssessmentQueryFilter) (int64, error) Get(ctx context.Context, id uuid.UUID) (*model.Assessment, error) Create(ctx context.Context, assessment model.Assessment, inventory []byte) (*model.Assessment, error) Update(ctx context.Context, assessmentID uuid.UUID, name *string, inventory []byte) (*model.Assessment, error) @@ -32,9 +33,9 @@ func NewAssessmentStore(db *gorm.DB) Assessment { return &AssessmentStore{db: db} } -func (a *AssessmentStore) List(ctx context.Context, filter *AssessmentQueryFilter) (model.AssessmentList, error) { +func (a *AssessmentStore) List(ctx context.Context, filter *AssessmentQueryFilter, options *AssessmentQueryOptions) (model.AssessmentList, error) { var assessments model.AssessmentList - tx := a.getDB(ctx).Model(&assessments).Order("created_at DESC").Preload("Snapshots", func(db *gorm.DB) *gorm.DB { + tx := a.getDB(ctx).Model(&assessments).Preload("Snapshots", func(db *gorm.DB) *gorm.DB { return db.Order("snapshots.created_at DESC") }) @@ -44,6 +45,15 @@ func (a *AssessmentStore) List(ctx context.Context, filter *AssessmentQueryFilte } } + // Apply default sorting if no sort options provided + if options == nil || len(options.QueryFn) == 0 { + tx = tx.Order("created_at DESC") + } else { + for _, fn := range options.QueryFn { + tx = fn(tx) + } + } + result := tx.Find(&assessments) if result.Error != nil { return nil, result.Error @@ -51,6 +61,23 @@ func (a *AssessmentStore) List(ctx context.Context, filter *AssessmentQueryFilte return assessments, nil } +func (a *AssessmentStore) Count(ctx context.Context, filter *AssessmentQueryFilter) (int64, error) { + var count int64 + tx := a.getDB(ctx).Model(&model.Assessment{}) + + if filter != nil { + for _, fn := range filter.QueryFn { + tx = fn(tx) + } + } + + result := tx.Count(&count) + if result.Error != nil { + return 0, result.Error + } + return count, nil +} + func (a *AssessmentStore) Get(ctx context.Context, id uuid.UUID) (*model.Assessment, error) { var assessment model.Assessment result := a.getDB(ctx).Preload("Snapshots", func(db *gorm.DB) *gorm.DB { diff --git a/internal/store/assessment_test.go b/internal/store/assessment_test.go index d4eb8286c..af760c3d9 100644 --- a/internal/store/assessment_test.go +++ b/internal/store/assessment_test.go @@ -35,7 +35,7 @@ var _ = Describe("assessment store", Ordered, func() { }) AfterAll(func() { - s.Close() + _ = s.Close() }) Context("list", func() { @@ -48,7 +48,7 @@ var _ = Describe("assessment store", Ordered, func() { tx = gormdb.Exec(fmt.Sprintf(insertAssessmentStm, assessmentID2, "assessment2", "org1", "user2", "John", "Doe", "rvtools", "NULL")) Expect(tx.Error).To(BeNil()) - assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter()) + assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter(), nil) Expect(err).To(BeNil()) Expect(assessments).To(HaveLen(2)) @@ -73,7 +73,7 @@ var _ = Describe("assessment store", Ordered, func() { tx = gormdb.Exec(fmt.Sprintf(insertAssessmentStm, assessmentID3, "assessment3", "org1", "user3", "John", "Doe", "agent", "NULL")) Expect(tx.Error).To(BeNil()) - assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter().WithOrgID("org1")) + assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter().WithOrgID("org1"), nil) Expect(err).To(BeNil()) Expect(assessments).To(HaveLen(2)) @@ -94,7 +94,7 @@ var _ = Describe("assessment store", Ordered, func() { tx = gormdb.Exec(fmt.Sprintf(insertAssessmentStm, assessmentID3, "assessment3", "org1", "user3", "John", "Doe", "agent", "NULL")) Expect(tx.Error).To(BeNil()) - assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter().WithSourceType("rvtools")) + assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter().WithSourceType("rvtools"), nil) Expect(err).To(BeNil()) Expect(assessments).To(HaveLen(1)) Expect(assessments[0].SourceType).To(Equal("rvtools")) @@ -112,14 +112,14 @@ var _ = Describe("assessment store", Ordered, func() { tx = gormdb.Exec(fmt.Sprintf(insertSnapshotStm, assessmentID, `{"vcenter": {"id": "test2"}}`)) Expect(tx.Error).To(BeNil()) - assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter()) + assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter(), nil) Expect(err).To(BeNil()) Expect(assessments).To(HaveLen(1)) Expect(assessments[0].Snapshots).To(HaveLen(2)) }) It("list assessments - no assessments", func() { - assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter()) + assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter(), nil) Expect(err).To(BeNil()) Expect(assessments).To(HaveLen(0)) }) @@ -132,7 +132,7 @@ var _ = Describe("assessment store", Ordered, func() { assessmentID, "legacy-assessment", "org1", "legacy-user", "inventory") Expect(tx.Error).To(BeNil()) - assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter()) + assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter(), nil) Expect(err).To(BeNil()) Expect(assessments).To(HaveLen(1)) @@ -578,7 +578,7 @@ var _ = Describe("assessment store", Ordered, func() { }) It("filters by name pattern", func() { - assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter().WithNameLike("Test")) + assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter().WithNameLike("Test"), nil) Expect(err).To(BeNil()) Expect(assessments).To(HaveLen(2)) @@ -589,7 +589,7 @@ var _ = Describe("assessment store", Ordered, func() { It("filters by source ID", func() { sourceID := "12345678-1234-1234-1234-123456789012" - assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter().WithSourceID(sourceID)) + assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter().WithSourceID(sourceID), nil) Expect(err).To(BeNil()) Expect(assessments).To(HaveLen(1)) Expect(assessments[0].SourceID.String()).To(Equal(sourceID)) @@ -599,7 +599,7 @@ var _ = Describe("assessment store", Ordered, func() { assessments, err := s.Assessment().List(context.TODO(), store.NewAssessmentQueryFilter(). WithOrgID("org1"). - WithSourceType("inventory")) + WithSourceType("inventory"), nil) Expect(err).To(BeNil()) Expect(assessments).To(HaveLen(1)) Expect(assessments[0].OrgID).To(Equal("org1")) diff --git a/internal/store/cache_key_test.go b/internal/store/cache_key_test.go index ca9ec2c8e..a3851596e 100644 --- a/internal/store/cache_key_test.go +++ b/internal/store/cache_key_test.go @@ -35,7 +35,7 @@ var _ = Describe("cache key store", Ordered, func() { }) AfterAll(func() { - s.Close() + _ = s.Close() }) Context("GetPublicKey", func() { diff --git a/internal/store/image_test.go b/internal/store/image_test.go index 6cb420f7d..b84962f94 100644 --- a/internal/store/image_test.go +++ b/internal/store/image_test.go @@ -34,7 +34,7 @@ var _ = Describe("image infra store", Ordered, func() { }) AfterAll(func() { - s.Close() + _ = s.Close() }) Context("create", func() { diff --git a/internal/store/key_test.go b/internal/store/key_test.go index 76d1295ad..c9142756e 100644 --- a/internal/store/key_test.go +++ b/internal/store/key_test.go @@ -39,7 +39,7 @@ var _ = Describe("key store", Ordered, func() { }) AfterAll(func() { - s.Close() + _ = s.Close() }) Context("create", func() { diff --git a/internal/store/label_test.go b/internal/store/label_test.go index 38b3ca205..c452458c8 100644 --- a/internal/store/label_test.go +++ b/internal/store/label_test.go @@ -34,7 +34,7 @@ var _ = Describe("label store", Ordered, func() { }) AfterAll(func() { - s.Close() + _ = s.Close() }) Context("UpdateLabels", func() { diff --git a/internal/store/options.go b/internal/store/options.go index 9f9d90b04..1069e3037 100644 --- a/internal/store/options.go +++ b/internal/store/options.go @@ -159,3 +159,24 @@ func (o *AssessmentQueryOptions) WithOrder(order string) *AssessmentQueryOptions }) return o } + +// WithSort applies multi-field sorting +func (o *AssessmentQueryOptions) WithSort(sorts []SortParam) *AssessmentQueryOptions { + o.QueryFn = append(o.QueryFn, func(tx *gorm.DB) *gorm.DB { + for _, sort := range sorts { + direction := "ASC" + if sort.Desc { + direction = "DESC" + } + tx = tx.Order(sort.Field + " " + direction) + } + return tx + }) + return o +} + +// SortParam represents a single sort field and direction +type SortParam struct { + Field string + Desc bool +} diff --git a/internal/store/source_test.go b/internal/store/source_test.go index de70cf5ea..91b394c99 100644 --- a/internal/store/source_test.go +++ b/internal/store/source_test.go @@ -36,7 +36,7 @@ var _ = Describe("source store", Ordered, func() { }) AfterAll(func() { - s.Close() + _ = s.Close() }) Context("list", func() { diff --git a/internal/store/store.go b/internal/store/store.go index ce02f5ffb..e39567d59 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -77,7 +77,7 @@ func (s *DataStore) Job() Job { } func (s *DataStore) Statistics(ctx context.Context) (model.InventoryStats, error) { - assessments, err := s.Assessment().List(ctx, NewAssessmentQueryFilter()) + assessments, err := s.Assessment().List(ctx, NewAssessmentQueryFilter(), nil) if err != nil { return model.InventoryStats{}, err } diff --git a/internal/store/store_test.go b/internal/store/store_test.go index d03a2c87d..140528d87 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -30,7 +30,7 @@ var _ = Describe("Store", Ordered, func() { }) AfterAll(func() { - store.Close() + _ = store.Close() }) Context("transaction", func() { diff --git a/pkg/duckdb_parser/inventory_builder_test.go b/pkg/duckdb_parser/inventory_builder_test.go index 8ccfda6ef..b2630369b 100644 --- a/pkg/duckdb_parser/inventory_builder_test.go +++ b/pkg/duckdb_parser/inventory_builder_test.go @@ -62,7 +62,7 @@ func setupTestParser(t *testing.T, validator Validator) (*Parser, *sql.DB, func( require.NoError(t, parser.Init()) cleanup := func() { - db.Close() + _ = db.Close() } return parser, db, cleanup } @@ -135,7 +135,7 @@ func createTestExcel(t *testing.T, sheets ...ExcelSheet) string { t.Helper() f := excelize.NewFile() - defer f.Close() + defer func() { _ = f.Close() }() firstSheet := true for _, sh := range sheets { @@ -175,7 +175,7 @@ func createTestExcelWithCustomHeaders(t *testing.T, vInfoHeaders []string, vms [ t.Helper() f := excelize.NewFile() - defer f.Close() + defer func() { _ = f.Close() }() vInfoIndex, err := f.NewSheet("vInfo") require.NoError(t, err) @@ -811,7 +811,7 @@ func TestBuildInventory_VMsWithSharedDisksCount(t *testing.T) { } tmpFile := createTestExcel(t, append(defaultStandardSheets(vms, hosts), NewExcelSheet("vDisk", vDiskHeaders, disks))...) - defer os.Remove(tmpFile) + defer func() { _ = os.Remove(tmpFile) }() ctx := context.Background() _, err := parser.IngestRvTools(ctx, tmpFile) diff --git a/pkg/duckdb_parser/queries.go b/pkg/duckdb_parser/queries.go index c732fb50f..b4568b244 100644 --- a/pkg/duckdb_parser/queries.go +++ b/pkg/duckdb_parser/queries.go @@ -82,7 +82,7 @@ func (p *Parser) Clusters(ctx context.Context) ([]string, error) { if err != nil { return nil, fmt.Errorf("querying clusters: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() for rows.Next() { var cluster string if err := rows.Scan(&cluster); err != nil { @@ -104,7 +104,7 @@ func (p *Parser) ClusterDatacenters(ctx context.Context) (map[string]string, err if err != nil { return nil, fmt.Errorf("querying cluster datacenters: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() for rows.Next() { var cluster, datacenter string if err := rows.Scan(&cluster, &datacenter); err != nil { @@ -126,7 +126,7 @@ func (p *Parser) ClusterObjectIDs(ctx context.Context) (map[string]string, error if err != nil { return nil, fmt.Errorf("querying cluster object ids: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() for rows.Next() { var name, objectID string if err := rows.Scan(&name, &objectID); err != nil { @@ -215,7 +215,7 @@ func (p *Parser) DiskSizeTierDistribution(ctx context.Context, filters Filters) if err != nil { return nil, fmt.Errorf("querying disk size tier: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() for rows.Next() { var tier string var summary inventory.DiskSizeTierSummary @@ -238,7 +238,7 @@ func (p *Parser) DiskTypeSummary(ctx context.Context, filters Filters) ([]invent if err != nil { return nil, fmt.Errorf("querying disk type summary: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() for rows.Next() { var s inventory.DiskTypeSummary if err := rows.Scan(&s.Type, &s.VMCount, &s.TotalSizeTB); err != nil { @@ -366,7 +366,7 @@ func (p *Parser) MigrationIssues(ctx context.Context, filters Filters, category if err != nil { return nil, fmt.Errorf("querying migration issues: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() for rows.Next() { var m inventory.MigrationIssue if err := rows.Scan(&m.ID, &m.Label, &m.Category, &m.Assessment, &m.Count); err != nil { @@ -451,7 +451,7 @@ func (p *Parser) ClustersPerDatacenter(ctx context.Context) ([]int, error) { if err != nil { return nil, fmt.Errorf("querying clusters per datacenter: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() for rows.Next() { var dc string var count int @@ -470,7 +470,7 @@ func (p *Parser) readStringIntMap(ctx context.Context, query string) (map[string if err != nil { return nil, fmt.Errorf("querying: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() for rows.Next() { var key string var count int @@ -488,7 +488,7 @@ func (p *Parser) readVMs(ctx context.Context, query string) ([]models.VM, error) if err != nil { return nil, fmt.Errorf("querying VMs: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() var vms []models.VM for rows.Next() { diff --git a/pkg/iso/downloader_test.go b/pkg/iso/downloader_test.go index 7d5459450..026fe4678 100644 --- a/pkg/iso/downloader_test.go +++ b/pkg/iso/downloader_test.go @@ -100,11 +100,11 @@ var _ = Describe("iso download manager", func() { if err != nil { log.Fatal(err) } - defer os.Remove(f.Name()) + defer func() { _ = os.Remove(f.Name()) }() err = md.Download(context.TODO(), f) Expect(err).To(BeNil()) - f.Close() + _ = f.Close() content, err := os.ReadFile(f.Name()) Expect(err).To(BeNil()) diff --git a/pkg/iso/http.go b/pkg/iso/http.go index 25bf4bf14..88ad270d9 100644 --- a/pkg/iso/http.go +++ b/pkg/iso/http.go @@ -27,7 +27,7 @@ func (h *HttpDownloader) Get(ctx context.Context, dst io.Writer) error { if err != nil { return err } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return fmt.Errorf("failed to download ISO %q, status code: %d", h.imagePath, resp.StatusCode) diff --git a/pkg/iso/minio.go b/pkg/iso/minio.go index 3a18e3350..5e32bf981 100644 --- a/pkg/iso/minio.go +++ b/pkg/iso/minio.go @@ -62,7 +62,7 @@ func (s *minioDownloader) Get(ctx context.Context, dst io.Writer) error { if err != nil { return err } - defer object.Close() + defer func() { _ = object.Close() }() objInfo, err := object.Stat() if err != nil { diff --git a/pkg/migrations/migrations_test.go b/pkg/migrations/migrations_test.go index d8865dc23..3ed8d9558 100644 --- a/pkg/migrations/migrations_test.go +++ b/pkg/migrations/migrations_test.go @@ -31,7 +31,7 @@ var _ = Describe("migrations", Ordered, func() { }) AfterAll(func() { - s.Close() + _ = s.Close() }) Context("store migrations", Ordered, func() { diff --git a/pkg/opa/validator_test.go b/pkg/opa/validator_test.go index c14fe6387..3401b95bf 100644 --- a/pkg/opa/validator_test.go +++ b/pkg/opa/validator_test.go @@ -220,9 +220,9 @@ func TestNewValidatorFromDir(t *testing.T) { originalEnv := os.Getenv("OPA_POLICIES_DIR") defer func() { if originalEnv != "" { - os.Setenv("OPA_POLICIES_DIR", originalEnv) + _ = os.Setenv("OPA_POLICIES_DIR", originalEnv) } else { - os.Unsetenv("OPA_POLICIES_DIR") + _ = os.Unsetenv("OPA_POLICIES_DIR") } }() diff --git a/test/e2e/agent/agent.go b/test/e2e/agent/agent.go index e8910c10f..acb84a68d 100644 --- a/test/e2e/agent/agent.go +++ b/test/e2e/agent/agent.go @@ -4,9 +4,9 @@ import ( "fmt" "time" - . "github.com/kubev2v/migration-planner/test/e2e/utils" + e2eutils "github.com/kubev2v/migration-planner/test/e2e/utils" libvirt "github.com/libvirt/libvirt-go" - . "github.com/onsi/ginkgo/v2" + ginkgo "github.com/onsi/ginkgo/v2" "go.uber.org/zap" ) @@ -38,8 +38,8 @@ func NewPlannerAgent(name, url string) (*plannerAgentLibvirt, error) { // DumpLogs prints journal logs from the agent VM to the GinkgoWriter func (p *plannerAgentLibvirt) DumpLogs(agentIp string) { - stdout, _ := RunSSHCommand(agentIp, "journalctl --no-pager") - fmt.Fprintf(GinkgoWriter, "Journal: %v\n", stdout) + stdout, _ := e2eutils.RunSSHCommand(agentIp, "journalctl --no-pager") + _, _ = fmt.Fprintf(ginkgo.GinkgoWriter, "Journal: %v\n", stdout) } // GetIp retrieves the IP address of the agent VM @@ -66,7 +66,7 @@ func (p *plannerAgentLibvirt) GetIp() (string, error) { } } } - return "", fmt.Errorf("No IP found") + return "", fmt.Errorf("no IP found") } // Run prepares and launches the planner agent VM @@ -90,13 +90,13 @@ func (p *plannerAgentLibvirt) DumpDataDir() error { return fmt.Errorf("failed to get IP: %w", err) } - lsOutput, err := RunSSHCommand(ip, "ls -la /var/lib/data") + lsOutput, err := e2eutils.RunSSHCommand(ip, "ls -la /var/lib/data") if err != nil { return fmt.Errorf("failed to ls /var/lib/data: %w", err) } zap.S().Infof("ls /var/lib/data:\n%s", lsOutput) - statOutput, err := RunSSHCommand(ip, "stat /var/lib/data/agent.duckdb") + statOutput, err := e2eutils.RunSSHCommand(ip, "stat /var/lib/data/agent.duckdb") if err != nil { return fmt.Errorf("failed to stat agent.duckdb: %w", err) } diff --git a/test/e2e/agent/agent_api.go b/test/e2e/agent/agent_api.go index 73ce22e50..5ee9ac5f6 100644 --- a/test/e2e/agent/agent_api.go +++ b/test/e2e/agent/agent_api.go @@ -66,7 +66,7 @@ func (a *AgentApi) request(method string, path string, body []byte, result any) if err != nil { return nil, fmt.Errorf("error getting response from local server: %v", err) } - defer res.Body.Close() + defer func() { _ = res.Body.Close() }() resBody, err := io.ReadAll(res.Body) if err != nil { diff --git a/test/e2e/agent/agent_helpers.go b/test/e2e/agent/agent_helpers.go index 55c65011e..b74cba861 100644 --- a/test/e2e/agent/agent_helpers.go +++ b/test/e2e/agent/agent_helpers.go @@ -9,8 +9,8 @@ import ( "path/filepath" "time" - . "github.com/kubev2v/migration-planner/test/e2e" - . "github.com/kubev2v/migration-planner/test/e2e/utils" + e2e "github.com/kubev2v/migration-planner/test/e2e" + e2eutils "github.com/kubev2v/migration-planner/test/e2e/utils" "github.com/libvirt/libvirt-go" "go.uber.org/zap" ) @@ -31,7 +31,7 @@ func (p *plannerAgentLibvirt) cleanupAgentFiles() error { var errs []error for _, f := range files { - if err := RemoveFile(f.path); err != nil { + if err := e2eutils.RemoveFile(f.path); err != nil { errs = append(errs, fmt.Errorf("failed to remove %s: %w", f.name, err)) } } @@ -49,9 +49,9 @@ func (p *plannerAgentLibvirt) prepareImage() error { if err != nil { return err } - defer os.Remove(ovaFile.Name()) + defer func() { _ = os.Remove(ovaFile.Name()) }() - if err = os.Mkdir(DefaultBasePath, os.ModePerm); err != nil { + if err = os.Mkdir(e2e.DefaultBasePath, os.ModePerm); err != nil { if !os.IsExist(err) { return fmt.Errorf("creating default base path: %w", err) } @@ -68,7 +68,7 @@ func (p *plannerAgentLibvirt) prepareImage() error { zap.S().Infof("Successfully extracted the Iso and Vmdk files from the OVA.") - if err := ConvertVMDKtoQCOW2(p.vmdkDiskFilePath(), p.qcowDiskFilePath()); err != nil { + if err := e2eutils.ConvertVMDKtoQCOW2(p.vmdkDiskFilePath(), p.qcowDiskFilePath()); err != nil { return fmt.Errorf("failed to convert vmdk to qcow: %w", err) } @@ -85,7 +85,7 @@ func (p *plannerAgentLibvirt) downloadOvaFromUrl(ovaFile *os.File) error { return fmt.Errorf("failed to download image: %v", err) } - defer res.Body.Close() + defer func() { _ = res.Body.Close() }() if _, err = io.Copy(ovaFile, res.Body); err != nil { return fmt.Errorf("failed to write to the file: %w", err) @@ -120,17 +120,17 @@ func (p *plannerAgentLibvirt) createVm() error { // ovaValidateAndExtract validates the OVA as a tar archive and extracts the ISO and VMDK files // from it using their known filenames inside the archive. func (p *plannerAgentLibvirt) ovaValidateAndExtract(ovaFile *os.File) error { - if err := ValidateTar(ovaFile); err != nil { + if err := e2eutils.ValidateTar(ovaFile); err != nil { return fmt.Errorf("failed to validate tar: %w", err) } // Untar ISO from OVA - if err := Untar(ovaFile, p.isoFilePath(), "MigrationAssessment.iso"); err != nil { + if err := e2eutils.Untar(ovaFile, p.isoFilePath(), "MigrationAssessment.iso"); err != nil { return fmt.Errorf("failed to uncompress the file: %w", err) } // Untar VMDK from OVA - if err := Untar(ovaFile, p.vmdkDiskFilePath(), "persistence-disk.vmdk"); err != nil { + if err := e2eutils.Untar(ovaFile, p.vmdkDiskFilePath(), "persistence-disk.vmdk"); err != nil { return fmt.Errorf("failed to uncompress the file: %w", err) } @@ -139,22 +139,22 @@ func (p *plannerAgentLibvirt) ovaValidateAndExtract(ovaFile *os.File) error { // ovaFilePath returns the expected file path of the agent OVA, func (p *plannerAgentLibvirt) ovaFilePath() string { - return filepath.Join(Home, fmt.Sprintf("%s.ova", p.name)) + return filepath.Join(e2e.Home, fmt.Sprintf("%s.ova", p.name)) } // isoFilePath returns the expected file path of the agent ISO, func (p *plannerAgentLibvirt) isoFilePath() string { - return filepath.Join(DefaultBasePath, fmt.Sprintf("%s.iso", p.name)) + return filepath.Join(e2e.DefaultBasePath, fmt.Sprintf("%s.iso", p.name)) } // vmdkDiskFilePath returns the expected path of the VMDK disk image, func (p *plannerAgentLibvirt) vmdkDiskFilePath() string { - return filepath.Join(DefaultBasePath, fmt.Sprintf("%s.vmdk", p.name)) + return filepath.Join(e2e.DefaultBasePath, fmt.Sprintf("%s.vmdk", p.name)) } // qcowDiskFilePath returns the expected path of the QCOW2 disk image, func (p *plannerAgentLibvirt) qcowDiskFilePath() string { - return filepath.Join(DefaultBasePath, fmt.Sprintf("%s.qcow2", p.name)) + return filepath.Join(e2e.DefaultBasePath, fmt.Sprintf("%s.qcow2", p.name)) } // WaitForDomainState polls the libvirt domain state until the desired state is reached diff --git a/test/e2e/e2e_rvtools_test.go b/test/e2e/e2e_rvtools_test.go index fd982a617..ec1dc6b1b 100644 --- a/test/e2e/e2e_rvtools_test.go +++ b/test/e2e/e2e_rvtools_test.go @@ -51,7 +51,7 @@ var _ = Describe("e2e-rvtools", func() { Expect(err).To(BeNil(), "Failed to generate example Excel file") tmpFile, err := CreateTempExcelFile(exampleContent) Expect(err).To(BeNil(), "Failed to create temporary Excel file") - defer os.Remove(tmpFile) + defer func() { _ = os.Remove(tmpFile) }() // Create Assessment assessment, err = svc.CreateAssessmentFromRvtools("assessment", tmpFile) @@ -82,7 +82,7 @@ var _ = Describe("e2e-rvtools", func() { // Create a corrupted Excel file tmpFile, err := CreateTempExcelFile([]byte("This is not an Excel file content")) Expect(err).To(BeNil()) - defer os.Remove(tmpFile) + defer func() { _ = os.Remove(tmpFile) }() // Attempt to create assessment from corrupted file assessment, err = svc.CreateAssessmentFromRvtools("corrupted-test", tmpFile) @@ -99,7 +99,7 @@ var _ = Describe("e2e-rvtools", func() { // Create an empty file tmpFile, err := CreateTempExcelFile([]byte{}) Expect(err).To(BeNil()) - defer os.Remove(tmpFile) + defer func() { _ = os.Remove(tmpFile) }() // Attempt to create assessment from empty file // Empty files are validated immediately at handler level before job creation @@ -119,7 +119,7 @@ var _ = Describe("e2e-rvtools", func() { Expect(err).To(BeNil()) tmpFile, err := CreateTempExcelFile(missingSheetsContent) Expect(err).To(BeNil()) - defer os.Remove(tmpFile) + defer func() { _ = os.Remove(tmpFile) }() // Attempt to create assessment from file with missing sheets assessment, err = svc.CreateAssessmentFromRvtools("missing-sheets-test", tmpFile) @@ -142,7 +142,7 @@ var _ = Describe("e2e-rvtools", func() { Expect(err).To(BeNil()) tmpFile, err := CreateTempExcelFile(validContent) Expect(err).To(BeNil()) - defer os.Remove(tmpFile) + defer func() { _ = os.Remove(tmpFile) }() // Create first assessment assessment1, err := svc.CreateAssessmentFromRvtools("duplicate-name-test", tmpFile) @@ -169,7 +169,7 @@ var _ = Describe("e2e-rvtools", func() { Expect(err).To(BeNil()) tmpFile, err := CreateTempExcelFile(validContent) Expect(err).To(BeNil()) - defer os.Remove(tmpFile) + defer func() { _ = os.Remove(tmpFile) }() // Test empty name - validated immediately at handler level assessment, err = svc.CreateAssessmentFromRvtools("", tmpFile) @@ -205,7 +205,7 @@ var _ = Describe("e2e-rvtools", func() { Expect(err).To(BeNil()) tmpFile, err := CreateTempExcelFile(largeContent) Expect(err).To(BeNil()) - defer os.Remove(tmpFile) + defer func() { _ = os.Remove(tmpFile) }() // Attempt to create assessment from large file assessment, err = svc.CreateAssessmentFromRvtools("large-file-test", tmpFile) @@ -227,7 +227,7 @@ var _ = Describe("e2e-rvtools", func() { Expect(err).To(BeNil()) tmpFile, err := CreateTempExcelFile(validContent) Expect(err).To(BeNil()) - defer os.Remove(tmpFile) + defer func() { _ = os.Remove(tmpFile) }() // Create multiple assessments concurrently (simulate concurrent uploads) done := make(chan bool, 3) @@ -271,7 +271,7 @@ var _ = Describe("e2e-rvtools", func() { Expect(err).To(BeNil()) tmpFile, err := CreateTempExcelFile(excelContent) Expect(err).To(BeNil()) - defer os.Remove(tmpFile) + defer func() { _ = os.Remove(tmpFile) }() assessment, err = svc.CreateAssessmentFromRvtools("inventory-vm-counts-test", tmpFile) Expect(err).To(BeNil()) @@ -294,7 +294,7 @@ var _ = Describe("e2e-rvtools", func() { Expect(err).To(BeNil()) tmpFile, err := CreateTempExcelFile(excelContent) Expect(err).To(BeNil()) - defer os.Remove(tmpFile) + defer func() { _ = os.Remove(tmpFile) }() assessment, err = svc.CreateAssessmentFromRvtools("inventory-power-states-test", tmpFile) Expect(err).To(BeNil()) @@ -319,7 +319,7 @@ var _ = Describe("e2e-rvtools", func() { Expect(err).To(BeNil()) tmpFile, err := CreateTempExcelFile(excelContent) Expect(err).To(BeNil()) - defer os.Remove(tmpFile) + defer func() { _ = os.Remove(tmpFile) }() assessment, err = svc.CreateAssessmentFromRvtools("inventory-infra-test", tmpFile) Expect(err).To(BeNil()) @@ -345,7 +345,7 @@ var _ = Describe("e2e-rvtools", func() { Expect(err).To(BeNil()) tmpFile, err := CreateTempExcelFile(excelContent) Expect(err).To(BeNil()) - defer os.Remove(tmpFile) + defer func() { _ = os.Remove(tmpFile) }() assessment, err = svc.CreateAssessmentFromRvtools("inventory-resources-test", tmpFile) Expect(err).To(BeNil()) @@ -372,7 +372,7 @@ var _ = Describe("e2e-rvtools", func() { Expect(err).To(BeNil()) tmpFile, err := CreateTempExcelFile(excelContent) Expect(err).To(BeNil()) - defer os.Remove(tmpFile) + defer func() { _ = os.Remove(tmpFile) }() assessment, err = svc.CreateAssessmentFromRvtools("multi-cluster-test", tmpFile) Expect(err).To(BeNil()) @@ -404,7 +404,7 @@ var _ = Describe("e2e-rvtools", func() { Expect(err).To(BeNil()) tmpFile, err := CreateTempExcelFile(excelContent) Expect(err).To(BeNil()) - defer os.Remove(tmpFile) + defer func() { _ = os.Remove(tmpFile) }() assessment, err = svc.CreateAssessmentFromRvtools("cluster-totals-test", tmpFile) Expect(err).To(BeNil()) @@ -430,7 +430,7 @@ var _ = Describe("e2e-rvtools", func() { Expect(err).To(BeNil()) tmpFile, err := CreateTempExcelFile(excelContent) Expect(err).To(BeNil()) - defer os.Remove(tmpFile) + defer func() { _ = os.Remove(tmpFile) }() assessment, err = svc.CreateAssessmentFromRvtools("concerns-test", tmpFile) Expect(err).To(BeNil()) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index a4e1e8df0..76ee5dc58 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -477,9 +477,10 @@ var _ = Describe("e2e", func() { Expect(err).To(BeNil()) Expect(assessment.Name).To(Equal("assessment1")) - assessments, err := svc.GetAssessments() + assessmentsResponse, err := svc.GetAssessments() Expect(err).To(BeNil()) - Expect(*assessments).To(HaveLen(1)) + Expect(assessmentsResponse.Assessments).To(HaveLen(1)) + Expect(assessmentsResponse.Total).To(Equal(1)) err = svc.RemoveAssessment(assessment.Id) Expect(err).To(BeNil()) diff --git a/test/e2e/helpers/test_helpers.go b/test/e2e/helpers/test_helpers.go index db286a350..16afd7770 100644 --- a/test/e2e/helpers/test_helpers.go +++ b/test/e2e/helpers/test_helpers.go @@ -5,20 +5,20 @@ import ( "github.com/google/uuid" "github.com/kubev2v/migration-planner/api/v1alpha1" - . "github.com/kubev2v/migration-planner/test/e2e/agent" - . "github.com/kubev2v/migration-planner/test/e2e/service" + agentpkg "github.com/kubev2v/migration-planner/test/e2e/agent" + servicepkg "github.com/kubev2v/migration-planner/test/e2e/service" "go.uber.org/zap" ) // CreateAgent Create VM with the UUID of the source created -func CreateAgent(sourceId uuid.UUID, vmName string, svc PlannerService) (PlannerAgent, error) { +func CreateAgent(sourceId uuid.UUID, vmName string, svc servicepkg.PlannerService) (agentpkg.PlannerAgent, error) { zap.S().Info("Creating agent...") url, err := svc.GetImageUrl(sourceId) if err != nil { return nil, fmt.Errorf("failed to get image url: %w", err) } - agent, err := NewPlannerAgent(vmName, url) + agent, err := agentpkg.NewPlannerAgent(vmName, url) if err != nil { return nil, err } @@ -32,7 +32,7 @@ func CreateAgent(sourceId uuid.UUID, vmName string, svc PlannerService) (Planner } // AgentIsUpToDate helper function to check that source is up to date eventually -func AgentIsUpToDate(svc PlannerService, uuid uuid.UUID) (bool, error) { +func AgentIsUpToDate(svc servicepkg.PlannerService, uuid uuid.UUID) (bool, error) { source, err := svc.GetSource(uuid) if err != nil { return false, fmt.Errorf("error getting source") diff --git a/test/e2e/model/e2e_models.go b/test/e2e/model/e2e_models.go index 5bdd7ffd6..99b64d234 100644 --- a/test/e2e/model/e2e_models.go +++ b/test/e2e/model/e2e_models.go @@ -1,10 +1,10 @@ package model import ( - . "github.com/kubev2v/migration-planner/test/e2e/agent" + agentpkg "github.com/kubev2v/migration-planner/test/e2e/agent" ) type E2EAgent struct { - Agent PlannerAgent - Api *AgentApi + Agent agentpkg.PlannerAgent + Api *agentpkg.AgentApi } diff --git a/test/e2e/service/assessment.go b/test/e2e/service/assessment.go index d4e1a724e..688f953be 100644 --- a/test/e2e/service/assessment.go +++ b/test/e2e/service/assessment.go @@ -38,7 +38,7 @@ func (s *plannerService) CreateAssessment(name, sourceType string, sourceId *uui if err != nil { return nil, err } - defer res.Body.Close() + defer func() { _ = res.Body.Close() }() parsed, err := internalclient.ParseCreateAssessmentResponse(res) if err != nil { @@ -104,7 +104,7 @@ func (s *plannerService) CreateRVToolsJob(name, filepath string) (*v1alpha1.Job, if err != nil { return nil, err } - defer res.Body.Close() + defer func() { _ = res.Body.Close() }() parsed, err := internalclient.ParseCreateRVToolsAssessmentResponse(res) if err != nil { @@ -124,7 +124,7 @@ func (s *plannerService) GetJob(id int64) (*v1alpha1.Job, error) { if err != nil { return nil, err } - defer res.Body.Close() + defer func() { _ = res.Body.Close() }() parsed, err := internalclient.ParseGetJobResponse(res) if err != nil { @@ -146,7 +146,7 @@ func (s *plannerService) CancelJob(id int64) (*v1alpha1.Job, error) { if err != nil { return nil, err } - defer res.Body.Close() + defer func() { _ = res.Body.Close() }() parsed, err := internalclient.ParseCancelJobResponse(res) if err != nil || parsed.HTTPResponse.StatusCode != http.StatusOK { @@ -167,7 +167,7 @@ func (s *plannerService) GetAssessment(id uuid.UUID) (*v1alpha1.Assessment, erro if err != nil { return nil, err } - defer res.Body.Close() + defer func() { _ = res.Body.Close() }() parsed, err := internalclient.ParseGetAssessmentResponse(res) if err != nil || parsed.HTTPResponse.StatusCode != http.StatusOK { @@ -181,14 +181,14 @@ func (s *plannerService) GetAssessment(id uuid.UUID) (*v1alpha1.Assessment, erro } // GetAssessments lists all assessments -func (s *plannerService) GetAssessments() (*v1alpha1.AssessmentList, error) { +func (s *plannerService) GetAssessments() (*v1alpha1.AssessmentListResponse, error) { zap.S().Infof("[PlannerService] Get assessments [user: %s, organization: %s]", s.credentials.Username, s.credentials.Organization) res, err := s.api.GetRequest(apiV1AssessmentsPath) if err != nil { return nil, err } - defer res.Body.Close() + defer func() { _ = res.Body.Close() }() parsed, err := internalclient.ParseListAssessmentsResponse(res) if err != nil { @@ -217,7 +217,7 @@ func (s *plannerService) UpdateAssessment(id uuid.UUID, name string) (*v1alpha1. if err != nil { return nil, err } - defer res.Body.Close() + defer func() { _ = res.Body.Close() }() parsed, err := internalclient.ParseUpdateAssessmentResponse(res) if err != nil { @@ -238,7 +238,7 @@ func (s *plannerService) RemoveAssessment(id uuid.UUID) error { if err != nil { return err } - defer res.Body.Close() + defer func() { _ = res.Body.Close() }() // Spec returns 200 with Assessment body if res.StatusCode != http.StatusOK { diff --git a/test/e2e/service/interfaces.go b/test/e2e/service/interfaces.go index 9a61891b5..785d7eae2 100644 --- a/test/e2e/service/interfaces.go +++ b/test/e2e/service/interfaces.go @@ -30,7 +30,7 @@ type assessmentApi interface { CreateAssessment(name, sourceType string, sourceId *uuid.UUID, inventory *v1alpha1.Inventory) (*v1alpha1.Assessment, error) CreateAssessmentFromRvtools(name, filepath string) (*v1alpha1.Assessment, error) GetAssessment(uuid.UUID) (*v1alpha1.Assessment, error) - GetAssessments() (*v1alpha1.AssessmentList, error) + GetAssessments() (*v1alpha1.AssessmentListResponse, error) UpdateAssessment(uuid.UUID, string) (*v1alpha1.Assessment, error) RemoveAssessment(uuid.UUID) error } diff --git a/test/e2e/service/service.go b/test/e2e/service/service.go index 70f20b769..1fa70a07f 100644 --- a/test/e2e/service/service.go +++ b/test/e2e/service/service.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/kubev2v/migration-planner/internal/auth" - . "github.com/kubev2v/migration-planner/test/e2e/utils" + e2eutils "github.com/kubev2v/migration-planner/test/e2e/utils" "go.uber.org/zap" ) @@ -23,7 +23,7 @@ type plannerService struct { // DefaultPlannerService initializes a planner service using default *auth.User credentials func DefaultPlannerService() (*plannerService, error) { - return NewPlannerService(DefaultUserAuth()) + return NewPlannerService(e2eutils.DefaultUserAuth()) } // NewPlannerService initializes the planner service with custom *auth.User credentials diff --git a/test/e2e/service/service_api.go b/test/e2e/service/service_api.go index 4af33b82d..0410b7f98 100644 --- a/test/e2e/service/service_api.go +++ b/test/e2e/service/service_api.go @@ -11,8 +11,8 @@ import ( "strings" "github.com/kubev2v/migration-planner/internal/auth" - . "github.com/kubev2v/migration-planner/test/e2e" - . "github.com/kubev2v/migration-planner/test/e2e/utils" + e2e "github.com/kubev2v/migration-planner/test/e2e" + e2eutils "github.com/kubev2v/migration-planner/test/e2e/utils" "go.uber.org/zap" ) @@ -25,12 +25,12 @@ type ServiceApi struct { // NewServiceApi creates and returns a new ServiceApi instance, initializing the base URL // and HTTP client for making requests to the service API func NewServiceApi(cred *auth.User) (*ServiceApi, error) { - token, err := GetToken(cred) + token, err := e2eutils.GetToken(cred) if err != nil { return nil, fmt.Errorf("error getting token: %v", err) } return &ServiceApi{ - baseURL: DefaultServiceUrl, + baseURL: e2e.DefaultServiceUrl, httpClient: &http.Client{}, jwtToken: token, }, nil @@ -114,7 +114,7 @@ func (api *ServiceApi) MultipartRequest(path, filepathStr, assessmentName string if err != nil { return nil, err } - defer file.Close() + defer func() { _ = file.Close() }() var buf bytes.Buffer writer := multipart.NewWriter(&buf) diff --git a/test/e2e/service/source.go b/test/e2e/service/source.go index e7b38196b..2cad07570 100644 --- a/test/e2e/service/source.go +++ b/test/e2e/service/source.go @@ -8,7 +8,6 @@ import ( "path" "github.com/google/uuid" - "github.com/kubev2v/migration-planner/api/v1alpha1" api "github.com/kubev2v/migration-planner/api/v1alpha1" internalclient "github.com/kubev2v/migration-planner/internal/api/client" "go.uber.org/zap" @@ -19,7 +18,7 @@ func (s *plannerService) CreateSource(name string) (*api.Source, error) { zap.S().Infof("[PlannerService] Creating source: user: %s, organization: %s", s.credentials.Username, s.credentials.Organization) - params := &v1alpha1.CreateSourceJSONRequestBody{Name: name} + params := &api.CreateSourceJSONRequestBody{Name: name} reqBody, err := json.Marshal(params) if err != nil { @@ -54,7 +53,7 @@ func (s *plannerService) GetImageUrl(id uuid.UUID) (string, error) { if err != nil { return "", fmt.Errorf("failed to get source url: %w", err) } - defer res.Body.Close() + defer func() { _ = res.Body.Close() }() var result struct { ExpiresAt string `json:"expires_at"` @@ -159,10 +158,10 @@ func (s *plannerService) RemoveSources() error { } // UpdateSource updates the inventory of a specific source -func (s *plannerService) UpdateSource(uuid uuid.UUID, inventory *v1alpha1.Inventory) error { +func (s *plannerService) UpdateSource(uuid uuid.UUID, inventory *api.Inventory) error { zap.S().Infof("[PlannerService] Update source [user: %s, organization: %s]", s.credentials.Username, s.credentials.Organization) - update := v1alpha1.UpdateInventoryJSONRequestBody{ + update := api.UpdateInventoryJSONRequestBody{ AgentId: uuid, Inventory: *inventory, } diff --git a/test/e2e/utils/auth.go b/test/e2e/utils/auth.go index 7eeea3688..8ccfc705d 100644 --- a/test/e2e/utils/auth.go +++ b/test/e2e/utils/auth.go @@ -6,13 +6,13 @@ import ( "github.com/kubev2v/migration-planner/internal/auth" "github.com/kubev2v/migration-planner/internal/cli" - . "github.com/kubev2v/migration-planner/test/e2e" + e2e "github.com/kubev2v/migration-planner/test/e2e" ) // GetToken retrieves the private key from the specified path, parses it, and then generates a token // for the given credentials using the private key. Returns the token or an error. func GetToken(credentials *auth.User) (string, error) { - privateKeyString, err := os.ReadFile(PrivateKeyPath) + privateKeyString, err := os.ReadFile(e2e.PrivateKeyPath) if err != nil { return "", fmt.Errorf("error, unable to read the private key: %v", err) } @@ -43,5 +43,5 @@ func UserAuth(user string, org string, emailDomain string) *auth.User { // DefaultUserAuth returns an auth.User object with the default username and organization. func DefaultUserAuth() *auth.User { - return UserAuth(DefaultUsername, DefaultOrganization, DefaultEmailDomain) + return UserAuth(e2e.DefaultUsername, e2e.DefaultOrganization, e2e.DefaultEmailDomain) } diff --git a/test/e2e/utils/file.go b/test/e2e/utils/file.go index 65e4f3444..ca720e203 100644 --- a/test/e2e/utils/file.go +++ b/test/e2e/utils/file.go @@ -85,7 +85,7 @@ func Untar(file *os.File, destFile string, fileName string) error { if err != nil { return fmt.Errorf("failed to create file: %w", err) } - defer outFile.Close() + defer func() { _ = outFile.Close() }() if _, err := io.Copy(outFile, tarReader); err != nil { return fmt.Errorf("failed to write file: %w", err) diff --git a/test/e2e/utils/log.go b/test/e2e/utils/log.go index 2a89730d5..1dac2fe99 100644 --- a/test/e2e/utils/log.go +++ b/test/e2e/utils/log.go @@ -4,11 +4,11 @@ import ( "sort" "time" - . "github.com/kubev2v/migration-planner/test/e2e" + e2e "github.com/kubev2v/migration-planner/test/e2e" "go.uber.org/zap" ) -// LogExecutionSummary logs the execution time of all tests stored in the TestsExecutionTime map. +// LogExecutionSummary logs the execution time of all tests stored in the e2e.TestsExecutionTime map. // It sorts the tests by duration in descending order and logs the test name along with its execution duration. func LogExecutionSummary() { zap.S().Infof("============Summarizing execution time============") @@ -20,7 +20,7 @@ func LogExecutionSummary() { var results []testResult - for test, duration := range TestsExecutionTime { + for test, duration := range e2e.TestsExecutionTime { results = append(results, testResult{name: test, duration: duration}) } diff --git a/test/e2e/utils/rvtools.go b/test/e2e/utils/rvtools.go index c73515d92..bdb2030a4 100644 --- a/test/e2e/utils/rvtools.go +++ b/test/e2e/utils/rvtools.go @@ -16,7 +16,7 @@ func ColumnToLetter(col int) string { func CreateExcelOnlyWithVInfo() ([]byte, error) { f := excelize.NewFile() - defer f.Close() + defer func() { _ = f.Close() }() // Only create vInfo sheet, missing vHost and other required sheets sheetIndex, err := f.NewSheet("vInfo") @@ -74,7 +74,7 @@ func CreateExcelOnlyWithVInfo() ([]byte, error) { func CreateLargeExcel() ([]byte, error) { f := excelize.NewFile() - defer f.Close() + defer func() { _ = f.Close() }() type SheetConfig struct { Headers []string @@ -151,7 +151,7 @@ func CreateLargeExcel() ([]byte, error) { func CreateValidTestExcel() ([]byte, error) { f := excelize.NewFile() - defer f.Close() + defer func() { _ = f.Close() }() type SheetConfig struct { Headers []string @@ -224,7 +224,7 @@ func CreateValidTestExcel() ([]byte, error) { // This replaces the static example1.xlsx file with a dynamically generated one that meets validation requirements func CreateExample1Excel() ([]byte, error) { f := excelize.NewFile() - defer f.Close() + defer func() { _ = f.Close() }() type SheetConfig struct { Headers []string @@ -311,11 +311,11 @@ func CreateTempExcelFile(content []byte) (string, error) { if err != nil { return "", err } - defer tmpFile.Close() + defer func() { _ = tmpFile.Close() }() _, err = tmpFile.Write(content) if err != nil { - os.Remove(tmpFile.Name()) + _ = os.Remove(tmpFile.Name()) return "", err } @@ -326,7 +326,7 @@ func CreateTempExcelFile(content []byte) (string, error) { // Used for testing multi-cluster inventory scenarios. func CreateMultiClusterTestExcel() ([]byte, error) { f := excelize.NewFile() - defer f.Close() + defer func() { _ = f.Close() }() type SheetConfig struct { Headers []string @@ -407,7 +407,7 @@ func CreateMultiClusterTestExcel() ([]byte, error) { // Contains VMs with CBT disabled, templates, and other concern-triggering configurations. func CreateExcelWithConcerns() ([]byte, error) { f := excelize.NewFile() - defer f.Close() + defer func() { _ = f.Close() }() type SheetConfig struct { Headers []string