// NewApp takes a configuration and returns a configured app, ready to serve // requests. The app only implements ServeHTTP and can be wrapped in other // handlers accordingly. func NewApp(ctx context.Context, configuration *configuration.Configuration) *App { app := &App{ Config: configuration, Context: ctx, router: v2.RouterWithPrefix(configuration.HTTP.Prefix), isCache: configuration.Proxy.RemoteURL != "", } // Register the handler dispatchers. app.register(v2.RouteNameBase, func(ctx *Context, r *http.Request) http.Handler { return http.HandlerFunc(apiBase) }) app.register(v2.RouteNameManifest, imageManifestDispatcher) app.register(v2.RouteNameCatalog, catalogDispatcher) app.register(v2.RouteNameTags, tagsDispatcher) app.register(v2.RouteNameBlob, blobDispatcher) app.register(v2.RouteNameBlobUpload, blobUploadDispatcher) app.register(v2.RouteNameBlobUploadChunk, blobUploadDispatcher) var err error app.driver, err = factory.Create(configuration.Storage.Type(), configuration.Storage.Parameters()) if err != nil { // TODO(stevvooe): Move the creation of a service into a protected // method, where this is created lazily. Its status can be queried via // a health check. panic(err) } purgeConfig := uploadPurgeDefaultConfig() if mc, ok := configuration.Storage["maintenance"]; ok { if v, ok := mc["uploadpurging"]; ok { purgeConfig, ok = v.(map[interface{}]interface{}) if !ok { panic("uploadpurging config key must contain additional keys") } } if v, ok := mc["readonly"]; ok { readOnly, ok := v.(map[interface{}]interface{}) if !ok { panic("readonly config key must contain additional keys") } if readOnlyEnabled, ok := readOnly["enabled"]; ok { app.readOnly, ok = readOnlyEnabled.(bool) if !ok { panic("readonly's enabled config key must have a boolean value") } } } } startUploadPurger(app, app.driver, ctxu.GetLogger(app), purgeConfig) app.driver, err = applyStorageMiddleware(app.driver, configuration.Middleware["storage"]) if err != nil { panic(err) } app.configureSecret(configuration) app.configureEvents(configuration) app.configureRedis(configuration) app.configureLogHook(configuration) // Generate an ephemeral key to be used for signing converted manifests // for clients that don't support schema2. app.trustKey, err = libtrust.GenerateECP256PrivateKey() if err != nil { panic(err) } if configuration.HTTP.Host != "" { u, err := url.Parse(configuration.HTTP.Host) if err != nil { panic(fmt.Sprintf(`could not parse http "host" parameter: %v`, err)) } app.httpHost = *u } options := []storage.RegistryOption{} if app.isCache { options = append(options, storage.DisableDigestResumption) } // configure deletion if d, ok := configuration.Storage["delete"]; ok { e, ok := d["enabled"] if ok { if deleteEnabled, ok := e.(bool); ok && deleteEnabled { options = append(options, storage.EnableDelete) } } } // configure redirects var redirectDisabled bool if redirectConfig, ok := configuration.Storage["redirect"]; ok { v := redirectConfig["disable"] switch v := v.(type) { case bool: redirectDisabled = v default: panic(fmt.Sprintf("invalid type for redirect config: %#v", redirectConfig)) } } if redirectDisabled { ctxu.GetLogger(app).Infof("backend redirection disabled") } else { options = append(options, storage.EnableRedirect) } // configure storage caches if cc, ok := configuration.Storage["cache"]; ok { v, ok := cc["blobdescriptor"] if !ok { // Backwards compatible: "layerinfo" == "blobdescriptor" v = cc["layerinfo"] } switch v { case "redis": if app.redis == nil { panic("redis configuration required to use for layerinfo cache") } cacheProvider := rediscache.NewRedisBlobDescriptorCacheProvider(app.redis) localOptions := append(options, storage.BlobDescriptorCacheProvider(cacheProvider)) app.registry, err = storage.NewRegistry(app, app.driver, localOptions...) if err != nil { panic("could not create registry: " + err.Error()) } ctxu.GetLogger(app).Infof("using redis blob descriptor cache") case "inmemory": cacheProvider := memorycache.NewInMemoryBlobDescriptorCacheProvider() localOptions := append(options, storage.BlobDescriptorCacheProvider(cacheProvider)) app.registry, err = storage.NewRegistry(app, app.driver, localOptions...) if err != nil { panic("could not create registry: " + err.Error()) } ctxu.GetLogger(app).Infof("using inmemory blob descriptor cache") default: if v != "" { ctxu.GetLogger(app).Warnf("unknown cache type %q, caching disabled", configuration.Storage["cache"]) } } } if app.registry == nil { // configure the registry if no cache section is available. app.registry, err = storage.NewRegistry(app.Context, app.driver, options...) if err != nil { panic("could not create registry: " + err.Error()) } } app.registry, err = applyRegistryMiddleware(app.Context, app.registry, configuration.Middleware["registry"]) if err != nil { panic(err) } authType := configuration.Auth.Type() if authType != "" { accessController, err := auth.GetAccessController(configuration.Auth.Type(), configuration.Auth.Parameters()) if err != nil { panic(fmt.Sprintf("unable to configure authorization (%s): %v", authType, err)) } app.accessController = accessController ctxu.GetLogger(app).Debugf("configured %q access controller", authType) } // configure as a pull through cache if configuration.Proxy.RemoteURL != "" { app.registry, err = proxy.NewRegistryPullThroughCache(ctx, app.registry, app.driver, configuration.Proxy) if err != nil { panic(err.Error()) } app.isCache = true ctxu.GetLogger(app).Info("Registry configured as a proxy cache to ", configuration.Proxy.RemoteURL) } return app }
func main() { var configPath, reposPath string flag.StringVar(&configPath, "config", "", "path to a config file") flag.StringVar(&reposPath, "repos", "", "file with a list of repos") flag.Parse() if configPath == "" { fmt.Fprintln(os.Stderr, "must supply a config file with -config") flag.Usage() return } // Parse config file configFile, err := os.Open(configPath) if err != nil { panic(fmt.Sprintf("error opening config file: %v", err)) } defer configFile.Close() config, err := configuration.Parse(configFile) if err != nil { panic(fmt.Sprintf("error parsing config file: %v", err)) } ctx := context.Background() driver, err := factory.Create(config.Storage.Type(), config.Storage.Parameters()) if err != nil { panic(fmt.Sprintf("error creating storage driver: %v", err)) } registry, _ := storage.NewRegistry(ctx, driver, storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider())) var repos []string if reposPath != "" { reposFile, err := os.Open(reposPath) if err != nil { panic(fmt.Sprintf("could not open repos file: %v", err)) } scanner := bufio.NewScanner(reposFile) for scanner.Scan() { repoName := scanner.Text() if len(repoName) > 0 { if repoName[0] == '+' { repoName = repoName[1:] } repos = append(repos, repoName) } } } else { repos = make([]string, maxRepos) n, err := registry.Repositories(ctx, repos, "") if err != nil && err != io.EOF { panic(fmt.Sprintf("unexpected error getting repo: %v", err)) } if n == maxRepos { panic("too many repositories") } repos = repos[:n] } var wg sync.WaitGroup repoChan := make(chan string) for i := 0; i < 30; i++ { wg.Add(1) go func() { for repoName := range repoChan { if err := checkRepo(registry, repoName); err != nil { fmt.Fprintln(os.Stderr, err) } } wg.Done() }() } for _, repoName := range repos { repoChan <- repoName } close(repoChan) wg.Wait() }
// NewApp takes a configuration and returns a configured app, ready to serve // requests. The app only implements ServeHTTP and can be wrapped in other // handlers accordingly. func NewApp(ctx context.Context, configuration configuration.Configuration) *App { app := &App{ Config: configuration, Context: ctx, router: v2.RouterWithPrefix(configuration.HTTP.Prefix), isCache: configuration.Proxy.RemoteURL != "", } app.Context = ctxu.WithLogger(app.Context, ctxu.GetLogger(app, "instance.id")) // Register the handler dispatchers. app.register(v2.RouteNameBase, func(ctx *Context, r *http.Request) http.Handler { return http.HandlerFunc(apiBase) }) app.register(v2.RouteNameManifest, imageManifestDispatcher) app.register(v2.RouteNameCatalog, catalogDispatcher) app.register(v2.RouteNameTags, tagsDispatcher) app.register(v2.RouteNameBlob, blobDispatcher) app.register(v2.RouteNameBlobUpload, blobUploadDispatcher) app.register(v2.RouteNameBlobUploadChunk, blobUploadDispatcher) var err error app.driver, err = factory.Create(configuration.Storage.Type(), configuration.Storage.Parameters()) if err != nil { // TODO(stevvooe): Move the creation of a service into a protected // method, where this is created lazily. Its status can be queried via // a health check. panic(err) } purgeConfig := uploadPurgeDefaultConfig() if mc, ok := configuration.Storage["maintenance"]; ok { for k, v := range mc { switch k { case "uploadpurging": purgeConfig = v.(map[interface{}]interface{}) } } } startUploadPurger(app, app.driver, ctxu.GetLogger(app), purgeConfig) app.driver, err = applyStorageMiddleware(app.driver, configuration.Middleware["storage"]) if err != nil { panic(err) } app.configureSecret(&configuration) app.configureEvents(&configuration) app.configureRedis(&configuration) app.configureLogHook(&configuration) // configure deletion var deleteEnabled bool if d, ok := configuration.Storage["delete"]; ok { e, ok := d["enabled"] if ok { if deleteEnabled, ok = e.(bool); !ok { deleteEnabled = false } } } // configure redirects var redirectDisabled bool if redirectConfig, ok := configuration.Storage["redirect"]; ok { v := redirectConfig["disable"] switch v := v.(type) { case bool: redirectDisabled = v default: panic(fmt.Sprintf("invalid type for redirect config: %#v", redirectConfig)) } if redirectDisabled { ctxu.GetLogger(app).Infof("backend redirection disabled") } } // configure storage caches if cc, ok := configuration.Storage["cache"]; ok { v, ok := cc["blobdescriptor"] if !ok { // Backwards compatible: "layerinfo" == "blobdescriptor" v = cc["layerinfo"] } switch v { case "redis": if app.redis == nil { panic("redis configuration required to use for layerinfo cache") } app.registry = storage.NewRegistryWithDriver(app, app.driver, rediscache.NewRedisBlobDescriptorCacheProvider(app.redis), deleteEnabled, !redirectDisabled, app.isCache) ctxu.GetLogger(app).Infof("using redis blob descriptor cache") case "inmemory": app.registry = storage.NewRegistryWithDriver(app, app.driver, memorycache.NewInMemoryBlobDescriptorCacheProvider(), deleteEnabled, !redirectDisabled, app.isCache) ctxu.GetLogger(app).Infof("using inmemory blob descriptor cache") default: if v != "" { ctxu.GetLogger(app).Warnf("unknown cache type %q, caching disabled", configuration.Storage["cache"]) } } } if app.registry == nil { // configure the registry if no cache section is available. app.registry = storage.NewRegistryWithDriver(app.Context, app.driver, nil, deleteEnabled, !redirectDisabled, app.isCache) } app.registry, err = applyRegistryMiddleware(app.Context, app.registry, configuration.Middleware["registry"]) if err != nil { panic(err) } authType := configuration.Auth.Type() if authType != "" { accessController, err := auth.GetAccessController(configuration.Auth.Type(), configuration.Auth.Parameters()) if err != nil { panic(fmt.Sprintf("unable to configure authorization (%s): %v", authType, err)) } app.accessController = accessController ctxu.GetLogger(app).Debugf("configured %q access controller", authType) } // configure as a pull through cache if configuration.Proxy.RemoteURL != "" { app.registry, err = proxy.NewRegistryPullThroughCache(ctx, app.registry, app.driver, configuration.Proxy) if err != nil { panic(err.Error()) } app.isCache = true ctxu.GetLogger(app).Info("Registry configured as a proxy cache to ", configuration.Proxy.RemoteURL) } return app }
func TestBuild(t *testing.T) { config := &Config{} env := sys.NewFakeEnv() fs := sys.NewFakeFS() // NOTE(bacongobbler): there's a little easter egg here... ;) sha := "0462cef5812ce31fe12f25596ff68dc614c708af" tmpDir, err := ioutil.TempDir("", "tmpdir") if err != nil { t.Fatalf("error creating temp directory (%s)", err) } defer func() { if err := os.RemoveAll(tmpDir); err != nil { t.Fatalf("failed to remove tmpdir (%s)", err) } }() config.GitHome = tmpDir storageDriver, err := factory.Create("inmemory", nil) if err != nil { t.Fatal(err) } if err := build(config, storageDriver, nil, fs, env, "foo", sha); err == nil { t.Error("expected running build() without setting config.DockerBuilderImagePullPolicy to fail") } config.DockerBuilderImagePullPolicy = "Always" if err := build(config, storageDriver, nil, fs, env, "foo", sha); err == nil { t.Error("expected running build() without setting config.SlugBuilderImagePullPolicy to fail") } config.SlugBuilderImagePullPolicy = "Always" err = build(config, storageDriver, nil, fs, env, "foo", "abc123") expected := "git sha abc123 was invalid" if err.Error() != expected { t.Errorf("expected '%s', got '%v'", expected, err.Error()) } if err := build(config, storageDriver, nil, fs, env, "foo", sha); err == nil { t.Error("expected running build() without valid controller client info to fail") } config.ControllerHost = "localhost" config.ControllerPort = "1234" if err := build(config, storageDriver, nil, fs, env, "foo", sha); err == nil { t.Error("expected running build() without a valid builder key to fail") } builderconf.BuilderKeyLocation = filepath.Join(tmpDir, "builder-key") data := []byte("testbuilderkey") if err := ioutil.WriteFile(builderconf.BuilderKeyLocation, data, 0644); err != nil { t.Fatalf("error creating %s (%s)", builderconf.BuilderKeyLocation, err) } if err := build(config, storageDriver, nil, fs, env, "foo", sha); err == nil { t.Error("expected running build() without a valid controller connection to fail") } }
// NewApp takes a configuration and returns a configured app, ready to serve // requests. The app only implements ServeHTTP and can be wrapped in other // handlers accordingly. func NewApp(ctx context.Context, configuration configuration.Configuration) *App { app := &App{ Config: configuration, Context: ctx, router: v2.RouterWithPrefix(configuration.HTTP.Prefix), } app.Context = ctxu.WithLogger(app.Context, ctxu.GetLogger(app, "instance.id")) // Register the handler dispatchers. app.register(v2.RouteNameBase, func(ctx *Context, r *http.Request) http.Handler { return http.HandlerFunc(apiBase) }) app.register(v2.RouteNameManifest, imageManifestDispatcher) app.register(v2.RouteNameTags, tagsDispatcher) app.register(v2.RouteNameBlob, layerDispatcher) app.register(v2.RouteNameBlobUpload, layerUploadDispatcher) app.register(v2.RouteNameBlobUploadChunk, layerUploadDispatcher) var err error app.driver, err = factory.Create(configuration.Storage.Type(), configuration.Storage.Parameters()) if err != nil { // TODO(stevvooe): Move the creation of a service into a protected // method, where this is created lazily. Its status can be queried via // a health check. panic(err) } startUploadPurger(app.driver, ctxu.GetLogger(app)) app.driver, err = applyStorageMiddleware(app.driver, configuration.Middleware["storage"]) if err != nil { panic(err) } app.configureEvents(&configuration) app.configureRedis(&configuration) // configure storage caches if cc, ok := configuration.Storage["cache"]; ok { switch cc["layerinfo"] { case "redis": if app.redis == nil { panic("redis configuration required to use for layerinfo cache") } app.registry = storage.NewRegistryWithDriver(app.driver, cache.NewRedisLayerInfoCache(app.redis)) ctxu.GetLogger(app).Infof("using redis layerinfo cache") case "inmemory": app.registry = storage.NewRegistryWithDriver(app.driver, cache.NewInMemoryLayerInfoCache()) ctxu.GetLogger(app).Infof("using inmemory layerinfo cache") default: if cc["layerinfo"] != "" { ctxu.GetLogger(app).Warnf("unkown cache type %q, caching disabled", configuration.Storage["cache"]) } } } if app.registry == nil { // configure the registry if no cache section is available. app.registry = storage.NewRegistryWithDriver(app.driver, nil) } app.registry, err = applyRegistryMiddleware(app.registry, configuration.Middleware["registry"]) if err != nil { panic(err) } authType := configuration.Auth.Type() if authType != "" { accessController, err := auth.GetAccessController(configuration.Auth.Type(), configuration.Auth.Parameters()) if err != nil { panic(fmt.Sprintf("unable to configure authorization (%s): %v", authType, err)) } app.accessController = accessController } return app }
func main() { if os.Getenv("DEBUG") == "true" { pkglog.DefaultLogger.SetDebug(true) log.Printf("Running in debug mode") } app := cli.NewApp() app.Commands = []cli.Command{ { Name: "server", Aliases: []string{"srv"}, Usage: "Run the git server", Action: func(c *cli.Context) { cnf := new(sshd.Config) if err := conf.EnvConfig(serverConfAppName, cnf); err != nil { pkglog.Err("getting config for %s [%s]", serverConfAppName, err) os.Exit(1) } fs := sys.RealFS() env := sys.RealEnv() pushLock := sshd.NewInMemoryRepositoryLock() circ := sshd.NewCircuit() storageParams, err := conf.GetStorageParams(env) if err != nil { log.Printf("Error getting storage parameters (%s)", err) os.Exit(1) } var storageDriver storagedriver.StorageDriver if cnf.StorageType == "minio" { storageDriver, err = factory.Create("s3", storageParams) } else { storageDriver, err = factory.Create(cnf.StorageType, storageParams) } if err != nil { log.Printf("Error creating storage driver (%s)", err) os.Exit(1) } kubeClient, err := kcl.NewInCluster() if err != nil { log.Printf("Error getting kubernetes client [%s]", err) os.Exit(1) } log.Printf("Starting health check server on port %d", cnf.HealthSrvPort) healthSrvCh := make(chan error) go func() { if err := healthsrv.Start(cnf.HealthSrvPort, kubeClient.Namespaces(), storageDriver, circ); err != nil { healthSrvCh <- err } }() log.Printf("Starting deleted app cleaner") cleanerErrCh := make(chan error) go func() { if err := cleaner.Run(gitHomeDir, kubeClient.Namespaces(), fs, cnf.CleanerPollSleepDuration()); err != nil { cleanerErrCh <- err } }() log.Printf("Starting SSH server on %s:%d", cnf.SSHHostIP, cnf.SSHHostPort) sshCh := make(chan int) go func() { sshCh <- pkg.RunBuilder(cnf.SSHHostIP, cnf.SSHHostPort, gitHomeDir, circ, pushLock) }() select { case err := <-healthSrvCh: log.Printf("Error running health server (%s)", err) os.Exit(1) case i := <-sshCh: log.Printf("Unexpected SSH server stop with code %d", i) os.Exit(i) case err := <-cleanerErrCh: log.Printf("Error running the deleted app cleaner (%s)", err) os.Exit(1) } }, }, { Name: "git-receive", Aliases: []string{"gr"}, Usage: "Run the git-receive hook", Action: func(c *cli.Context) { cnf := new(gitreceive.Config) if err := conf.EnvConfig(gitReceiveConfAppName, cnf); err != nil { log.Printf("Error getting config for %s [%s]", gitReceiveConfAppName, err) os.Exit(1) } cnf.CheckDurations() fs := sys.RealFS() env := sys.RealEnv() storageParams, err := conf.GetStorageParams(env) if err != nil { log.Printf("Error getting storage parameters (%s)", err) os.Exit(1) } var storageDriver storagedriver.StorageDriver if cnf.StorageType == "minio" { storageDriver, err = factory.Create("s3", storageParams) } else { storageDriver, err = factory.Create(cnf.StorageType, storageParams) } if err != nil { log.Printf("Error creating storage driver (%s)", err) os.Exit(1) } if err := gitreceive.Run(cnf, fs, env, storageDriver); err != nil { log.Printf("Error running git receive hook [%s]", err) os.Exit(1) } }, }, } app.Run(os.Args) }
var dryRun bool // GCCmd is the cobra command that corresponds to the garbage-collect subcommand var GCCmd = &cobra.Command{ Use: "garbage-collect <config>", 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) { config, err := resolveConfiguration(args) if err != nil { fmt.Fprintf(os.Stderr, "configuration error: %v\n", err) cmd.Usage() os.Exit(1) } driver, err := factory.Create(config.Storage.Type(), config.Storage.Parameters()) if err != nil { fmt.Fprintf(os.Stderr, "failed to construct %s driver: %v", config.Storage.Type(), err) os.Exit(1) } ctx := context.Background() ctx, err = configureLogging(ctx, config) if err != nil { fmt.Fprintf(os.Stderr, "unable to configure logging with config: %s", err) os.Exit(1) } k, err := libtrust.GenerateECP256PrivateKey() if err != nil { fmt.Fprint(os.Stderr, err)