github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/instances/checks.go (about)

     1  package instances
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"net/http"
     7  	"strconv"
     8  
     9  	"github.com/cozy/cozy-stack/model/instance/lifecycle"
    10  	"github.com/cozy/cozy-stack/model/sharing"
    11  	"github.com/cozy/cozy-stack/model/vfs"
    12  	"github.com/cozy/cozy-stack/pkg/consts"
    13  	"github.com/cozy/cozy-stack/pkg/couchdb"
    14  	"github.com/labstack/echo/v4"
    15  )
    16  
    17  func fsckHandler(c echo.Context) (err error) {
    18  	domain := c.Param("domain")
    19  	i, err := lifecycle.GetInstance(domain)
    20  	if err != nil {
    21  		return wrapError(err)
    22  	}
    23  
    24  	indexIntegrityCheck, _ := strconv.ParseBool(c.QueryParam("IndexIntegrity"))
    25  	filesConsistencyCheck, _ := strconv.ParseBool(c.QueryParam("FilesConsistency"))
    26  	failFast, _ := strconv.ParseBool(c.QueryParam("FailFast"))
    27  
    28  	logCh := make(chan *vfs.FsckLog)
    29  	go func() {
    30  		fs := i.VFS()
    31  		if indexIntegrityCheck {
    32  			err = fs.CheckIndexIntegrity(func(log *vfs.FsckLog) { logCh <- log }, failFast)
    33  		} else if filesConsistencyCheck {
    34  			err = fs.CheckFilesConsistency(func(log *vfs.FsckLog) { logCh <- log }, failFast)
    35  		} else {
    36  			err = fs.Fsck(func(log *vfs.FsckLog) { logCh <- log }, failFast)
    37  		}
    38  		close(logCh)
    39  	}()
    40  
    41  	w := c.Response().Writer
    42  	w.WriteHeader(200)
    43  	encoder := json.NewEncoder(w)
    44  	for log := range logCh {
    45  		// XXX do not serialize to JSON the children and the cozyMetadata, as
    46  		// it can take more than 64ko and scanner will ignore such lines.
    47  		if log.FileDoc != nil {
    48  			log.FileDoc.DirsChildren = nil  // It can be filled on type mismatch
    49  			log.FileDoc.FilesChildren = nil // Idem
    50  			log.FileDoc.FilesChildrenSize = 0
    51  			log.FileDoc.Metadata = nil
    52  		}
    53  		if log.DirDoc != nil {
    54  			log.DirDoc.DirsChildren = nil
    55  			log.DirDoc.FilesChildren = nil
    56  			log.DirDoc.FilesChildrenSize = 0
    57  			log.DirDoc.Metadata = nil
    58  		}
    59  		if log.VersionDoc != nil {
    60  			log.VersionDoc.Metadata = nil
    61  		}
    62  		if errenc := encoder.Encode(log); errenc != nil {
    63  			i.Logger().WithNamespace("fsck").
    64  				Warnf("Cannot encode to JSON: %s (%v)", errenc, log)
    65  		}
    66  		if f, ok := w.(http.Flusher); ok {
    67  			f.Flush()
    68  		}
    69  	}
    70  	if err != nil {
    71  		log := map[string]string{"error": err.Error()}
    72  		if couchdb.IsNotFoundError(err) {
    73  			log = map[string]string{"type": "no_database", "error": err.Error()}
    74  		}
    75  		if errenc := encoder.Encode(log); errenc != nil {
    76  			i.Logger().WithNamespace("fsck").
    77  				Warnf("Cannot encode to JSON: %s (%v)", errenc, log)
    78  		}
    79  		if f, ok := w.(http.Flusher); ok {
    80  			f.Flush()
    81  		}
    82  	}
    83  	return nil
    84  }
    85  
    86  func checkTriggers(c echo.Context) error {
    87  	domain := c.Param("domain")
    88  	inst, err := lifecycle.GetInstance(domain)
    89  	if err != nil {
    90  		return wrapError(err)
    91  	}
    92  
    93  	type TriggerInfo struct {
    94  		TID        string          `json:"_id"`
    95  		Type       string          `json:"type"`
    96  		WorkerType string          `json:"worker"`
    97  		Arguments  string          `json:"arguments"`
    98  		Debounce   string          `json:"debounce"`
    99  		Message    json.RawMessage `json:"message"`
   100  	}
   101  	var triggers []*TriggerInfo
   102  	err = couchdb.ForeachDocs(inst, consts.Triggers, func(_ string, data json.RawMessage) error {
   103  		var t *TriggerInfo
   104  		if err := json.Unmarshal(data, &t); err != nil {
   105  			return err
   106  		}
   107  		triggers = append(triggers, t)
   108  		return nil
   109  	})
   110  	if err != nil {
   111  		if couchdb.IsNotFoundError(err) {
   112  			return c.JSON(http.StatusOK, []map[string]interface{}{
   113  				{"type": "no_database", "error": err.Error()},
   114  			})
   115  		}
   116  		return wrapError(err)
   117  	}
   118  
   119  	results := []map[string]interface{}{}
   120  	for i, left := range triggers {
   121  		for j, right := range triggers {
   122  			if i >= j {
   123  				continue
   124  			}
   125  			if left.Type == "@in" {
   126  				// It doesn't make sense to compare @in triggers as they can
   127  				// have scheduled at different times
   128  				continue
   129  			}
   130  			if left.Type != right.Type {
   131  				continue
   132  			}
   133  			if left.WorkerType != right.WorkerType {
   134  				continue
   135  			}
   136  			if left.Arguments != right.Arguments {
   137  				continue
   138  			}
   139  			if left.Debounce != right.Debounce {
   140  				continue
   141  			}
   142  			lHasMessage := left.Message != nil
   143  			rHasMessage := right.Message != nil
   144  			if lHasMessage != rHasMessage {
   145  				continue
   146  			}
   147  			if lHasMessage && rHasMessage {
   148  				if !bytes.Equal(left.Message, right.Message) {
   149  					continue
   150  				}
   151  			}
   152  			results = append(results, map[string]interface{}{
   153  				"type":      "duplicate",
   154  				"_id":       left.TID,
   155  				"other_id":  right.TID,
   156  				"trigger":   left.Type,
   157  				"worker":    left.WorkerType,
   158  				"arguments": left.Arguments,
   159  				"debounce":  left.Debounce,
   160  				"message":   string(left.Message),
   161  			})
   162  			break
   163  		}
   164  	}
   165  
   166  	for _, t := range triggers {
   167  		if t.Type == "@event" && t.WorkerType == "service" {
   168  			var msg map[string]interface{}
   169  			err := json.Unmarshal(t.Message, &msg)
   170  			if err != nil {
   171  				results = append(results, map[string]interface{}{
   172  					"type":    "invalid_message",
   173  					"_id":     t.TID,
   174  					"trigger": t.Type,
   175  					"worker":  t.WorkerType,
   176  					"message": string(t.Message),
   177  				})
   178  				continue
   179  			}
   180  			if name, _ := msg["name"].(string); name == "" {
   181  				results = append(results, map[string]interface{}{
   182  					"type":    "missing_name",
   183  					"_id":     t.TID,
   184  					"trigger": t.Type,
   185  					"worker":  t.WorkerType,
   186  					"message": string(t.Message),
   187  				})
   188  			}
   189  			if slug, _ := msg["slug"].(string); slug == "" {
   190  				results = append(results, map[string]interface{}{
   191  					"type":    "missing_slug",
   192  					"_id":     t.TID,
   193  					"trigger": t.Type,
   194  					"worker":  t.WorkerType,
   195  					"message": string(t.Message),
   196  				})
   197  			}
   198  		}
   199  	}
   200  
   201  	return c.JSON(http.StatusOK, results)
   202  }
   203  
   204  func checkShared(c echo.Context) error {
   205  	domain := c.Param("domain")
   206  	i, err := lifecycle.GetInstance(domain)
   207  	if err != nil {
   208  		return wrapError(err)
   209  	}
   210  
   211  	results, err := sharing.CheckShared(i)
   212  	if err != nil {
   213  		if couchdb.IsNotFoundError(err) {
   214  			return c.JSON(http.StatusOK, []map[string]interface{}{
   215  				{"type": "no_database", "error": err.Error()},
   216  			})
   217  		}
   218  		if _, ok := err.(*json.SyntaxError); ok {
   219  			return c.JSON(http.StatusOK, []map[string]interface{}{
   220  				{"type": "invalid_json", "error": err.Error()},
   221  			})
   222  		}
   223  		return wrapError(err)
   224  	}
   225  	return c.JSON(http.StatusOK, results)
   226  }
   227  
   228  func checkSharings(c echo.Context) error {
   229  	domain := c.Param("domain")
   230  	i, err := lifecycle.GetInstance(domain)
   231  	if err != nil {
   232  		return wrapError(err)
   233  	}
   234  
   235  	skipFSConsistency, _ := strconv.ParseBool(c.QueryParam("SkipFSConsistency"))
   236  
   237  	results, err := sharing.CheckSharings(i, skipFSConsistency)
   238  	if err != nil {
   239  		if couchdb.IsNotFoundError(err) {
   240  			return c.JSON(http.StatusOK, []map[string]interface{}{
   241  				{"type": "no_database", "error": err.Error()},
   242  			})
   243  		}
   244  		return wrapError(err)
   245  	}
   246  	return c.JSON(http.StatusOK, results)
   247  }