Refactor by separating AptlyContext into separate package. #116

This commit is contained in:
Andrey Smirnov
2014-10-06 21:54:15 +04:00
parent 159608cef3
commit 8a787d2c35
29 changed files with 463 additions and 413 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
GOVERSION=$(shell go version | awk '{print $$3;}') GOVERSION=$(shell go version | awk '{print $$3;}')
PACKAGES=database deb files http query s3 utils PACKAGES=context database deb files http query s3 utils
ALL_PACKAGES=aptly cmd console database deb files http query s3 utils ALL_PACKAGES=aptly context cmd console database deb files http query s3 utils
BINPATH=$(abspath ./_vendor/bin) BINPATH=$(abspath ./_vendor/bin)
GOM_ENVIRONMENT=-test GOM_ENVIRONMENT=-test
PYTHON?=python PYTHON?=python
+1
View File
@@ -76,6 +76,7 @@ package environment to new version.`,
makeCmdPublish(), makeCmdPublish(),
makeCmdVersion(), makeCmdVersion(),
makeCmdPackage(), makeCmdPackage(),
makeCmdAPI(),
}, },
} }
+6 -352
View File
@@ -1,313 +1,20 @@
package cmd package cmd
import ( import (
"fmt" ctx "github.com/smira/aptly/context"
"github.com/smira/aptly/aptly"
"github.com/smira/aptly/console"
"github.com/smira/aptly/database"
"github.com/smira/aptly/deb"
"github.com/smira/aptly/files"
"github.com/smira/aptly/http"
"github.com/smira/aptly/s3"
"github.com/smira/aptly/utils"
"github.com/smira/commander"
"github.com/smira/flag" "github.com/smira/flag"
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"strings"
"time"
) )
// AptlyContext is a common context shared by all commands var context *ctx.AptlyContext
type AptlyContext struct {
flags, globalFlags *flag.FlagSet
configLoaded bool
progress aptly.Progress
downloader aptly.Downloader
database database.Storage
packagePool aptly.PackagePool
publishedStorages map[string]aptly.PublishedStorage
collectionFactory *deb.CollectionFactory
dependencyOptions int
architecturesList []string
// Debug features
fileCPUProfile *os.File
fileMemProfile *os.File
fileMemStats *os.File
}
var context *AptlyContext
// Check interface
var _ aptly.PublishedStorageProvider = &AptlyContext{}
// FatalError is type for panicking to abort execution with non-zero
// exit code and print meaningful explanation
type FatalError struct {
ReturnCode int
Message string
}
// Fatal panics and aborts execution with exit code 1
func Fatal(err error) {
returnCode := 1
if err == commander.ErrFlagError || err == commander.ErrCommandError {
returnCode = 2
}
panic(&FatalError{ReturnCode: returnCode, Message: err.Error()})
}
// Config loads and returns current configuration
func (context *AptlyContext) Config() *utils.ConfigStructure {
if !context.configLoaded {
var err error
configLocation := context.globalFlags.Lookup("config").Value.String()
if configLocation != "" {
err = utils.LoadConfig(configLocation, &utils.Config)
if err != nil {
Fatal(err)
}
} else {
configLocations := []string{
filepath.Join(os.Getenv("HOME"), ".aptly.conf"),
"/etc/aptly.conf",
}
for _, configLocation := range configLocations {
err = utils.LoadConfig(configLocation, &utils.Config)
if err == nil {
break
}
if !os.IsNotExist(err) {
Fatal(fmt.Errorf("error loading config file %s: %s", configLocation, err))
}
}
if err != nil {
fmt.Printf("Config file not found, creating default config at %s\n\n", configLocations[0])
utils.SaveConfig(configLocations[0], &utils.Config)
}
}
context.configLoaded = true
}
return &utils.Config
}
// DependencyOptions calculates options related to dependecy handling
func (context *AptlyContext) DependencyOptions() int {
if context.dependencyOptions == -1 {
context.dependencyOptions = 0
if LookupOption(context.Config().DepFollowSuggests, context.globalFlags, "dep-follow-suggests") {
context.dependencyOptions |= deb.DepFollowSuggests
}
if LookupOption(context.Config().DepFollowRecommends, context.globalFlags, "dep-follow-recommends") {
context.dependencyOptions |= deb.DepFollowRecommends
}
if LookupOption(context.Config().DepFollowAllVariants, context.globalFlags, "dep-follow-all-variants") {
context.dependencyOptions |= deb.DepFollowAllVariants
}
if LookupOption(context.Config().DepFollowSource, context.globalFlags, "dep-follow-source") {
context.dependencyOptions |= deb.DepFollowSource
}
}
return context.dependencyOptions
}
// ArchitecturesList returns list of architectures fixed via command line or config
func (context *AptlyContext) ArchitecturesList() []string {
if context.architecturesList == nil {
context.architecturesList = context.Config().Architectures
optionArchitectures := context.globalFlags.Lookup("architectures").Value.String()
if optionArchitectures != "" {
context.architecturesList = strings.Split(optionArchitectures, ",")
}
}
return context.architecturesList
}
// Progress creates or returns Progress object
func (context *AptlyContext) Progress() aptly.Progress {
if context.progress == nil {
context.progress = console.NewProgress()
context.progress.Start()
}
return context.progress
}
// Downloader returns instance of current downloader
func (context *AptlyContext) Downloader() aptly.Downloader {
if context.downloader == nil {
var downloadLimit int64
limitFlag := context.flags.Lookup("download-limit")
if limitFlag != nil {
downloadLimit = limitFlag.Value.Get().(int64)
}
if downloadLimit == 0 {
downloadLimit = context.Config().DownloadLimit
}
context.downloader = http.NewDownloader(context.Config().DownloadConcurrency,
downloadLimit*1024, context.Progress())
}
return context.downloader
}
// DBPath builds path to database
func (context *AptlyContext) DBPath() string {
return filepath.Join(context.Config().RootDir, "db")
}
// Database opens and returns current instance of database
func (context *AptlyContext) Database() (database.Storage, error) {
if context.database == nil {
var err error
context.database, err = database.OpenDB(context.DBPath())
if err != nil {
return nil, fmt.Errorf("can't open database: %s", err)
}
}
return context.database, nil
}
// CloseDatabase closes the db temporarily
func (context *AptlyContext) CloseDatabase() error {
if context.database == nil {
return nil
}
return context.database.Close()
}
// ReOpenDatabase reopens the db after close
func (context *AptlyContext) ReOpenDatabase() error {
if context.database == nil {
return nil
}
const MaxTries = 10
const Delay = 10 * time.Second
for try := 0; try < MaxTries; try++ {
err := context.database.ReOpen()
if err == nil || strings.Index(err.Error(), "resource temporarily unavailable") == -1 {
return err
}
context.Progress().Printf("Unable to reopen database, sleeping %s\n", Delay)
<-time.After(Delay)
}
return fmt.Errorf("unable to reopen the DB, maximum number of retries reached")
}
// CollectionFactory builds factory producing all kinds of collections
func (context *AptlyContext) CollectionFactory() *deb.CollectionFactory {
if context.collectionFactory == nil {
db, err := context.Database()
if err != nil {
Fatal(err)
}
context.collectionFactory = deb.NewCollectionFactory(db)
}
return context.collectionFactory
}
// PackagePool returns instance of PackagePool
func (context *AptlyContext) PackagePool() aptly.PackagePool {
if context.packagePool == nil {
context.packagePool = files.NewPackagePool(context.Config().RootDir)
}
return context.packagePool
}
// GetPublishedStorage returns instance of PublishedStorage
func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedStorage {
publishedStorage, ok := context.publishedStorages[name]
if !ok {
if name == "" {
publishedStorage = files.NewPublishedStorage(context.Config().RootDir)
} else if strings.HasPrefix(name, "s3:") {
params, ok := context.Config().S3PublishRoots[name[3:]]
if !ok {
Fatal(fmt.Errorf("published S3 storage %v not configured", name[3:]))
}
var err error
publishedStorage, err = s3.NewPublishedStorage(params.AccessKeyID, params.SecretAccessKey,
params.Region, params.Bucket, params.ACL, params.Prefix, params.StorageClass,
params.EncryptionMethod, params.PlusWorkaround)
if err != nil {
Fatal(err)
}
} else {
Fatal(fmt.Errorf("unknown published storage format: %v", name))
}
context.publishedStorages[name] = publishedStorage
}
return publishedStorage
}
// UpdateFlags sets internal copy of flags in the context
func (context *AptlyContext) UpdateFlags(flags *flag.FlagSet) {
context.flags = flags
}
// ShutdownContext shuts context down // ShutdownContext shuts context down
func ShutdownContext() { func ShutdownContext() {
if aptly.EnableDebug { context.Shutdown()
if context.fileMemProfile != nil {
pprof.WriteHeapProfile(context.fileMemProfile)
context.fileMemProfile.Close()
context.fileMemProfile = nil
}
if context.fileCPUProfile != nil {
pprof.StopCPUProfile()
context.fileCPUProfile.Close()
context.fileCPUProfile = nil
}
if context.fileMemProfile != nil {
context.fileMemProfile.Close()
context.fileMemProfile = nil
}
}
if context.database != nil {
context.database.Close()
context.database = nil
}
if context.downloader != nil {
context.downloader.Abort()
context.downloader = nil
}
if context.progress != nil {
context.progress.Shutdown()
context.progress = nil
}
} }
// CleanupContext does partial shutdown of context // CleanupContext does partial shutdown of context
func CleanupContext() { func CleanupContext() {
if context.downloader != nil { context.Cleanup()
context.downloader.Shutdown()
context.downloader = nil
}
if context.progress != nil {
context.progress.Shutdown()
context.progress = nil
}
} }
// InitContext initializes context with default settings // InitContext initializes context with default settings
@@ -318,60 +25,7 @@ func InitContext(flags *flag.FlagSet) error {
panic("context already initialized") panic("context already initialized")
} }
context = &AptlyContext{ context, err = ctx.NewContext(flags)
flags: flags,
globalFlags: flags,
dependencyOptions: -1,
publishedStorages: map[string]aptly.PublishedStorage{},
}
if aptly.EnableDebug { return err
cpuprofile := flags.Lookup("cpuprofile").Value.String()
if cpuprofile != "" {
context.fileCPUProfile, err = os.Create(cpuprofile)
if err != nil {
return err
}
pprof.StartCPUProfile(context.fileCPUProfile)
}
memprofile := flags.Lookup("memprofile").Value.String()
if memprofile != "" {
context.fileMemProfile, err = os.Create(memprofile)
if err != nil {
return err
}
}
memstats := flags.Lookup("memstats").Value.String()
if memstats != "" {
interval := flags.Lookup("meminterval").Value.Get().(time.Duration)
context.fileMemStats, err = os.Create(memstats)
if err != nil {
return err
}
context.fileMemStats.WriteString("# Time\tHeapSys\tHeapAlloc\tHeapIdle\tHeapReleased\n")
go func() {
var stats runtime.MemStats
start := time.Now().UnixNano()
for {
runtime.ReadMemStats(&stats)
if context.fileMemStats != nil {
context.fileMemStats.WriteString(fmt.Sprintf("%d\t%d\t%d\t%d\t%d\n",
(time.Now().UnixNano()-start)/1000000, stats.HeapSys, stats.HeapAlloc, stats.HeapIdle, stats.HeapReleased))
time.Sleep(interval)
} else {
break
}
}
}()
}
}
return nil
} }
+5 -5
View File
@@ -16,8 +16,8 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
return commander.ErrCommandError return commander.ErrCommandError
} }
downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.flags, "with-sources") downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.Flags(), "with-sources")
downloadUdebs := context.flags.Lookup("with-udebs").Value.Get().(bool) downloadUdebs := context.Flags().Lookup("with-udebs").Value.Get().(bool)
var ( var (
mirrorName, archiveURL, distribution string mirrorName, archiveURL, distribution string
@@ -40,8 +40,8 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to create mirror: %s", err) return fmt.Errorf("unable to create mirror: %s", err)
} }
repo.Filter = context.flags.Lookup("filter").Value.String() repo.Filter = context.Flags().Lookup("filter").Value.String()
repo.FilterWithDeps = context.flags.Lookup("filter-with-deps").Value.Get().(bool) repo.FilterWithDeps = context.Flags().Lookup("filter-with-deps").Value.Get().(bool)
if repo.Filter != "" { if repo.Filter != "" {
_, err = query.Parse(repo.Filter) _, err = query.Parse(repo.Filter)
@@ -50,7 +50,7 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
} }
} }
verifier, err := getVerifier(context.flags) verifier, err := getVerifier(context.Flags())
if err != nil { if err != nil {
return fmt.Errorf("unable to initialize GPG verifier: %s", err) return fmt.Errorf("unable to initialize GPG verifier: %s", err)
} }
+1 -1
View File
@@ -25,7 +25,7 @@ func aptlyMirrorDrop(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to drop: %s", err) return fmt.Errorf("unable to drop: %s", err)
} }
force := context.flags.Lookup("force").Value.Get().(bool) force := context.Flags().Lookup("force").Value.Get().(bool)
if !force { if !force {
snapshots := context.CollectionFactory().SnapshotCollection().ByRemoteRepoSource(repo) snapshots := context.CollectionFactory().SnapshotCollection().ByRemoteRepoSource(repo)
+2 -2
View File
@@ -24,7 +24,7 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to edit: %s", err) return fmt.Errorf("unable to edit: %s", err)
} }
context.flags.Visit(func(flag *flag.Flag) { context.Flags().Visit(func(flag *flag.Flag) {
switch flag.Name { switch flag.Name {
case "filter": case "filter":
repo.Filter = flag.Value.String() repo.Filter = flag.Value.String()
@@ -48,7 +48,7 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
} }
} }
if context.globalFlags.Lookup("architectures").Value.String() != "" { if context.GlobalFlags().Lookup("architectures").Value.String() != "" {
repo.Architectures = context.ArchitecturesList() repo.Architectures = context.ArchitecturesList()
err = repo.Fetch(context.Downloader(), nil) err = repo.Fetch(context.Downloader(), nil)
+1 -1
View File
@@ -66,7 +66,7 @@ func aptlyMirrorShow(cmd *commander.Command, args []string) error {
fmt.Printf("%s: %s\n", k, repo.Meta[k]) fmt.Printf("%s: %s\n", k, repo.Meta[k])
} }
withPackages := context.flags.Lookup("with-packages").Value.Get().(bool) withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool)
if withPackages { if withPackages {
if repo.LastDownloadDate.IsZero() { if repo.LastDownloadDate.IsZero() {
fmt.Printf("Unable to show package list, mirror hasn't been downloaded yet.\n") fmt.Printf("Unable to show package list, mirror hasn't been downloaded yet.\n")
+3 -3
View File
@@ -31,7 +31,7 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to update: %s", err) return fmt.Errorf("unable to update: %s", err)
} }
force := context.flags.Lookup("force").Value.Get().(bool) force := context.Flags().Lookup("force").Value.Get().(bool)
if !force { if !force {
err = repo.CheckLock() err = repo.CheckLock()
if err != nil { if err != nil {
@@ -39,9 +39,9 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
} }
} }
ignoreMismatch := context.flags.Lookup("ignore-checksums").Value.Get().(bool) ignoreMismatch := context.Flags().Lookup("ignore-checksums").Value.Get().(bool)
verifier, err := getVerifier(context.flags) verifier, err := getVerifier(context.Flags())
if err != nil { if err != nil {
return fmt.Errorf("unable to initialize GPG verifier: %s", err) return fmt.Errorf("unable to initialize GPG verifier: %s", err)
} }
+2 -2
View File
@@ -72,8 +72,8 @@ func aptlyPackageShow(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to show: %s", err) return fmt.Errorf("unable to show: %s", err)
} }
withFiles := context.flags.Lookup("with-files").Value.Get().(bool) withFiles := context.Flags().Lookup("with-files").Value.Get().(bool)
withReferences := context.flags.Lookup("with-references").Value.Get().(bool) withReferences := context.Flags().Lookup("with-references").Value.Get().(bool)
w := bufio.NewWriter(os.Stdout) w := bufio.NewWriter(os.Stdout)
+4 -4
View File
@@ -13,7 +13,7 @@ import (
func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error { func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
var err error var err error
components := strings.Split(context.flags.Lookup("component").Value.String(), ",") components := strings.Split(context.Flags().Lookup("component").Value.String(), ",")
if len(args) < len(components) || len(args) > len(components)+1 { if len(args) < len(components) || len(args) > len(components)+1 {
cmd.Usage() cmd.Usage()
@@ -110,7 +110,7 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
panic("unknown command") panic("unknown command")
} }
distribution := context.flags.Lookup("distribution").Value.String() distribution := context.Flags().Lookup("distribution").Value.String()
published, err := deb.NewPublishedRepo(storage, prefix, distribution, context.ArchitecturesList(), components, sources, context.CollectionFactory()) published, err := deb.NewPublishedRepo(storage, prefix, distribution, context.ArchitecturesList(), components, sources, context.CollectionFactory())
if err != nil { if err != nil {
@@ -125,12 +125,12 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
return fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate) return fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate)
} }
signer, err := getSigner(context.flags) signer, err := getSigner(context.Flags())
if err != nil { if err != nil {
return fmt.Errorf("unable to initialize GPG signer: %s", err) return fmt.Errorf("unable to initialize GPG signer: %s", err)
} }
forceOverwrite := context.flags.Lookup("force-overwrite").Value.Get().(bool) forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
if forceOverwrite { if forceOverwrite {
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " + context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " +
"the same package pool.\n") "the same package pool.\n")
+3 -3
View File
@@ -11,7 +11,7 @@ import (
func aptlyPublishSwitch(cmd *commander.Command, args []string) error { func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
var err error var err error
components := strings.Split(context.flags.Lookup("component").Value.String(), ",") components := strings.Split(context.Flags().Lookup("component").Value.String(), ",")
if len(args) < len(components)+1 || len(args) > len(components)+2 { if len(args) < len(components)+1 || len(args) > len(components)+2 {
cmd.Usage() cmd.Usage()
@@ -74,12 +74,12 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
published.UpdateSnapshot(component, snapshot) published.UpdateSnapshot(component, snapshot)
} }
signer, err := getSigner(context.flags) signer, err := getSigner(context.Flags())
if err != nil { if err != nil {
return fmt.Errorf("unable to initialize GPG signer: %s", err) return fmt.Errorf("unable to initialize GPG signer: %s", err)
} }
forceOverwrite := context.flags.Lookup("force-overwrite").Value.Get().(bool) forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
if forceOverwrite { if forceOverwrite {
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " + context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " +
"the same package pool.\n") "the same package pool.\n")
+2 -2
View File
@@ -43,12 +43,12 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
published.UpdateLocalRepo(component) published.UpdateLocalRepo(component)
} }
signer, err := getSigner(context.flags) signer, err := getSigner(context.Flags())
if err != nil { if err != nil {
return fmt.Errorf("unable to initialize GPG signer: %s", err) return fmt.Errorf("unable to initialize GPG signer: %s", err)
} }
forceOverwrite := context.flags.Lookup("force-overwrite").Value.Get().(bool) forceOverwrite := context.Flags().Lookup("force-overwrite").Value.Get().(bool)
if forceOverwrite { if forceOverwrite {
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " + context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing " +
"the same package pool.\n") "the same package pool.\n")
+2 -2
View File
@@ -40,7 +40,7 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to load packages: %s", err) return fmt.Errorf("unable to load packages: %s", err)
} }
forceReplace := context.flags.Lookup("force-replace").Value.Get().(bool) forceReplace := context.Flags().Lookup("force-replace").Value.Get().(bool)
packageFiles := []string{} packageFiles := []string{}
failedFiles := []string{} failedFiles := []string{}
@@ -194,7 +194,7 @@ func aptlyRepoAdd(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to save: %s", err) return fmt.Errorf("unable to save: %s", err)
} }
if context.flags.Lookup("remove-files").Value.Get().(bool) { if context.Flags().Lookup("remove-files").Value.Get().(bool) {
processedFiles = utils.StrSliceDeduplicate(processedFiles) processedFiles = utils.StrSliceDeduplicate(processedFiles)
for _, file := range processedFiles { for _, file := range processedFiles {
+3 -3
View File
@@ -14,9 +14,9 @@ func aptlyRepoCreate(cmd *commander.Command, args []string) error {
return commander.ErrCommandError return commander.ErrCommandError
} }
repo := deb.NewLocalRepo(args[0], context.flags.Lookup("comment").Value.String()) repo := deb.NewLocalRepo(args[0], context.Flags().Lookup("comment").Value.String())
repo.DefaultDistribution = context.flags.Lookup("distribution").Value.String() repo.DefaultDistribution = context.Flags().Lookup("distribution").Value.String()
repo.DefaultComponent = context.flags.Lookup("component").Value.String() repo.DefaultComponent = context.Flags().Lookup("component").Value.String()
err = context.CollectionFactory().LocalRepoCollection().Add(repo) err = context.CollectionFactory().LocalRepoCollection().Add(repo)
if err != nil { if err != nil {
+1 -1
View File
@@ -34,7 +34,7 @@ func aptlyRepoDrop(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to drop: local repo is published") return fmt.Errorf("unable to drop: local repo is published")
} }
force := context.flags.Lookup("force").Value.Get().(bool) force := context.Flags().Lookup("force").Value.Get().(bool)
if !force { if !force {
snapshots := context.CollectionFactory().SnapshotCollection().ByLocalRepoSource(repo) snapshots := context.CollectionFactory().SnapshotCollection().ByLocalRepoSource(repo)
+6 -6
View File
@@ -23,16 +23,16 @@ func aptlyRepoEdit(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to edit: %s", err) return fmt.Errorf("unable to edit: %s", err)
} }
if context.flags.Lookup("comment").Value.String() != "" { if context.Flags().Lookup("comment").Value.String() != "" {
repo.Comment = context.flags.Lookup("comment").Value.String() repo.Comment = context.Flags().Lookup("comment").Value.String()
} }
if context.flags.Lookup("distribution").Value.String() != "" { if context.Flags().Lookup("distribution").Value.String() != "" {
repo.DefaultDistribution = context.flags.Lookup("distribution").Value.String() repo.DefaultDistribution = context.Flags().Lookup("distribution").Value.String()
} }
if context.flags.Lookup("component").Value.String() != "" { if context.Flags().Lookup("component").Value.String() != "" {
repo.DefaultComponent = context.flags.Lookup("component").Value.String() repo.DefaultComponent = context.Flags().Lookup("component").Value.String()
} }
err = context.CollectionFactory().LocalRepoCollection().Update(repo) err = context.CollectionFactory().LocalRepoCollection().Update(repo)
+2 -2
View File
@@ -87,7 +87,7 @@ func aptlyRepoMoveCopyImport(cmd *commander.Command, args []string) error {
var architecturesList []string var architecturesList []string
withDeps := context.flags.Lookup("with-deps").Value.Get().(bool) withDeps := context.Flags().Lookup("with-deps").Value.Get().(bool)
if withDeps { if withDeps {
dstList.PrepareIndex() dstList.PrepareIndex()
@@ -145,7 +145,7 @@ func aptlyRepoMoveCopyImport(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to %s: %s", command, err) return fmt.Errorf("unable to %s: %s", command, err)
} }
if context.flags.Lookup("dry-run").Value.Get().(bool) { if context.Flags().Lookup("dry-run").Value.Get().(bool) {
context.Progress().Printf("\nChanges not saved, as dry run has been requested.\n") context.Progress().Printf("\nChanges not saved, as dry run has been requested.\n")
} else { } else {
dstRepo.UpdateRefList(deb.NewPackageRefListFromPackageList(dstList)) dstRepo.UpdateRefList(deb.NewPackageRefListFromPackageList(dstList))
+1 -1
View File
@@ -54,7 +54,7 @@ func aptlyRepoRemove(cmd *commander.Command, args []string) error {
return nil return nil
}) })
if context.flags.Lookup("dry-run").Value.Get().(bool) { if context.Flags().Lookup("dry-run").Value.Get().(bool) {
context.Progress().Printf("\nChanges not saved, as dry run has been requested.\n") context.Progress().Printf("\nChanges not saved, as dry run has been requested.\n")
} else { } else {
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list)) repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
+1 -1
View File
@@ -31,7 +31,7 @@ func aptlyRepoShow(cmd *commander.Command, args []string) error {
fmt.Printf("Default Component: %s\n", repo.DefaultComponent) fmt.Printf("Default Component: %s\n", repo.DefaultComponent)
fmt.Printf("Number of packages: %d\n", repo.NumPackages()) fmt.Printf("Number of packages: %d\n", repo.NumPackages())
withPackages := context.flags.Lookup("with-packages").Value.Get().(bool) withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool)
if withPackages { if withPackages {
ListPackagesRefList(repo.RefList()) ListPackagesRefList(repo.RefList())
} }
+5 -4
View File
@@ -2,6 +2,7 @@ package cmd
import ( import (
"fmt" "fmt"
ctx "github.com/smira/aptly/context"
"github.com/smira/commander" "github.com/smira/commander"
) )
@@ -9,7 +10,7 @@ import (
func Run(cmd *commander.Command, cmdArgs []string, initContext bool) (returnCode int) { func Run(cmd *commander.Command, cmdArgs []string, initContext bool) (returnCode int) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
fatal, ok := r.(*FatalError) fatal, ok := r.(*ctx.FatalError)
if !ok { if !ok {
panic(r) panic(r)
} }
@@ -22,13 +23,13 @@ func Run(cmd *commander.Command, cmdArgs []string, initContext bool) (returnCode
flags, args, err := cmd.ParseFlags(cmdArgs) flags, args, err := cmd.ParseFlags(cmdArgs)
if err != nil { if err != nil {
Fatal(err) ctx.Fatal(err)
} }
if initContext { if initContext {
err = InitContext(flags) err = InitContext(flags)
if err != nil { if err != nil {
Fatal(err) ctx.Fatal(err)
} }
defer ShutdownContext() defer ShutdownContext()
} }
@@ -37,7 +38,7 @@ func Run(cmd *commander.Command, cmdArgs []string, initContext bool) (returnCode
err = cmd.Dispatch(args) err = cmd.Dispatch(args)
if err != nil { if err != nil {
Fatal(err) ctx.Fatal(err)
} }
return return
+1 -1
View File
@@ -27,7 +27,7 @@ func aptlyServe(cmd *commander.Command, args []string) error {
return nil return nil
} }
listen := context.flags.Lookup("listen").Value.String() listen := context.Flags().Lookup("listen").Value.String()
listenHost, listenPort, err := net.SplitHostPort(listen) listenHost, listenPort, err := net.SplitHostPort(listen)
+1 -1
View File
@@ -13,7 +13,7 @@ func aptlySnapshotDiff(cmd *commander.Command, args []string) error {
return commander.ErrCommandError return commander.ErrCommandError
} }
onlyMatching := context.flags.Lookup("only-matching").Value.Get().(bool) onlyMatching := context.Flags().Lookup("only-matching").Value.Get().(bool)
// Load <name-a> snapshot // Load <name-a> snapshot
snapshotA, err := context.CollectionFactory().SnapshotCollection().ByName(args[0]) snapshotA, err := context.CollectionFactory().SnapshotCollection().ByName(args[0])
+1 -1
View File
@@ -35,7 +35,7 @@ func aptlySnapshotDrop(cmd *commander.Command, args []string) error {
return fmt.Errorf("unable to drop: snapshot is published") return fmt.Errorf("unable to drop: snapshot is published")
} }
force := context.flags.Lookup("force").Value.Get().(bool) force := context.Flags().Lookup("force").Value.Get().(bool)
if !force { if !force {
snapshots := context.CollectionFactory().SnapshotCollection().BySnapshotSource(snapshot) snapshots := context.CollectionFactory().SnapshotCollection().BySnapshotSource(snapshot)
if len(snapshots) > 0 { if len(snapshots) > 0 {
+1 -1
View File
@@ -17,7 +17,7 @@ func aptlySnapshotFilter(cmd *commander.Command, args []string) error {
return commander.ErrCommandError return commander.ErrCommandError
} }
withDeps := context.flags.Lookup("with-deps").Value.Get().(bool) withDeps := context.Flags().Lookup("with-deps").Value.Get().(bool)
// Load <source> snapshot // Load <source> snapshot
source, err := context.CollectionFactory().SnapshotCollection().ByName(args[0]) source, err := context.CollectionFactory().SnapshotCollection().ByName(args[0])
+6 -6
View File
@@ -28,12 +28,12 @@ func aptlySnapshotMerge(cmd *commander.Command, args []string) error {
} }
} }
latest := context.flags.Lookup("latest").Value.Get().(bool) latest := context.Flags().Lookup("latest").Value.Get().(bool)
noRemove := context.flags.Lookup("no-remove").Value.Get().(bool) noRemove := context.Flags().Lookup("no-remove").Value.Get().(bool)
if noRemove && latest { if noRemove && latest {
return fmt.Errorf("-no-remove and -latest can't be specified together") return fmt.Errorf("-no-remove and -latest can't be specified together")
} }
overrideMatching := !latest && !noRemove overrideMatching := !latest && !noRemove
@@ -84,7 +84,7 @@ Example:
} }
cmd.Flag.Bool("latest", false, "use only the latest version of each package") cmd.Flag.Bool("latest", false, "use only the latest version of each package")
cmd.Flag.Bool("no-remove", false, "don't remove duplicate arch/name packages") cmd.Flag.Bool("no-remove", false, "don't remove duplicate arch/name packages")
return cmd return cmd
} }
+4 -4
View File
@@ -17,9 +17,9 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error {
return commander.ErrCommandError return commander.ErrCommandError
} }
noDeps := context.flags.Lookup("no-deps").Value.Get().(bool) noDeps := context.Flags().Lookup("no-deps").Value.Get().(bool)
noRemove := context.flags.Lookup("no-remove").Value.Get().(bool) noRemove := context.Flags().Lookup("no-remove").Value.Get().(bool)
allMatches := context.flags.Lookup("all-matches").Value.Get().(bool) allMatches := context.Flags().Lookup("all-matches").Value.Get().(bool)
// Load <name> snapshot // Load <name> snapshot
snapshot, err := context.CollectionFactory().SnapshotCollection().ByName(args[0]) snapshot, err := context.CollectionFactory().SnapshotCollection().ByName(args[0])
@@ -129,7 +129,7 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error {
}) })
alreadySeen = nil alreadySeen = nil
if context.flags.Lookup("dry-run").Value.Get().(bool) { if context.Flags().Lookup("dry-run").Value.Get().(bool) {
context.Progress().Printf("\nNot creating snapshot, as dry run was requested.\n") context.Progress().Printf("\nNot creating snapshot, as dry run was requested.\n")
} else { } else {
// Create <destination> snapshot // Create <destination> snapshot
+1 -1
View File
@@ -73,7 +73,7 @@ func aptlySnapshotMirrorRepoSearch(cmd *commander.Command, args []string) error
return fmt.Errorf("unable to search: %s", err) return fmt.Errorf("unable to search: %s", err)
} }
withDeps := context.flags.Lookup("with-deps").Value.Get().(bool) withDeps := context.Flags().Lookup("with-deps").Value.Get().(bool)
architecturesList := []string{} architecturesList := []string{}
if withDeps { if withDeps {
+1 -1
View File
@@ -30,7 +30,7 @@ func aptlySnapshotShow(cmd *commander.Command, args []string) error {
fmt.Printf("Description: %s\n", snapshot.Description) fmt.Printf("Description: %s\n", snapshot.Description)
fmt.Printf("Number of packages: %d\n", snapshot.NumPackages()) fmt.Printf("Number of packages: %d\n", snapshot.NumPackages())
withPackages := context.flags.Lookup("with-packages").Value.Get().(bool) withPackages := context.Flags().Lookup("with-packages").Value.Get().(bool)
if withPackages { if withPackages {
ListPackagesRefList(snapshot.RefList()) ListPackagesRefList(snapshot.RefList())
} }
+394
View File
@@ -0,0 +1,394 @@
// Package context provides single entry to all resources
package context
import (
"fmt"
"github.com/smira/aptly/aptly"
"github.com/smira/aptly/console"
"github.com/smira/aptly/database"
"github.com/smira/aptly/deb"
"github.com/smira/aptly/files"
"github.com/smira/aptly/http"
"github.com/smira/aptly/s3"
"github.com/smira/aptly/utils"
"github.com/smira/commander"
"github.com/smira/flag"
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"strings"
"time"
)
// AptlyContext is a common context shared by all commands
type AptlyContext struct {
flags, globalFlags *flag.FlagSet
configLoaded bool
progress aptly.Progress
downloader aptly.Downloader
database database.Storage
packagePool aptly.PackagePool
publishedStorages map[string]aptly.PublishedStorage
collectionFactory *deb.CollectionFactory
dependencyOptions int
architecturesList []string
// Debug features
fileCPUProfile *os.File
fileMemProfile *os.File
fileMemStats *os.File
}
// Check interface
var _ aptly.PublishedStorageProvider = &AptlyContext{}
// FatalError is type for panicking to abort execution with non-zero
// exit code and print meaningful explanation
type FatalError struct {
ReturnCode int
Message string
}
// Fatal panics and aborts execution with exit code 1
func Fatal(err error) {
returnCode := 1
if err == commander.ErrFlagError || err == commander.ErrCommandError {
returnCode = 2
}
panic(&FatalError{ReturnCode: returnCode, Message: err.Error()})
}
// Config loads and returns current configuration
func (context *AptlyContext) Config() *utils.ConfigStructure {
if !context.configLoaded {
var err error
configLocation := context.globalFlags.Lookup("config").Value.String()
if configLocation != "" {
err = utils.LoadConfig(configLocation, &utils.Config)
if err != nil {
Fatal(err)
}
} else {
configLocations := []string{
filepath.Join(os.Getenv("HOME"), ".aptly.conf"),
"/etc/aptly.conf",
}
for _, configLocation := range configLocations {
err = utils.LoadConfig(configLocation, &utils.Config)
if err == nil {
break
}
if !os.IsNotExist(err) {
Fatal(fmt.Errorf("error loading config file %s: %s", configLocation, err))
}
}
if err != nil {
fmt.Printf("Config file not found, creating default config at %s\n\n", configLocations[0])
utils.SaveConfig(configLocations[0], &utils.Config)
}
}
context.configLoaded = true
}
return &utils.Config
}
// LookupOption checks boolean flag with default (usually config) and command-line
// setting
func (context *AptlyContext) LookupOption(defaultValue bool, name string) (result bool) {
result = defaultValue
if context.globalFlags.IsSet(name) {
result = context.globalFlags.Lookup(name).Value.Get().(bool)
}
return
}
// DependencyOptions calculates options related to dependecy handling
func (context *AptlyContext) DependencyOptions() int {
if context.dependencyOptions == -1 {
context.dependencyOptions = 0
if context.LookupOption(context.Config().DepFollowSuggests, "dep-follow-suggests") {
context.dependencyOptions |= deb.DepFollowSuggests
}
if context.LookupOption(context.Config().DepFollowRecommends, "dep-follow-recommends") {
context.dependencyOptions |= deb.DepFollowRecommends
}
if context.LookupOption(context.Config().DepFollowAllVariants, "dep-follow-all-variants") {
context.dependencyOptions |= deb.DepFollowAllVariants
}
if context.LookupOption(context.Config().DepFollowSource, "dep-follow-source") {
context.dependencyOptions |= deb.DepFollowSource
}
}
return context.dependencyOptions
}
// ArchitecturesList returns list of architectures fixed via command line or config
func (context *AptlyContext) ArchitecturesList() []string {
if context.architecturesList == nil {
context.architecturesList = context.Config().Architectures
optionArchitectures := context.globalFlags.Lookup("architectures").Value.String()
if optionArchitectures != "" {
context.architecturesList = strings.Split(optionArchitectures, ",")
}
}
return context.architecturesList
}
// Progress creates or returns Progress object
func (context *AptlyContext) Progress() aptly.Progress {
if context.progress == nil {
context.progress = console.NewProgress()
context.progress.Start()
}
return context.progress
}
// Downloader returns instance of current downloader
func (context *AptlyContext) Downloader() aptly.Downloader {
if context.downloader == nil {
var downloadLimit int64
limitFlag := context.flags.Lookup("download-limit")
if limitFlag != nil {
downloadLimit = limitFlag.Value.Get().(int64)
}
if downloadLimit == 0 {
downloadLimit = context.Config().DownloadLimit
}
context.downloader = http.NewDownloader(context.Config().DownloadConcurrency,
downloadLimit*1024, context.Progress())
}
return context.downloader
}
// DBPath builds path to database
func (context *AptlyContext) DBPath() string {
return filepath.Join(context.Config().RootDir, "db")
}
// Database opens and returns current instance of database
func (context *AptlyContext) Database() (database.Storage, error) {
if context.database == nil {
var err error
context.database, err = database.OpenDB(context.DBPath())
if err != nil {
return nil, fmt.Errorf("can't open database: %s", err)
}
}
return context.database, nil
}
// CloseDatabase closes the db temporarily
func (context *AptlyContext) CloseDatabase() error {
if context.database == nil {
return nil
}
return context.database.Close()
}
// ReOpenDatabase reopens the db after close
func (context *AptlyContext) ReOpenDatabase() error {
if context.database == nil {
return nil
}
const MaxTries = 10
const Delay = 10 * time.Second
for try := 0; try < MaxTries; try++ {
err := context.database.ReOpen()
if err == nil || strings.Index(err.Error(), "resource temporarily unavailable") == -1 {
return err
}
context.Progress().Printf("Unable to reopen database, sleeping %s\n", Delay)
<-time.After(Delay)
}
return fmt.Errorf("unable to reopen the DB, maximum number of retries reached")
}
// CollectionFactory builds factory producing all kinds of collections
func (context *AptlyContext) CollectionFactory() *deb.CollectionFactory {
if context.collectionFactory == nil {
db, err := context.Database()
if err != nil {
Fatal(err)
}
context.collectionFactory = deb.NewCollectionFactory(db)
}
return context.collectionFactory
}
// PackagePool returns instance of PackagePool
func (context *AptlyContext) PackagePool() aptly.PackagePool {
if context.packagePool == nil {
context.packagePool = files.NewPackagePool(context.Config().RootDir)
}
return context.packagePool
}
// GetPublishedStorage returns instance of PublishedStorage
func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedStorage {
publishedStorage, ok := context.publishedStorages[name]
if !ok {
if name == "" {
publishedStorage = files.NewPublishedStorage(context.Config().RootDir)
} else if strings.HasPrefix(name, "s3:") {
params, ok := context.Config().S3PublishRoots[name[3:]]
if !ok {
Fatal(fmt.Errorf("published S3 storage %v not configured", name[3:]))
}
var err error
publishedStorage, err = s3.NewPublishedStorage(params.AccessKeyID, params.SecretAccessKey,
params.Region, params.Bucket, params.ACL, params.Prefix, params.StorageClass,
params.EncryptionMethod, params.PlusWorkaround)
if err != nil {
Fatal(err)
}
} else {
Fatal(fmt.Errorf("unknown published storage format: %v", name))
}
context.publishedStorages[name] = publishedStorage
}
return publishedStorage
}
// UpdateFlags sets internal copy of flags in the context
func (context *AptlyContext) UpdateFlags(flags *flag.FlagSet) {
context.flags = flags
}
// Flags returns current command flags
func (context *AptlyContext) Flags() *flag.FlagSet {
return context.flags
}
// GlobalFlags returns flags passed to all commands
func (context *AptlyContext) GlobalFlags() *flag.FlagSet {
return context.globalFlags
}
// Shutdown shuts context down
func (context *AptlyContext) Shutdown() {
if aptly.EnableDebug {
if context.fileMemProfile != nil {
pprof.WriteHeapProfile(context.fileMemProfile)
context.fileMemProfile.Close()
context.fileMemProfile = nil
}
if context.fileCPUProfile != nil {
pprof.StopCPUProfile()
context.fileCPUProfile.Close()
context.fileCPUProfile = nil
}
if context.fileMemProfile != nil {
context.fileMemProfile.Close()
context.fileMemProfile = nil
}
}
if context.database != nil {
context.database.Close()
context.database = nil
}
if context.downloader != nil {
context.downloader.Abort()
context.downloader = nil
}
if context.progress != nil {
context.progress.Shutdown()
context.progress = nil
}
}
// Cleanup does partial shutdown of context
func (context *AptlyContext) Cleanup() {
if context.downloader != nil {
context.downloader.Shutdown()
context.downloader = nil
}
if context.progress != nil {
context.progress.Shutdown()
context.progress = nil
}
}
// NewContext initializes context with default settings
func NewContext(flags *flag.FlagSet) (*AptlyContext, error) {
var err error
context := &AptlyContext{
flags: flags,
globalFlags: flags,
dependencyOptions: -1,
publishedStorages: map[string]aptly.PublishedStorage{},
}
if aptly.EnableDebug {
cpuprofile := flags.Lookup("cpuprofile").Value.String()
if cpuprofile != "" {
context.fileCPUProfile, err = os.Create(cpuprofile)
if err != nil {
return nil, err
}
pprof.StartCPUProfile(context.fileCPUProfile)
}
memprofile := flags.Lookup("memprofile").Value.String()
if memprofile != "" {
context.fileMemProfile, err = os.Create(memprofile)
if err != nil {
return nil, err
}
}
memstats := flags.Lookup("memstats").Value.String()
if memstats != "" {
interval := flags.Lookup("meminterval").Value.Get().(time.Duration)
context.fileMemStats, err = os.Create(memstats)
if err != nil {
return nil, err
}
context.fileMemStats.WriteString("# Time\tHeapSys\tHeapAlloc\tHeapIdle\tHeapReleased\n")
go func() {
var stats runtime.MemStats
start := time.Now().UnixNano()
for {
runtime.ReadMemStats(&stats)
if context.fileMemStats != nil {
context.fileMemStats.WriteString(fmt.Sprintf("%d\t%d\t%d\t%d\t%d\n",
(time.Now().UnixNano()-start)/1000000, stats.HeapSys, stats.HeapAlloc, stats.HeapIdle, stats.HeapReleased))
time.Sleep(interval)
} else {
break
}
}
}()
}
}
return context, nil
}