github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/cmd/check.go (about)

     1  package cmd
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/url"
     8  	"os"
     9  	"strconv"
    10  
    11  	"github.com/cozy/cozy-stack/client/request"
    12  	"github.com/spf13/cobra"
    13  )
    14  
    15  var flagCheckFSIndexIntegrity bool
    16  var flagCheckFSFilesConsistensy bool
    17  var flagCheckFSFailFast bool
    18  var flagCheckSharingsFast bool
    19  
    20  var checkCmdGroup = &cobra.Command{
    21  	Use:   "check <command>",
    22  	Short: "A set of tools to check that instances are in the expected state.",
    23  	RunE: func(cmd *cobra.Command, args []string) error {
    24  		return cmd.Usage()
    25  	},
    26  }
    27  
    28  var checkFSCmd = &cobra.Command{
    29  	Use:   "fs <domain>",
    30  	Short: "Check a vfs",
    31  	Long: `
    32  This command checks that the files in the VFS are not desynchronized, ie a file
    33  present in CouchDB but not swift/localfs, or present in swift/localfs but not
    34  couchdb.
    35  
    36  There are 2 steps:
    37  
    38  - index integrity checks that there are nothing wrong in the index (CouchDB),
    39    like a file present in a directory that has been deleted
    40  - files consistency checks that the files are the same in the index (CouchDB)
    41    and the storage (Swift or localfs).
    42  
    43  By default, both operations are done, but you can choose one or the other via
    44  the flags.
    45  `,
    46  	RunE: func(cmd *cobra.Command, args []string) error {
    47  		if len(args) == 0 {
    48  			return cmd.Usage()
    49  		}
    50  		return fsck(args[0])
    51  	},
    52  }
    53  
    54  func fsck(domain string) error {
    55  	if flagCheckFSFilesConsistensy && flagCheckFSIndexIntegrity {
    56  		flagCheckFSIndexIntegrity = false
    57  		flagCheckFSFilesConsistensy = false
    58  	}
    59  
    60  	ac := newAdminClient()
    61  	res, err := ac.Req(&request.Options{
    62  		Method: "GET",
    63  		Path:   "/instances/" + url.PathEscape(domain) + "/fsck",
    64  		Queries: url.Values{
    65  			"IndexIntegrity":   {strconv.FormatBool(flagCheckFSIndexIntegrity)},
    66  			"FilesConsistency": {strconv.FormatBool(flagCheckFSFilesConsistensy)},
    67  			"FailFast":         {strconv.FormatBool(flagCheckFSFailFast)},
    68  		},
    69  	})
    70  	if err != nil {
    71  		return err
    72  	}
    73  
    74  	hasLogs := false
    75  	scanner := bufio.NewScanner(res.Body)
    76  	buf := make([]byte, 512*1024) // The default buffer can be too short for some lines
    77  	scanner.Buffer(buf, len(buf))
    78  
    79  	for scanner.Scan() {
    80  		hasLogs = true
    81  		fmt.Println(string(scanner.Bytes()))
    82  	}
    83  	if err := scanner.Err(); err != nil {
    84  		_ = res.Body.Close()
    85  		return err
    86  	}
    87  
    88  	if hasLogs {
    89  		// Needs to handle manually the body close because os.Exit bypass all the
    90  		// defer functions.
    91  		_ = res.Body.Close()
    92  		os.Exit(1)
    93  	}
    94  
    95  	_ = res.Body.Close()
    96  	return nil
    97  }
    98  
    99  var checkTriggers = &cobra.Command{
   100  	Use:   "triggers <domain>",
   101  	Short: "Check the triggers",
   102  	Long: `
   103  This command checks that the instance doesn't have duplicate triggers: several
   104  triggers of the same type, for the same worker, and with the same arguments.
   105  `,
   106  	RunE: func(cmd *cobra.Command, args []string) error {
   107  		if len(args) == 0 {
   108  			return cmd.Usage()
   109  		}
   110  		domain := args[0]
   111  
   112  		ac := newAdminClient()
   113  		res, err := ac.Req(&request.Options{
   114  			Method: "POST",
   115  			Path:   "/instances/" + url.PathEscape(domain) + "/checks/triggers",
   116  		})
   117  		if err != nil {
   118  			return err
   119  		}
   120  
   121  		var result []map[string]interface{}
   122  		err = json.NewDecoder(res.Body).Decode(&result)
   123  		if err != nil {
   124  			return err
   125  		}
   126  
   127  		if len(result) > 0 {
   128  			for _, r := range result {
   129  				j, _ := json.Marshal(r)
   130  				fmt.Fprintf(os.Stdout, "%s\n", j)
   131  			}
   132  			os.Exit(1)
   133  		}
   134  		return nil
   135  	},
   136  }
   137  
   138  var checkSharedCmd = &cobra.Command{
   139  	Use:   "shared <domain>",
   140  	Short: "Check the io.cozy.shared documents",
   141  	Long: `
   142  The io.cozy.shared documents have a tree of revisions. This command will check
   143  that all revisions in this tree are either the root or their parent have a
   144  generation smaller than their generation.
   145  `,
   146  	RunE: func(cmd *cobra.Command, args []string) error {
   147  		if len(args) == 0 {
   148  			return cmd.Usage()
   149  		}
   150  		domain := args[0]
   151  
   152  		ac := newAdminClient()
   153  		res, err := ac.Req(&request.Options{
   154  			Method: "POST",
   155  			Path:   "/instances/" + url.PathEscape(domain) + "/checks/shared",
   156  		})
   157  		if err != nil {
   158  			return err
   159  		}
   160  
   161  		var result []map[string]interface{}
   162  		err = json.NewDecoder(res.Body).Decode(&result)
   163  		if err != nil {
   164  			return err
   165  		}
   166  
   167  		if len(result) > 0 {
   168  			for _, r := range result {
   169  				j, _ := json.Marshal(r)
   170  				fmt.Fprintf(os.Stdout, "%s\n", j)
   171  			}
   172  			os.Exit(1)
   173  		}
   174  		return nil
   175  	},
   176  }
   177  
   178  var checkSharingsCmd = &cobra.Command{
   179  	Use:   "sharings <domain>",
   180  	Short: "Check the io.cozy.sharings documents",
   181  	Long: `
   182  This command checks that the io.cozy.sharings have no inconsistencies. It can
   183  be triggers that are missing on an active sharing, or missing credentials for
   184  an active member.
   185  
   186  There are 2 steps:
   187  
   188  - setup integrity checks that there are nothing wrong in the configuration like
   189    a missing trigger
   190  - files and folders consistency checks that the shared documents are the same
   191    for all members
   192  
   193  By default, both operations are done, but you can choose to skip the consistency
   194  check via the flags.
   195  `,
   196  	RunE: func(cmd *cobra.Command, args []string) error {
   197  		if len(args) == 0 {
   198  			return cmd.Usage()
   199  		}
   200  		domain := args[0]
   201  
   202  		ac := newAdminClient()
   203  		res, err := ac.Req(&request.Options{
   204  			Method: "POST",
   205  			Path:   "/instances/" + url.PathEscape(domain) + "/checks/sharings",
   206  			Queries: url.Values{
   207  				"SkipFSConsistency": {strconv.FormatBool(flagCheckSharingsFast)},
   208  			},
   209  		})
   210  		if err != nil {
   211  			return err
   212  		}
   213  
   214  		var result []map[string]interface{}
   215  		err = json.NewDecoder(res.Body).Decode(&result)
   216  		if err != nil {
   217  			return err
   218  		}
   219  
   220  		if len(result) > 0 {
   221  			for _, r := range result {
   222  				j, _ := json.Marshal(r)
   223  				fmt.Fprintf(os.Stdout, "%s\n", j)
   224  			}
   225  			os.Exit(1)
   226  		}
   227  		return nil
   228  	},
   229  }
   230  
   231  func init() {
   232  	checkCmdGroup.AddCommand(checkFSCmd)
   233  	checkCmdGroup.AddCommand(checkTriggers)
   234  	checkCmdGroup.AddCommand(checkSharedCmd)
   235  	checkCmdGroup.AddCommand(checkSharingsCmd)
   236  	checkFSCmd.Flags().BoolVar(&flagCheckFSIndexIntegrity, "index-integrity", false, "Check the index integrity only")
   237  	checkFSCmd.Flags().BoolVar(&flagCheckFSFilesConsistensy, "files-consistency", false, "Check the files consistency only (between CouchDB and Swift)")
   238  	checkFSCmd.Flags().BoolVar(&flagCheckFSFailFast, "fail-fast", false, "Stop the FSCK on the first error")
   239  	checkSharingsCmd.Flags().BoolVar(&flagCheckSharingsFast, "fast", false, "Skip the sharings FS consistency check")
   240  
   241  	RootCmd.AddCommand(checkCmdGroup)
   242  }