2019-06-20 18:36:40 -05:00
|
|
|
package cli
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/anuvu/zot/errors"
|
|
|
|
"github.com/anuvu/zot/pkg/api"
|
|
|
|
"github.com/anuvu/zot/pkg/storage"
|
2021-05-13 13:59:12 -05:00
|
|
|
"github.com/fsnotify/fsnotify"
|
2019-06-20 18:36:40 -05:00
|
|
|
"github.com/mitchellh/mapstructure"
|
2021-05-21 15:47:28 -05:00
|
|
|
distspec "github.com/opencontainers/distribution-spec/specs-go"
|
2019-06-20 18:36:40 -05:00
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
)
|
|
|
|
|
|
|
|
// metadataConfig reports metadata after parsing, which we use to track
|
2020-05-11 17:13:24 -05:00
|
|
|
// errors.
|
2019-06-20 18:36:40 -05:00
|
|
|
func metadataConfig(md *mapstructure.Metadata) viper.DecoderConfigOption {
|
|
|
|
return func(c *mapstructure.DecoderConfig) {
|
|
|
|
c.Metadata = md
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-14 12:11:01 -05:00
|
|
|
func NewRootCmd() *cobra.Command {
|
2019-06-20 18:36:40 -05:00
|
|
|
showVersion := false
|
|
|
|
config := api.NewConfig()
|
|
|
|
|
2019-10-09 13:50:10 -05:00
|
|
|
// "serve"
|
2019-06-20 18:36:40 -05:00
|
|
|
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 {
|
2021-05-13 13:59:12 -05:00
|
|
|
LoadConfiguration(config, args[0])
|
|
|
|
}
|
|
|
|
c := api.NewController(config)
|
|
|
|
|
|
|
|
// creates a new file watcher
|
|
|
|
watcher, err := fsnotify.NewWatcher()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
defer watcher.Close()
|
2019-06-20 18:36:40 -05:00
|
|
|
|
2021-05-13 13:59:12 -05:00
|
|
|
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 := api.NewConfig()
|
|
|
|
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])
|
2019-06-20 18:36:40 -05:00
|
|
|
panic(err)
|
|
|
|
}
|
2021-05-13 13:59:12 -05:00
|
|
|
<-done
|
|
|
|
}()
|
2019-06-20 18:36:40 -05:00
|
|
|
|
|
|
|
if err := c.Run(); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2021-05-13 13:59:12 -05:00
|
|
|
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 := api.NewConfig()
|
|
|
|
LoadConfiguration(config, args[0])
|
|
|
|
log.Info().Msgf("Config file %s is valid", args[0])
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-10-09 13:50:10 -05:00
|
|
|
// "garbage-collect"
|
2019-06-20 18:36:40 -05:00
|
|
|
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", config).Msg("configuration settings")
|
|
|
|
if config.Storage.RootDirectory != "" {
|
|
|
|
if err := storage.Scrub(config.Storage.RootDirectory, gcDryRun); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
gcCmd.Flags().StringVarP(&config.Storage.RootDirectory, "storage-root-dir", "r", "",
|
|
|
|
"Use specified directory for filestore backing image data")
|
2019-12-13 00:53:18 -05:00
|
|
|
|
2019-06-20 18:36:40 -05:00
|
|
|
_ = 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 {
|
2021-05-21 15:47:28 -05:00
|
|
|
log.Info().Str("distribution-spec", distspec.Version).Str("commit", api.Commit).
|
2021-02-12 19:52:02 -05:00
|
|
|
Str("binary-type", api.BinaryType).Msg("version")
|
2019-06-20 18:36:40 -05:00
|
|
|
}
|
|
|
|
_ = cmd.Usage()
|
2021-05-21 15:47:28 -05:00
|
|
|
cmd.SilenceErrors = false
|
2019-06-20 18:36:40 -05:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
rootCmd.AddCommand(serveCmd)
|
|
|
|
rootCmd.AddCommand(gcCmd)
|
2021-05-13 13:59:12 -05:00
|
|
|
rootCmd.AddCommand(verifyCmd)
|
2020-06-16 20:52:40 -05:00
|
|
|
|
2020-10-14 16:47:20 -05:00
|
|
|
enableCli(rootCmd)
|
2020-06-16 20:52:40 -05:00
|
|
|
|
2019-06-20 18:36:40 -05:00
|
|
|
rootCmd.Flags().BoolVarP(&showVersion, "version", "v", false, "show the version and exit")
|
|
|
|
|
|
|
|
return rootCmd
|
|
|
|
}
|
2021-05-13 13:59:12 -05:00
|
|
|
|
|
|
|
func LoadConfiguration(config *api.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)
|
|
|
|
}
|
|
|
|
|
2021-09-01 04:15:00 -05:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-13 13:59:12 -05:00
|
|
|
err := config.LoadAccessControlConfig()
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(errors.ErrBadConfig).Msg("Unable to unmarshal http.accessControl.key.policies")
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|