diff --git a/Makefile b/Makefile index df52328727d..8f59871afd9 100644 --- a/Makefile +++ b/Makefile @@ -189,6 +189,10 @@ proto-build: error: dep-check go run github.com/layer5io/meshkit/cmd/errorutil -d . analyze -i ./server/helpers -o ./server/helpers --skip-dirs mesheryctl +## Runs meshkit error utility to update error codes for meshery server only. +server-error-util: + go run github.com/layer5io/meshkit/cmd/errorutil -d . --skip-dirs mesheryctl update -i ./server/helpers/ -o ./server/helpers + ## Build Meshery UI; Build and run Meshery Server on your local machine. ui-server: ui-meshery-build ui-provider-build server diff --git a/go.mod b/go.mod index d6da2e5e483..dd118763fa1 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,7 @@ require ( github.com/jinzhu/copier v0.3.5 github.com/layer5io/gowrk2 v0.6.1 github.com/layer5io/meshery-operator v0.6.10 - github.com/layer5io/meshkit v0.6.65 + github.com/layer5io/meshkit v0.6.66 github.com/layer5io/meshsync v0.6.14 github.com/layer5io/nighthawk-go v1.0.6 github.com/layer5io/service-mesh-performance v0.6.1 diff --git a/go.sum b/go.sum index ea10c1151f2..ab5f6890af9 100644 --- a/go.sum +++ b/go.sum @@ -1571,8 +1571,8 @@ github.com/layer5io/gowrk2 v0.6.1 h1:0eBj7VFYJ+QTMJt7i3PzxDJTEL+X+zWx4OP+lARWIoI github.com/layer5io/gowrk2 v0.6.1/go.mod h1:ugxQ23+HwQ8dmZYJd1LScw/TLKbdgfN6OOtg6iYMljg= github.com/layer5io/meshery-operator v0.6.10 h1:4YiznhS4AO/bA+uHBxCYp9Fc9w9LU2sopE3oJBdRU/Y= github.com/layer5io/meshery-operator v0.6.10/go.mod h1:RX9yjSvJS0KAdWOb/zRfYU/mSOVP1ySuUUlxHhrms1M= -github.com/layer5io/meshkit v0.6.65 h1:JltJ5hq8z/JIJf8V0m6UrsZ/PvIeKevLeIuAvH8q+DU= -github.com/layer5io/meshkit v0.6.65/go.mod h1:ZepHoPUmrDQK6T4ARmyWfKy8HejxFdJsoqC1cq4Slb8= +github.com/layer5io/meshkit v0.6.66 h1:oJKgab+7nlp2EArTwq9r0ZW+/+UIPrY5Efs4eLqIpdg= +github.com/layer5io/meshkit v0.6.66/go.mod h1:/EX5QLmgZpLPqhBHGvNqyHM6ljfc6hYwULCEcnqTCVw= github.com/layer5io/meshsync v0.6.14 h1:y5Fbq76WGYWjdzYFNOD1YgowP/0m/NxPZ/hA1JGJ3RA= github.com/layer5io/meshsync v0.6.14/go.mod h1:21VTdYITKXpBSb+kj2CcR3T0KbrcLJwhiewQRKTqvUM= github.com/layer5io/nighthawk-go v1.0.6 h1:YMCw65FvwpbByX+M7McdNYRNDW9oOw3GQaXJ1RMDdGw= diff --git a/server/handlers/design_engine_handler.go b/server/handlers/design_engine_handler.go index 423a7dc34f2..7ab89135db3 100644 --- a/server/handlers/design_engine_handler.go +++ b/server/handlers/design_engine_handler.go @@ -12,6 +12,7 @@ import ( "github.com/ghodss/yaml" "github.com/gofrs/uuid" + "github.com/layer5io/meshery/server/helpers/utils" "github.com/layer5io/meshery/server/meshes" "github.com/layer5io/meshery/server/models" "github.com/layer5io/meshery/server/models/pattern/core" @@ -89,7 +90,7 @@ func (h *Handler) PatternFileHandler( provider, patternFile, prefObj, - user.UserID, + user.ID, isDel, r.URL.Query().Get("verify") == "true", r.URL.Query().Get("dryRun") == "true", @@ -345,7 +346,7 @@ func (sap *serviceActionProvider) Mutate(p *core.Pattern) { // NOTE: Currently tied to kubernetes // Returns ComponentName->ContextID->Response -func (sap *serviceActionProvider) DryRun(comps []v1alpha1.Component) (resp map[string]map[string]core.DryRunResponse2, err error) { +func (sap *serviceActionProvider) DryRun(comps []v1alpha1.Component) (resp map[string]map[string]core.DryRunResponseWrapper, err error) { for _, cmp := range comps { for ctxID, kc := range sap.ctxTokubeconfig { cl, err := meshkube.New([]byte(kc)) @@ -353,8 +354,9 @@ func (sap *serviceActionProvider) DryRun(comps []v1alpha1.Component) (resp map[s return resp, err } - st, ok, err := k8s.DryRunHelper(cl, cmp) - dResp := core.DryRunResponse2{Success: ok, Component: &core.Service{ + // status represents kubernetes status object + status, ok, err := k8s.DryRunHelper(cl, cmp) + dResp := core.DryRunResponseWrapper{Success: ok, Component: &core.Service{ Name: cmp.Name, Type: cmp.Spec.Type, Namespace: cmp.Namespace, @@ -364,10 +366,11 @@ func (sap *serviceActionProvider) DryRun(comps []v1alpha1.Component) (resp map[s Labels: cmp.Labels, Annotations: cmp.Annotations, }} + // Dry run was success if ok { dResp.Component.Settings = make(map[string]interface{}) - for k, v := range st { + for k, v := range status { if k == "apiVersion" || k == "kind" || k == "metadata" { continue } @@ -377,63 +380,66 @@ func (sap *serviceActionProvider) DryRun(comps []v1alpha1.Component) (resp map[s dResp.Error = &core.DryRunResponse{ Status: err.Error(), } - } else { //Dry run failure returned with an error wrapped in kubernetes custom error - dResp.Error = &core.DryRunResponse{} - byt, err := json.Marshal(st) - if err != nil { - return nil, err - } - var a v1.StatusApplyConfiguration - err = json.Unmarshal(byt, &a) + } else { //Dry run failure returned with an error wrapped in kubernetes custom error + dResp.Error, err = convertRawDryRunResponse(cmp.Name, status) if err != nil { return nil, err } - if a.Status != nil { - dResp.Error.Status = *a.Status - } - dResp.Error.Causes = make([]core.DryRunFailureCause, 0) - if a.Details != nil { - for _, c := range a.Details.Causes { - msg := "" - field := "" - typ := "" - if c.Message != nil { - msg = *c.Message - } - if c.Field != nil { - field = cmp.Name + "." + getComponentFieldPathFromK8sFieldPath(*c.Field) - } - if c.Type != nil { - typ = string(*c.Type) - } - failureCase := core.DryRunFailureCause{Message: msg, FieldPath: field, Type: typ} - dResp.Error.Causes = append(dResp.Error.Causes, failureCase) - } - } } if resp == nil { - resp = make(map[string]map[string]core.DryRunResponse2) + resp = make(map[string]map[string]core.DryRunResponseWrapper) } if resp[cmp.Name] == nil { - resp[cmp.Name] = make(map[string]core.DryRunResponse2) + resp[cmp.Name] = make(map[string]core.DryRunResponseWrapper) } resp[cmp.Name][ctxID] = dResp } } return } -func getComponentFieldPathFromK8sFieldPath(path string) (newpath string) { - if strings.HasPrefix(path, "metadata.") { - path = strings.TrimPrefix(path, "metadata.") - paths := strings.Split(path, ".") - if len(paths) != 0 { - if paths[0] == "name" || paths[0] == "namespace" || paths[0] == "labels" || paths[0] == "annotations" { - return paths[0] + +func convertRawDryRunResponse(componentName string, status map[string]interface{}) (*core.DryRunResponse, error) { + response := core.DryRunResponse{} + + byt, err := json.Marshal(status) + if err != nil { + return nil, err + } + + var a v1.StatusApplyConfiguration + err = json.Unmarshal(byt, &a) + if err != nil { + return nil, err + } + + if a.Status != nil { + response.Status = *a.Status + } + + response.Causes = make([]core.DryRunFailureCause, 0) + if a.Details != nil { + for _, cause := range a.Details.Causes { + msg := "" + field := "" + typ := "" + if cause.Message != nil { + msg = *cause.Message + } + if cause.Field != nil { + field = componentName + "." + utils.GetComponentFieldPathFromK8sFieldPath(*cause.Field) + } + if cause.Type != nil { + typ = string(*cause.Type) } + failureCase := core.DryRunFailureCause{Message: msg, FieldPath: field, Type: typ} + response.Causes = append(response.Causes, failureCase) } - return } - return fmt.Sprintf("%s.%s", "settings", path) + + if len(response.Causes) == 0 && a.Message != nil { + response.Status = *a.Message + } + return &response, nil } func (sap *serviceActionProvider) Provision(ccp stages.CompConfigPair) (string, error) { // Marshal the component diff --git a/server/handlers/error.go b/server/handlers/error.go index 96dbf843eef..0a7564d59e7 100644 --- a/server/handlers/error.go +++ b/server/handlers/error.go @@ -464,7 +464,7 @@ func ErrDecodePattern(err error) error { } func ErrParsePattern(err error) error { - return errors.New(ErrParsePatternCode, errors.Alert, []string{"Error failed to parse pattern file"}, []string{err.Error()}, []string{}, []string{}) + return errors.New(ErrParsePatternCode, errors.Alert, []string{"Error failed to parse pattern file from cytoJSON format"}, []string{err.Error()}, []string{}, []string{}) } func ErrConvertPattern(err error) error { diff --git a/server/handlers/meshery_application_handler.go b/server/handlers/meshery_application_handler.go index aa6a7850d6b..eeec29f1121 100644 --- a/server/handlers/meshery_application_handler.go +++ b/server/handlers/meshery_application_handler.go @@ -135,10 +135,10 @@ func (h *Handler) handleApplicationPOST( } var parsedBody *MesheryApplicationRequestBody if err := json.NewDecoder(r.Body).Decode(&parsedBody); err != nil { - http.Error(rw, ErrRetrieveData(err).Error(), http.StatusBadRequest) + http.Error(rw, ErrRequestBody(err).Error(), http.StatusBadRequest) addMeshkitErr(&res, ErrRetrieveData(err)) event := eventBuilder.WithSeverity(events.Error).WithMetadata(map[string]interface{}{ - "error": ErrRetrieveData(err), + "error": ErrRequestBody(err), }).WithDescription("Unable to parse uploaded application.").Build() _ = provider.PersistEvent(event) @@ -491,7 +491,9 @@ func (h *Handler) handleApplicationPOST( h.formatApplicationOutput(rw, resp, format, &res, eventBuilder) eventBuilder.WithSeverity(events.Informational) - _ = provider.PersistEvent(eventBuilder.Build()) + event := eventBuilder.Build() + go h.config.EventBroadcaster.Publish(userID, event) + _ = provider.PersistEvent(event) var mesheryApplicationContent []models.MesheryApplication err = json.Unmarshal(resp, &mesheryApplicationContent) @@ -537,6 +539,10 @@ func (h *Handler) handleApplicationPOST( } h.formatApplicationOutput(rw, byt, format, &res, eventBuilder) + + event := eventBuilder.Build() + _ = provider.PersistEvent(event) + go h.config.EventBroadcaster.Publish(userID, event) } func (h *Handler) handleApplicationUpdate(rw http.ResponseWriter, @@ -674,7 +680,9 @@ func (h *Handler) handleApplicationUpdate(rw http.ResponseWriter, go h.config.ConfigurationChannel.PublishApplications() h.formatApplicationOutput(rw, resp, format, &res, eventBuilder) - _ = provider.PersistEvent(eventBuilder.Build()) + event := eventBuilder.Build() + go h.config.EventBroadcaster.Publish(userID, event) + _ = provider.PersistEvent(event) return } @@ -718,7 +726,10 @@ func (h *Handler) handleApplicationUpdate(rw http.ResponseWriter, eventBuilder.WithSeverity(events.Informational) h.formatApplicationOutput(rw, resp, format, &res, eventBuilder) - _ = provider.PersistEvent(eventBuilder.Build()) + event := eventBuilder.Build() + _ = provider.PersistEvent(event) + go h.config.EventBroadcaster.Publish(userID, event) + } // swagger:route GET /api/application ApplicationsAPI idGetMesheryApplications @@ -789,6 +800,9 @@ func (h *Handler) DeleteMesheryApplicationHandler( eventBuilder := events.NewEvent().FromUser(userID).FromSystem(*h.SystemID).WithCategory("application").WithAction("delete").ActedUpon(uuid.FromStringOrNil(applicationID)) resp, err := provider.DeleteMesheryApplication(r, applicationID) + mesheryApplication := models.MesheryApplication{} + _ = json.Unmarshal(resp, &mesheryApplication) + if err != nil { errAppDelete := ErrDeleteApplication(err) h.log.Error(errAppDelete) @@ -801,7 +815,7 @@ func (h *Handler) DeleteMesheryApplicationHandler( return } - event := eventBuilder.WithSeverity(events.Informational).WithDescription("Application deleted.").Build() + event := eventBuilder.WithSeverity(events.Informational).WithDescription(fmt.Sprintf("Application %s deleted.", mesheryApplication.Name)).Build() _ = provider.PersistEvent(event) go h.config.EventBroadcaster.Publish(userID, event) diff --git a/server/handlers/meshery_pattern_handler.go b/server/handlers/meshery_pattern_handler.go index 9286f64abe1..08f8864c015 100644 --- a/server/handlers/meshery_pattern_handler.go +++ b/server/handlers/meshery_pattern_handler.go @@ -7,11 +7,12 @@ import ( "net/http" "strings" - "github.com/google/uuid" + "github.com/gofrs/uuid" "github.com/gorilla/mux" "github.com/layer5io/meshery/server/meshes" "github.com/layer5io/meshery/server/models" "github.com/layer5io/meshkit/errors" + "github.com/layer5io/meshkit/models/events" pCore "github.com/layer5io/meshery/server/models/pattern/core" "github.com/layer5io/meshery/server/models/pattern/stages" @@ -60,7 +61,7 @@ func (h *Handler) handlePatternPOST( rw http.ResponseWriter, r *http.Request, _ *models.Preference, - _ *models.User, + user *models.User, provider models.Provider, ) { defer func() { @@ -68,27 +69,46 @@ func (h *Handler) handlePatternPOST( }() var err error + userID := uuid.FromStringOrNil(user.ID) + eventBuilder := events.NewEvent().FromUser(userID).FromSystem(*h.SystemID).WithCategory("pattern").WithAction("create").ActedUpon(userID) + res := meshes.EventsResponse{ Component: "core", ComponentName: "Design", - OperationId: uuid.NewString(), + OperationId: uuid.Nil.String(), // to be removed EventType: meshes.EventType_INFO, } var parsedBody *MesheryPatternRequestBody if err := json.NewDecoder(r.Body).Decode(&parsedBody); err != nil { h.log.Error(ErrRequestBody(err)) http.Error(rw, ErrRequestBody(err).Error(), http.StatusBadRequest) - addMeshkitErr(&res, ErrRequestBody(err)) - go h.EventsBuffer.Publish(&res) + event := eventBuilder.WithSeverity(events.Error).WithMetadata(map[string]interface{}{ + "error": ErrRequestBody(err), + }).WithDescription("Unable to parse uploaded pattern.").Build() + + _ = provider.PersistEvent(event) + go h.config.EventBroadcaster.Publish(userID, event) return } + actedUpon := &userID + if parsedBody.PatternData != nil && parsedBody.PatternData.ID != nil { + actedUpon = parsedBody.PatternData.ID + } + + eventBuilder.ActedUpon(*actedUpon) + token, err := provider.GetProviderToken(r) if err != nil { h.log.Error(ErrRetrieveUserToken(err)) http.Error(rw, ErrRetrieveUserToken(err).Error(), http.StatusInternalServerError) - addMeshkitErr(&res, ErrRequestBody(err)) - go h.EventsBuffer.Publish(&res) + event := eventBuilder.WithSeverity(events.Critical).WithMetadata(map[string]interface{}{ + "error": ErrRetrieveUserToken(err), + }).WithDescription("No auth token provided in the request.").Build() + + _ = provider.PersistEvent(event) + go h.config.EventBroadcaster.Publish(userID, event) + return } @@ -99,8 +119,12 @@ func (h *Handler) handlePatternPOST( if err != nil { rw.WriteHeader(http.StatusBadRequest) fmt.Fprintf(rw, "%s", err) - addMeshkitErr(&res, ErrSavePattern(err)) - go h.EventsBuffer.Publish(&res) + event := eventBuilder.WithSeverity(events.Error).WithMetadata(map[string]interface{}{ + "error": ErrSavePattern(err), + }).WithDescription("Pattern save failed, cytoJSON could be malformed.").Build() + + _ = provider.PersistEvent(event) + go h.config.EventBroadcaster.Publish(userID, event) return } @@ -108,17 +132,25 @@ func (h *Handler) handlePatternPOST( if err != nil { rw.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(rw, "%s", err) - addMeshkitErr(&res, ErrSavePattern(err)) - go h.EventsBuffer.Publish(&res) + event := eventBuilder.WithSeverity(events.Error).WithMetadata(map[string]interface{}{ + "error": ErrSavePattern(err), + }).WithDescription(ErrSavePattern(err).Error()).Build() + + _ = provider.PersistEvent(event) + go h.config.EventBroadcaster.Publish(userID, event) return } patternName, err := models.GetPatternName(string(pfByt)) if err != nil { - h.log.Error(ErrGetPattern(err)) - http.Error(rw, ErrGetPattern(err).Error(), http.StatusBadRequest) - addMeshkitErr(&res, ErrGetPattern(err)) - go h.EventsBuffer.Publish(&res) + h.log.Error(ErrSavePattern(err)) + http.Error(rw, ErrSavePattern(err).Error(), http.StatusBadRequest) + event := eventBuilder.WithSeverity(events.Error).WithMetadata(map[string]interface{}{ + "error": ErrSavePattern(err), + }).WithDescription("unable to get \"name\" from the pattern.").Build() + + _ = provider.PersistEvent(event) + go h.config.EventBroadcaster.Publish(userID, event) return } @@ -139,13 +171,17 @@ func (h *Handler) handlePatternPOST( if err != nil { h.log.Error(ErrSavePattern(err)) http.Error(rw, ErrSavePattern(err).Error(), http.StatusInternalServerError) - addMeshkitErr(&res, ErrSavePattern(err)) - go h.EventsBuffer.Publish(&res) + event := eventBuilder.WithSeverity(events.Error).WithMetadata(map[string]interface{}{ + "error": ErrSavePattern(err), + }).WithDescription(ErrSavePattern(err).Error()).Build() + + _ = provider.PersistEvent(event) + go h.config.EventBroadcaster.Publish(userID, event) return } go h.config.ConfigurationChannel.PublishPatterns() - h.formatPatternOutput(rw, resp, format, &res) + h.formatPatternOutput(rw, resp, format, &res, eventBuilder) return } @@ -158,7 +194,8 @@ func (h *Handler) handlePatternPOST( return } - h.formatPatternOutput(rw, byt, format, &res) + h.formatPatternOutput(rw, byt, format, &res, eventBuilder) + return } // If Content is not empty then assume it's a local upload @@ -178,8 +215,12 @@ func (h *Handler) handlePatternPOST( if err != nil { h.log.Error(ErrSavePattern(err)) http.Error(rw, ErrSavePattern(err).Error(), http.StatusBadRequest) - addMeshkitErr(&res, ErrSavePattern(err)) - go h.EventsBuffer.Publish(&res) + event := eventBuilder.WithSeverity(events.Error).WithMetadata(map[string]interface{}{ + "error": ErrSavePattern(err), + }).WithDescription("unable to get \"name\" from the pattern.").Build() + + _ = provider.PersistEvent(event) + go h.config.EventBroadcaster.Publish(userID, event) return } parsedBody.PatternData.Name = patternName @@ -202,12 +243,20 @@ func (h *Handler) handlePatternPOST( if err != nil { h.log.Error(ErrSavePattern(err)) http.Error(rw, ErrSavePattern(err).Error(), http.StatusInternalServerError) - addMeshkitErr(&res, ErrSavePattern(err)) - go h.EventsBuffer.Publish(&res) + + event := eventBuilder.WithSeverity(events.Error).WithMetadata(map[string]interface{}{ + "error": ErrSavePattern(err), + }).WithDescription(ErrSavePattern(err).Error()).Build() + + _ = provider.PersistEvent(event) + go h.config.EventBroadcaster.Publish(userID, event) return } + h.formatPatternOutput(rw, resp, format, &res, eventBuilder) + event := eventBuilder.Build() + _ = provider.PersistEvent(event) + go h.config.EventBroadcaster.Publish(userID, event) go h.config.ConfigurationChannel.PublishPatterns() - h.formatPatternOutput(rw, resp, format, &res) return } @@ -220,7 +269,10 @@ func (h *Handler) handlePatternPOST( return } - h.formatPatternOutput(rw, byt, format, &res) + h.formatPatternOutput(rw, byt, format, &res, eventBuilder) + event := eventBuilder.Build() + _ = provider.PersistEvent(event) + go h.config.EventBroadcaster.Publish(userID, event) return } @@ -230,12 +282,19 @@ func (h *Handler) handlePatternPOST( if err != nil { h.log.Error(ErrImportPattern(err)) http.Error(rw, ErrImportPattern(err).Error(), http.StatusInternalServerError) - addMeshkitErr(&res, ErrImportPattern(err)) - go h.EventsBuffer.Publish(&res) + event := eventBuilder.WithSeverity(events.Error).WithMetadata(map[string]interface{}{ + "error": ErrImportPattern(err), + }).WithDescription(ErrImportPattern(err).Error()).Build() + + _ = provider.PersistEvent(event) + go h.config.EventBroadcaster.Publish(userID, event) return } - h.formatPatternOutput(rw, resp, format, &res) + h.formatPatternOutput(rw, resp, format, &res, eventBuilder) + event := eventBuilder.Build() + _ = provider.PersistEvent(event) + go h.config.EventBroadcaster.Publish(userID, event) return } //Depracated: The below logic was used when applications were stored as k8s_manifests. @@ -399,17 +458,35 @@ func (h *Handler) DeleteMesheryPatternHandler( rw http.ResponseWriter, r *http.Request, _ *models.Preference, - _ *models.User, + user *models.User, provider models.Provider, ) { patternID := mux.Vars(r)["id"] + userID := uuid.FromStringOrNil(user.ID) + eventBuilder := events.NewEvent().FromUser(userID).FromSystem(*h.SystemID).WithCategory("pattern").WithAction("delete").ActedUpon(uuid.FromStringOrNil(patternID)) + + mesheryPattern := models.MesheryPattern{} + resp, err := provider.DeleteMesheryPattern(r, patternID) if err != nil { - h.log.Error(ErrDeletePattern(err)) - http.Error(rw, ErrDeletePattern(err).Error(), http.StatusInternalServerError) + errPatternDelete := ErrDeletePattern(err) + + h.log.Error(errPatternDelete) + http.Error(rw, errPatternDelete.Error(), http.StatusInternalServerError) + event := eventBuilder.WithSeverity(events.Error).WithMetadata(map[string]interface{}{ + "error": errPatternDelete, + }).WithDescription("Error deleting pattern.").Build() + http.Error(rw, errPatternDelete.Error(), http.StatusInternalServerError) + _ = provider.PersistEvent(event) + go h.config.EventBroadcaster.Publish(userID, event) return } + _ = json.Unmarshal(resp, &mesheryPattern) + + event := eventBuilder.WithSeverity(events.Informational).WithDescription(fmt.Sprintf("Pattern %s deleted.", mesheryPattern.Name)).Build() + _ = provider.PersistEvent(event) + go h.config.EventBroadcaster.Publish(userID, event) go h.config.ConfigurationChannel.PublishPatterns() rw.Header().Set("Content-Type", "application/json") fmt.Fprint(rw, string(resp)) @@ -633,7 +710,7 @@ func (h *Handler) GetMesheryPatternHandler( fmt.Fprint(rw, string(resp)) } -func (h *Handler) formatPatternOutput(rw http.ResponseWriter, content []byte, format string, res *meshes.EventsResponse) { +func (h *Handler) formatPatternOutput(rw http.ResponseWriter, content []byte, format string, res *meshes.EventsResponse, eventBuilder *events.EventBuilder) { contentMesheryPatternSlice := make([]models.MesheryPattern, 0) if err := json.Unmarshal(content, &contentMesheryPatternSlice); err != nil { @@ -646,12 +723,15 @@ func (h *Handler) formatPatternOutput(rw http.ResponseWriter, content []byte, fo result := []models.MesheryPattern{} names := []string{} for _, content := range contentMesheryPatternSlice { + eventBuilder.ActedUpon(*content.ID) if format == "cytoscape" { patternFile, err := pCore.NewPatternFile([]byte(content.PatternFile)) if err != nil { http.Error(rw, ErrParsePattern(err).Error(), http.StatusBadRequest) - addMeshkitErr(res, ErrParsePattern(err)) - go h.EventsBuffer.Publish(res) + + eventBuilder.WithSeverity(events.Error).WithMetadata(map[string]interface{}{ + "error": ErrParsePattern(err), + }).WithDescription("Unable to parse pattern file, pattern could be malformed.").Build() return } @@ -681,9 +761,11 @@ func (h *Handler) formatPatternOutput(rw http.ResponseWriter, content []byte, fo obj := "pattern file" http.Error(rw, models.ErrMarshal(err, obj).Error(), http.StatusInternalServerError) addMeshkitErr(res, models.ErrMarshal(err, obj)) + go h.EventsBuffer.Publish(res) return } + eventBuilder.WithDescription(fmt.Sprintf("Design %s saved", strings.Join(names, ","))) rw.Header().Set("Content-Type", "application/json") fmt.Fprint(rw, string(data)) res.Details = "\"" + strings.Join(names, ",") + "\" design saved" diff --git a/server/helpers/component_info.json b/server/helpers/component_info.json index 49e5f569232..5467e00e10e 100644 --- a/server/helpers/component_info.json +++ b/server/helpers/component_info.json @@ -1,5 +1,5 @@ { "name": "meshery-server", "type": "component", - "next_error_code": 1536 + "next_error_code": 1537 } \ No newline at end of file diff --git a/server/helpers/utils/utils.go b/server/helpers/utils/utils.go index 50fca08fea5..89bc2f3cbb9 100644 --- a/server/helpers/utils/utils.go +++ b/server/helpers/utils/utils.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path/filepath" + "regexp" "strconv" "strings" "sync" @@ -307,3 +308,34 @@ func SanitizeFileName(fileName string) string { finalPath = append(finalPath, "-*.", suffixPath) return strings.Join(finalPath, "") } + +func GetComponentFieldPathFromK8sFieldPath(path string) (newpath string) { + if strings.HasPrefix(path, "metadata.") { + path = strings.TrimPrefix(path, "metadata.") + paths := strings.Split(path, ".") + if len(paths) != 0 { + if paths[0] == "name" || paths[0] == "namespace" || paths[0] == "labels" || paths[0] == "annotations" { + return paths[0] + } + } + return + } + return fmt.Sprintf("%s.%s", "settings", path) +} + +// Prunes the diff part present in the k8s response message. +// Diff corresponds to the previous change and applied change, and doesn't contain any info which can be helpful to the user. +// If we want we can show this in a CodeEditor component. +func FormatK8sMessage(message string) string { + exp, err := regexp.Compile(`(/?[a-zA-Z]).*\n([-,+])+`) + if err != nil { + return message + } + index := exp.FindStringIndex(message) + if index == nil { + return message + } + // If index is not nil, there will always be an array of length 2. + // 0th index since we want the start index of matched string. + return message[:index[0]] +} \ No newline at end of file diff --git a/server/models/pattern/core/pattern.go b/server/models/pattern/core/pattern.go index 7433d337042..087ee842c3a 100644 --- a/server/models/pattern/core/pattern.go +++ b/server/models/pattern/core/pattern.go @@ -124,7 +124,7 @@ func isSpecialKey(k string) bool { // In case of any breaking change or bug caused by this, set this to false and the whitespace addition in schema generated/consumed would be removed(will go back to default behavior) const Format prettifier = true -type DryRunResponse2 struct { +type DryRunResponseWrapper struct { //When success is true, error will be nil and Component will contain the structure of the component as it will look after deployment //When success is false, error will contain the errors. And Component will be set to Nil Success bool `json:"success"` diff --git a/server/models/pattern/patterns/k8s/error.go b/server/models/pattern/patterns/k8s/error.go new file mode 100644 index 00000000000..7d529c8300e --- /dev/null +++ b/server/models/pattern/patterns/k8s/error.go @@ -0,0 +1,66 @@ +package k8s + +import ( + "encoding/json" + "fmt" + meshkitutils "github.com/layer5io/meshkit/utils/kubernetes" + + "github.com/layer5io/meshery/server/helpers/utils" + "github.com/layer5io/meshkit/errors" + kubeerror "k8s.io/apimachinery/pkg/api/errors" +) + +const ( + ErrDryRunCode = "1536" +) + +func isErrKubeStatusErr(err error) bool { + switch err.(type) { + case *kubeerror.StatusError: + return true + default: + return false + } +} + +func formatKubeStatusErrToMeshkitErr(status *[]byte, componentName string) error { + var shortDescription, longDescription []string + + var kubeErr kubeerror.StatusError + + err := json.Unmarshal(*status, &kubeErr) + kubeStatus := kubeErr.ErrStatus + if err != nil { + return err + } + + st := string(kubeStatus.Status) + + if kubeStatus.Details != nil { + + sd := kubeStatus.Details.Kind + + sd = fmt.Sprintf("%s %s", sd, st) + + sd = fmt.Sprintf("%s \"%s\"", sd, kubeStatus.Details.Name) + + for _, cause := range kubeStatus.Details.Causes { + var shortDes, longDes, field string + longDes = utils.FormatK8sMessage(cause.Message) + longDescription = append(longDescription, longDes) + sd = fmt.Sprintf("%s %s", sd, cause.Type) + field = componentName + "." + utils.GetComponentFieldPathFromK8sFieldPath(cause.Field) + shortDes = fmt.Sprintf("%s: %s", sd, field) + shortDescription = append(shortDescription, shortDes) + + } + if len(kubeStatus.Details.Causes) == 0 { + longDescription = append(longDescription, kubeStatus.Message) + } + } + return errors.New(meshkitutils.ErrApplyManifestCode, errors.Alert, shortDescription, longDescription, []string{}, []string{}) +} + +func ErrDryRun(err error, obj string) error { + return errors.New(ErrDryRunCode, errors.Alert, []string{"error running dry run on the design"}, []string{err.Error()}, []string{obj}, []string{}) +} diff --git a/server/models/pattern/patterns/k8s/k8s.go b/server/models/pattern/patterns/k8s/k8s.go index 6d5b998ba61..811c2aa28dc 100644 --- a/server/models/pattern/patterns/k8s/k8s.go +++ b/server/models/pattern/patterns/k8s/k8s.go @@ -6,23 +6,36 @@ import ( "fmt" "strings" + "github.com/layer5io/meshery/server/models" "github.com/layer5io/meshkit/models/oam/core/v1alpha1" meshkube "github.com/layer5io/meshkit/utils/kubernetes" "gopkg.in/yaml.v2" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/client-go/rest" ) -func Deploy(kubeClient *meshkube.Client, oamComp v1alpha1.Component, _ v1alpha1.Configuration, isDel bool) error { - resource := createK8sResourceStructure(oamComp) +func Deploy(kubeClient *meshkube.Client, comp v1alpha1.Component, _ v1alpha1.Configuration, isDel bool) error { + resource := createK8sResourceStructure(comp) manifest, err := yaml.Marshal(resource) if err != nil { return err } - return kubeClient.ApplyManifest(manifest, meshkube.ApplyOptions{ - Namespace: oamComp.Namespace, + err = kubeClient.ApplyManifest(manifest, meshkube.ApplyOptions{ + Namespace: comp.Namespace, Update: true, Delete: isDel, }) + if err != nil { + fmt.Println("29: ", err) + if isErrKubeStatusErr(err) { + status, _ := json.Marshal(err) + fmt.Println("31: ", string(status)) + return formatKubeStatusErrToMeshkitErr(&status, comp.Name) + } else { + return meshkube.ErrApplyManifest(err) + } + } + return nil } func DryRunHelper(client *meshkube.Client, comp v1alpha1.Component) (st map[string]interface{}, success bool, err error) { @@ -34,46 +47,45 @@ func DryRunHelper(client *meshkube.Client, comp v1alpha1.Component) (st map[stri // TODO: add more tests for this function func dryRun(rClient rest.Interface, k8sResource map[string]interface{}, namespace string) (st map[string]interface{}, success bool, err error) { if k8sResource["kind"] == "" || k8sResource["apiVersion"] == "" { - err = fmt.Errorf("invalid resource or namespace not provided") + err = ErrDryRun(fmt.Errorf("invalid resource or namespace not provided"), "\"kind\" and \"apiVersion\" cannot be empty") return } + aV := k8sResource["apiVersion"].(string) // for non-core resources, the endpoint should use 'apis' instead of 'api' apiString := "api" if len(strings.Split(aV, "/")) > 1 { apiString = apiString + "s" } + var path string + if namespace != "" { path = fmt.Sprintf("/%s/%s/namespaces/%s/%s", apiString, aV, namespace, kindToResource(k8sResource["kind"].(string))) } else { path = fmt.Sprintf("/%s/%s/%s", apiString, aV, kindToResource(k8sResource["kind"].(string))) } + data, err := json.Marshal(k8sResource) if err != nil { + err = models.ErrMarshal(err, "k8s resource") return } + req := rClient.Post().AbsPath(path).Body(data).SetHeader("Content-Type", "application/json").SetHeader("Accept", "application/json").Param("dryRun", "All").Param("fieldValidation", "Strict").Param("fieldManager", "meshery") res := req.Do(context.Background()) - if res.Error() != nil { - err = res.Error() - return - } + // ignoring the error since this client-go treats failure of dryRun as an error - resp, _ := res.Raw() - e := json.Unmarshal(resp, &st) - if e != nil { - err = fmt.Errorf("cannot serialize Status object from the server: %s", e.Error()) - return - } - if st == nil || st["kind"] == nil { - err = fmt.Errorf("nil response for dryRun from kubernetes") - } - if st["status"] == "Failure" { //The dryRun returned errors in the form of Status - success = false - return + resp, err := res.Raw() + fmt.Println("\n\n\n", string(resp)) + switch err.(type) { + case *errors.StatusError: + st, success, err = formatDryRunResponse(resp, err) + case *errors.UnexpectedObjectError: + st, success, err = formatDryRunResponse(resp, err) + default: + return } - success = true return } @@ -104,3 +116,21 @@ func createK8sResourceStructure(comp v1alpha1.Component) map[string]interface{} } return component } + +func formatDryRunResponse(resp []byte, err error) (status map[string]interface{}, success bool, meshkiterr error) { + + e := json.Unmarshal(resp, &status) + if e != nil { + meshkiterr = models.ErrMarshal(err, fmt.Sprintf("cannot serialize Status object from the server: %s", e.Error())) + return + } + if status == nil || status["kind"] == nil { + meshkiterr = ErrDryRun(fmt.Errorf("nil response for dryRun from kubernetes"), "") + } + if status["status"] == "Failure" { // The dryRun returns errors in the form of Status + success = false + return + } + success = true + return +} \ No newline at end of file diff --git a/server/models/pattern/patterns/patterns.go b/server/models/pattern/patterns/patterns.go index c1a35a30620..b05f881465f 100644 --- a/server/models/pattern/patterns/patterns.go +++ b/server/models/pattern/patterns/patterns.go @@ -77,7 +77,8 @@ func ProcessOAM(kconfigs []string, oamComps []string, oamConfig string, isDel bo evt := events.NewEvent().FromSystem(*mesheryInstanceID).WithSeverity(events.Alert).WithCategory("event").WithAction("persist").WithDescription("Failed persisting events").FromUser(userUUID).Build() go ec.Publish(userUUID, evt) } - + go ec.Publish(userUUID, event) + continue } var description string @@ -127,6 +128,7 @@ func ProcessOAM(kconfigs []string, oamComps []string, oamConfig string, isDel bo evt := events.NewEvent().FromSystem(*mesheryInstanceID).WithSeverity(events.Alert).WithCategory("event").WithAction("persist").WithDescription("Failed persisting events").FromUser(userUUID).Build() go ec.Publish(userUUID, evt) } + go ec.Publish(userUUID, event) } //All other components will be handled directly by Kubernetes //TODO: Add a Mapper utility function which carries the logic for X hosts can handle Y components under Z circumstances. @@ -151,6 +153,8 @@ func ProcessOAM(kconfigs []string, oamComps []string, oamConfig string, isDel bo evt := events.NewEvent().FromSystem(*mesheryInstanceID).WithSeverity(severity).WithCategory("event").WithAction("persist").WithDescription("Failed persisting events").FromUser(userUUID).Build() go ec.Publish(userUUID, evt) } + + go ec.Publish(userUUID, event) continue } if !isDel { @@ -166,6 +170,7 @@ func ProcessOAM(kconfigs []string, oamComps []string, oamConfig string, isDel bo evt := events.NewEvent().FromSystem(*mesheryInstanceID).WithSeverity(events.Alert).WithCategory("event").WithAction("persist").WithDescription("Failed persisting events").FromUser(userUUID).Build() go ec.Publish(userUUID, evt) } + go ec.Publish(userUUID, event) } }(kcli) } diff --git a/server/models/pattern/stages/svc_provider.go b/server/models/pattern/stages/svc_provider.go index efc05dcb260..5c098684604 100644 --- a/server/models/pattern/stages/svc_provider.go +++ b/server/models/pattern/stages/svc_provider.go @@ -25,6 +25,6 @@ type ServiceActionProvider interface { Provision(CompConfigPair) (string, error) GetRegistry() *meshmodel.RegistryManager Persist(string, core.Service, bool) error - DryRun([]v1alpha1.Component) (map[string]map[string]core.DryRunResponse2, error) + DryRun([]v1alpha1.Component) (map[string]map[string]core.DryRunResponseWrapper, error) Mutate(*core.Pattern) //Uses pre-defined policies/configuration to mutate the pattern } diff --git a/ui/components/DryRun/DryRunComponent.js b/ui/components/DryRun/DryRunComponent.js index 3911ba340a5..fd6febb8aa4 100644 --- a/ui/components/DryRun/DryRunComponent.js +++ b/ui/components/DryRun/DryRunComponent.js @@ -89,7 +89,7 @@ function getFieldPathString(fieldPath) { return ""; } - return fieldPath.split(".").splice(2).join(" > "); + return fieldPath.split(".").splice(2).join("."); } // errors - [{type, fieldPath, message}] @@ -193,7 +193,7 @@ const ExpandableComponentErrors = withStyles(styles)(({ export const dryRunAndFormatErrors = (design,selectedContexts) => { function getErrors(error) { - if (error?.Causes) { + if (error?.Causes && error?.Causes.length > 0) { return error.Causes; } // if causes aren't present use the status