0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00
zot/pkg/cli/root.go
Andreea-Lupu c61c3836db implement scrub to check manifest/blob integrity
Signed-off-by: Andreea-Lupu <andreealupu1470@yahoo.com>
2021-12-09 11:18:09 -08:00

287 lines
8.1 KiB
Go

package cli
import (
"fmt"
"net/http"
glob "github.com/bmatcuk/doublestar/v4"
"github.com/fsnotify/fsnotify"
"github.com/mitchellh/mapstructure"
distspec "github.com/opencontainers/distribution-spec/specs-go"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/api"
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/storage"
)
// metadataConfig reports metadata after parsing, which we use to track
// errors.
func metadataConfig(md *mapstructure.Metadata) viper.DecoderConfigOption {
return func(c *mapstructure.DecoderConfig) {
c.Metadata = md
}
}
func NewRootCmd() *cobra.Command {
showVersion := false
conf := config.New()
// "serve"
serveCmd := &cobra.Command{
Use: "serve <config>",
Aliases: []string{"serve"},
Short: "`serve` stores and distributes OCI images",
Long: "`serve` stores and distributes OCI images",
Run: func(cmd *cobra.Command, args []string) {
if len(args) > 0 {
LoadConfiguration(conf, args[0])
}
c := api.NewController(conf)
// creates a new file watcher
watcher, err := fsnotify.NewWatcher()
if err != nil {
panic(err)
}
defer watcher.Close()
done := make(chan bool)
// run watcher
go func() {
go func() {
for {
select {
// watch for events
case event := <-watcher.Events:
if event.Op == fsnotify.Write {
log.Info().Msg("config file changed, trying to reload accessControl config")
newConfig := config.New()
LoadConfiguration(newConfig, args[0])
c.Config.AccessControl = newConfig.AccessControl
}
// watch for errors
case err := <-watcher.Errors:
log.Error().Err(err).Msgf("FsNotify error while watching config %s", args[0])
panic(err)
}
}
}()
if err := watcher.Add(args[0]); err != nil {
log.Error().Err(err).Msgf("error adding config file %s to FsNotify watcher", args[0])
panic(err)
}
<-done
}()
if err := c.Run(); err != nil {
panic(err)
}
},
}
// "scrub"
scrubCmd := &cobra.Command{
Use: "scrub <config>",
Aliases: []string{"scrub"},
Short: "`scrub` checks manifest/blob integrity",
Long: "`scrub` checks manifest/blob integrity",
Run: func(cmd *cobra.Command, args []string) {
configuration := config.New()
if len(args) > 0 {
LoadConfiguration(configuration, args[0])
} else {
if err := cmd.Usage(); err != nil {
panic(err)
}
return
}
// checking if the server is already running
response, err := http.Get(fmt.Sprintf("http://%s:%s/v2", configuration.HTTP.Address, configuration.HTTP.Port))
if err == nil {
response.Body.Close()
log.Info().Msg("The server is running, in order to perform the scrub command the server should be shut down")
panic("Error: server is running")
} else {
// server is down
c := api.NewController(configuration)
if err := c.InitImageStore(); err != nil {
panic(err)
}
result, err := c.StoreController.CheckAllBlobsIntegrity()
if err != nil {
panic(err)
}
result.PrintScrubResults(cmd.OutOrStdout())
}
},
}
verifyCmd := &cobra.Command{
Use: "verify <config>",
Aliases: []string{"verify"},
Short: "`verify` validates a zot config file",
Long: "`verify` validates a zot config file",
Run: func(cmd *cobra.Command, args []string) {
if len(args) > 0 {
config := config.New()
LoadConfiguration(config, args[0])
log.Info().Msgf("Config file %s is valid", args[0])
}
},
}
// "garbage-collect"
gcDelUntagged := false
gcDryRun := false
gcCmd := &cobra.Command{
Use: "garbage-collect <config>",
Aliases: []string{"gc"},
Short: "`garbage-collect` deletes layers not referenced by any manifests",
Long: "`garbage-collect` deletes layers not referenced by any manifests",
Run: func(cmd *cobra.Command, args []string) {
log.Info().Interface("values", conf).Msg("configuration settings")
if conf.Storage.RootDirectory != "" {
if err := storage.Scrub(conf.Storage.RootDirectory, gcDryRun); err != nil {
panic(err)
}
}
},
}
gcCmd.Flags().StringVarP(&conf.Storage.RootDirectory, "storage-root-dir", "r", "",
"Use specified directory for filestore backing image data")
_ = gcCmd.MarkFlagRequired("storage-root-dir")
gcCmd.Flags().BoolVarP(&gcDelUntagged, "delete-untagged", "m", false,
"delete manifests that are not currently referenced via tag")
gcCmd.Flags().BoolVarP(&gcDryRun, "dry-run", "d", false,
"do everything except remove the blobs")
rootCmd := &cobra.Command{
Use: "zot",
Short: "`zot`",
Long: "`zot`",
Run: func(cmd *cobra.Command, args []string) {
if showVersion {
log.Info().Str("distribution-spec", distspec.Version).Str("commit", config.Commit).
Str("binary-type", config.BinaryType).Str("go version", config.GoVersion).Msg("version")
}
_ = cmd.Usage()
cmd.SilenceErrors = false
},
}
rootCmd.AddCommand(serveCmd)
rootCmd.AddCommand(scrubCmd)
rootCmd.AddCommand(gcCmd)
rootCmd.AddCommand(verifyCmd)
enableCli(rootCmd)
rootCmd.Flags().BoolVarP(&showVersion, "version", "v", false, "show the version and exit")
return rootCmd
}
func LoadConfiguration(config *config.Config, configPath string) {
viper.SetConfigFile(configPath)
if err := viper.ReadInConfig(); err != nil {
log.Error().Err(err).Msg("error while reading configuration")
panic(err)
}
md := &mapstructure.Metadata{}
if err := viper.Unmarshal(&config, metadataConfig(md)); err != nil {
log.Error().Err(err).Msg("error while unmarshalling new config")
panic(err)
}
if len(md.Keys) == 0 || len(md.Unused) > 0 {
log.Error().Err(errors.ErrBadConfig).Msg("bad configuration, retry writing it")
panic(errors.ErrBadConfig)
}
// check authorization config, it should have basic auth enabled or ldap
if config.HTTP.RawAccessControl != nil {
if config.HTTP.Auth == nil || (config.HTTP.Auth.HTPasswd.Path == "" && config.HTTP.Auth.LDAP == nil) {
log.Error().Err(errors.ErrBadConfig).
Msg("access control config requires httpasswd or ldap authentication to be enabled")
panic(errors.ErrBadConfig)
}
}
if len(config.Storage.StorageDriver) != 0 {
// enforce s3 driver in case of using storage driver
if config.Storage.StorageDriver["name"] != storage.S3StorageDriverName {
log.Error().Err(errors.ErrBadConfig).Msgf("unsupported storage driver: %s", config.Storage.StorageDriver["name"])
panic(errors.ErrBadConfig)
}
// enforce filesystem storage in case sync feature is enabled
if config.Extensions != nil && config.Extensions.Sync != nil {
log.Error().Err(errors.ErrBadConfig).Msg("sync supports only filesystem storage")
panic(errors.ErrBadConfig)
}
}
// check glob patterns in sync are compilable
if config.Extensions != nil && config.Extensions.Sync != nil {
for _, regCfg := range config.Extensions.Sync.Registries {
if regCfg.Content != nil {
for _, content := range regCfg.Content {
ok := glob.ValidatePattern(content.Prefix)
if !ok {
log.Error().Err(glob.ErrBadPattern).Str("pattern", content.Prefix).Msg("pattern could not be compiled")
panic(errors.ErrBadConfig)
}
}
}
}
}
// enforce s3 driver on subpaths in case of using storage driver
if config.Storage.SubPaths != nil {
if len(config.Storage.SubPaths) > 0 {
subPaths := config.Storage.SubPaths
for route, storageConfig := range subPaths {
if len(storageConfig.StorageDriver) != 0 {
if storageConfig.StorageDriver["name"] != storage.S3StorageDriverName {
log.Error().Err(errors.ErrBadConfig).Str("subpath",
route).Msgf("unsupported storage driver: %s", storageConfig.StorageDriver["name"])
panic(errors.ErrBadConfig)
}
}
}
}
}
err := config.LoadAccessControlConfig()
if err != nil {
log.Error().Err(errors.ErrBadConfig).Msg("unable to unmarshal http.accessControl.key.policies")
panic(err)
}
// defaults
defualtTLSVerify := true
if config.Extensions != nil && config.Extensions.Sync != nil {
for id, regCfg := range config.Extensions.Sync.Registries {
if regCfg.TLSVerify == nil {
config.Extensions.Sync.Registries[id].TLSVerify = &defualtTLSVerify
}
}
}
}