2019-11-30 02:46:32 +00:00
package main
import (
"context"
"fmt"
"os"
2019-12-19 17:34:18 +00:00
"os/signal"
2019-11-30 02:46:32 +00:00
"path/filepath"
"strings"
2019-12-16 13:47:14 +00:00
"time"
2019-11-30 02:46:32 +00:00
2022-06-15 09:19:17 +00:00
ipfscluster "github.com/ipfs-cluster/ipfs-cluster"
"github.com/ipfs-cluster/ipfs-cluster/allocator/balanced"
"github.com/ipfs-cluster/ipfs-cluster/api"
"github.com/ipfs-cluster/ipfs-cluster/api/rest"
"github.com/ipfs-cluster/ipfs-cluster/cmdutils"
"github.com/ipfs-cluster/ipfs-cluster/config"
"github.com/ipfs-cluster/ipfs-cluster/consensus/crdt"
"github.com/ipfs-cluster/ipfs-cluster/datastore/badger"
"github.com/ipfs-cluster/ipfs-cluster/datastore/leveldb"
"github.com/ipfs-cluster/ipfs-cluster/informer/disk"
2024-01-03 20:30:23 +00:00
"github.com/ipfs-cluster/ipfs-cluster/informer/pinqueue"
"github.com/ipfs-cluster/ipfs-cluster/informer/tags"
2022-06-15 09:19:17 +00:00
"github.com/ipfs-cluster/ipfs-cluster/ipfsconn/ipfshttp"
"github.com/ipfs-cluster/ipfs-cluster/monitor/pubsubmon"
"github.com/ipfs-cluster/ipfs-cluster/observations"
"github.com/ipfs-cluster/ipfs-cluster/pintracker/stateless"
2020-02-28 16:16:16 +00:00
"github.com/multiformats/go-multiaddr"
2019-11-30 02:46:32 +00:00
"github.com/pkg/errors"
cli "github.com/urfave/cli/v2"
)
func printFirstStart ( ) {
fmt . Printf ( `
No clusters configured yet !
If this is the first time you are running % s ,
be sure to check out the usage documentation . Here are some
examples to get you going :
$ % s -- help - general description and usage help
$ % s < clusterName > -- help - Help and subcommands for the < clusterName > ' s follower peer
$ % s < clusterName > info -- help - Help for the "info" subcommand ( same for others ) .
` , programName , programName , programName , programName )
}
2019-12-19 18:50:46 +00:00
func printNotInitialized ( clusterName string ) {
2019-11-30 02:46:32 +00:00
fmt . Printf ( `
2019-12-19 18:50:46 +00:00
This cluster peer has not been initialized .
2019-11-30 02:46:32 +00:00
2019-12-19 18:50:46 +00:00
Try running "%s %s init <config-url>" first .
` , programName , clusterName )
2019-11-30 02:46:32 +00:00
}
2019-12-16 12:32:55 +00:00
func setLogLevels ( lvl string ) {
for f := range ipfscluster . LoggingFacilities {
ipfscluster . SetFacilityLogLevel ( f , lvl )
}
2021-08-06 09:28:22 +00:00
for f := range ipfscluster . LoggingFacilitiesExtra {
ipfscluster . SetFacilityLogLevel ( f , lvl )
}
2019-12-16 12:32:55 +00:00
}
2019-12-19 18:50:46 +00:00
// returns whether the config folder exists
func isInitialized ( absPath string ) bool {
_ , err := os . Stat ( absPath )
return err == nil
}
2019-12-19 18:53:30 +00:00
func listClustersCmd ( c * cli . Context ) error {
2019-11-30 02:46:32 +00:00
absPath , _ , _ := buildPaths ( c , "" )
f , err := os . Open ( absPath )
if os . IsNotExist ( err ) {
printFirstStart ( )
return nil
}
if err != nil {
return cli . Exit ( err , 1 )
}
dirs , err := f . Readdir ( - 1 )
if err != nil {
return cli . Exit ( errors . Wrapf ( err , "reading %s" , absPath ) , 1 )
}
var filteredDirs [ ] string
for _ , d := range dirs {
if d . IsDir ( ) {
configPath := filepath . Join ( absPath , d . Name ( ) , DefaultConfigFile )
if _ , err := os . Stat ( configPath ) ; err == nil {
filteredDirs = append ( filteredDirs , d . Name ( ) )
}
}
}
if len ( filteredDirs ) == 0 {
printFirstStart ( )
return nil
}
fmt . Printf ( "Configurations found for %d follower peers. For info and help, try running:\n\n" , len ( filteredDirs ) )
for _ , d := range filteredDirs {
fmt . Printf ( "%s \"%s\"\n" , programName , d )
}
2019-12-19 19:40:06 +00:00
fmt . Printf ( "\nTip: \"%s --help\" for help and examples.\n" , programName )
2019-11-30 02:46:32 +00:00
return nil
}
func infoCmd ( c * cli . Context ) error {
clusterName := c . String ( "clusterName" )
2019-12-16 12:32:55 +00:00
// Avoid pollution of the screen
setLogLevels ( "critical" )
2019-11-30 02:46:32 +00:00
absPath , configPath , identityPath := buildPaths ( c , clusterName )
2019-12-19 18:50:46 +00:00
if ! isInitialized ( absPath ) {
printNotInitialized ( clusterName )
return cli . Exit ( "" , 1 )
}
2019-11-30 02:46:32 +00:00
cfgHelper , err := cmdutils . NewLoadedConfigHelper ( configPath , identityPath )
var url string
if err != nil {
if config . IsErrFetchingSource ( err ) {
url = fmt . Sprintf (
2019-12-23 22:38:23 +00:00
"failed retrieving configuration source (%s)" ,
2019-11-30 02:46:32 +00:00
cfgHelper . Manager ( ) . Source ,
)
2019-12-23 22:38:23 +00:00
ipfsCfg := ipfshttp . Config { }
ipfsCfg . Default ( )
cfgHelper . Configs ( ) . Ipfshttp = & ipfsCfg
2019-11-30 02:46:32 +00:00
} else {
2019-12-19 18:50:46 +00:00
return cli . Exit ( errors . Wrapf ( err , "reading the configurations in %s" , absPath ) , 1 )
2019-11-30 02:46:32 +00:00
}
} else {
url = fmt . Sprintf ( "Available (%s)" , cfgHelper . Manager ( ) . Source )
}
cfgHelper . Manager ( ) . Shutdown ( )
fmt . Printf ( "Information about follower peer for Cluster \"%s\":\n\n" , clusterName )
fmt . Printf ( "Config folder: %s\n" , absPath )
fmt . Printf ( "Config source URL: %s\n" , url )
ctx := context . Background ( )
client , err := getClient ( absPath , clusterName )
if err != nil {
return cli . Exit ( errors . Wrap ( err , "error creating client" ) , 1 )
}
_ , err = client . Version ( ctx )
fmt . Printf ( "Cluster Peer online: %t\n" , err == nil )
2019-12-16 13:55:36 +00:00
// Either we loaded a valid config, or we are using a default. Worth
// applying env vars in the second case.
2019-12-19 18:55:19 +00:00
if err := cfgHelper . Configs ( ) . Ipfshttp . ApplyEnvVars ( ) ; err != nil {
return cli . Exit ( errors . Wrap ( err , "applying environment variables to ipfshttp config" ) , 1 )
}
2019-12-16 13:55:36 +00:00
cfgHelper . Configs ( ) . Ipfshttp . ConnectSwarmsDelay = 0
2019-11-30 02:46:32 +00:00
connector , err := ipfshttp . NewConnector ( cfgHelper . Configs ( ) . Ipfshttp )
if err == nil {
_ , err = connector . ID ( ctx )
}
fmt . Printf ( "IPFS peer online: %t\n" , err == nil )
2019-12-16 13:46:24 +00:00
if c . Command . Name == "" {
fmt . Printf ( "Additional help:\n\n" )
fmt . Printf ( "-------------------------------------------------\n\n" )
2019-12-19 18:56:49 +00:00
return cli . ShowAppHelp ( c )
2019-12-16 13:46:24 +00:00
}
2019-11-30 02:46:32 +00:00
return nil
}
func initCmd ( c * cli . Context ) error {
if ! c . Args ( ) . Present ( ) {
return cli . Exit ( "configuration URL not provided" , 1 )
}
cfgURL := c . Args ( ) . First ( )
return initCluster ( c , false , cfgURL )
}
func initCluster ( c * cli . Context , ignoreReinit bool , cfgURL string ) error {
clusterName := c . String ( clusterNameFlag )
absPath , configPath , identityPath := buildPaths ( c , clusterName )
2019-12-19 18:50:46 +00:00
if isInitialized ( absPath ) {
2019-11-30 02:46:32 +00:00
if ignoreReinit {
fmt . Println ( "Configuration for this cluster already exists. Skipping initialization." )
fmt . Printf ( "If you wish to re-initialize, simply delete %s\n\n" , absPath )
return nil
}
cmdutils . ErrorOut ( "Configuration for this cluster already exists.\n" )
cmdutils . ErrorOut ( "Please delete %s if you wish to re-initialize." , absPath )
return cli . Exit ( "" , 1 )
}
2019-12-19 19:02:40 +00:00
gw := c . String ( "gateway" )
2019-12-16 14:02:07 +00:00
2019-11-30 02:46:32 +00:00
if ! strings . HasPrefix ( cfgURL , "http://" ) && ! strings . HasPrefix ( cfgURL , "https://" ) {
2019-12-16 14:02:07 +00:00
fmt . Printf ( "%s will be assumed to be an DNSLink-powered address: /ipns/%s.\n" , cfgURL , cfgURL )
fmt . Printf ( "It will be resolved using the local IPFS daemon's gateway (%s).\n" , gw )
fmt . Println ( "If this is not the case, specify the full url starting with http:// or https://." )
fmt . Println ( "(You can override the gateway URL by setting IPFS_GATEWAY)" )
2019-12-16 12:29:34 +00:00
fmt . Println ( )
2019-12-16 14:02:07 +00:00
cfgURL = fmt . Sprintf ( "http://%s/ipns/%s" , gw , cfgURL )
2019-11-30 02:46:32 +00:00
}
2021-06-09 17:40:36 +00:00
// Setting the datastore here is useless, as we initialize with remote
// config and we will have an empty service.json with the source only.
// That source will decide which datastore is actually used.
cfgHelper := cmdutils . NewConfigHelper ( configPath , identityPath , "crdt" , "" )
2019-11-30 02:46:32 +00:00
cfgHelper . Manager ( ) . Shutdown ( )
cfgHelper . Manager ( ) . Source = cfgURL
2019-12-19 19:32:09 +00:00
err := cfgHelper . Manager ( ) . Default ( )
if err != nil {
return cli . Exit ( errors . Wrap ( err , "error generating default config" ) , 1 )
}
2019-11-30 02:46:32 +00:00
ident := cfgHelper . Identity ( )
2019-12-19 19:32:09 +00:00
err = ident . Default ( )
2019-11-30 02:46:32 +00:00
if err != nil {
return cli . Exit ( errors . Wrap ( err , "error generating identity" ) , 1 )
}
err = ident . ApplyEnvVars ( )
if err != nil {
return cli . Exit ( errors . Wrap ( err , "error applying environment variables to the identity" ) , 1 )
}
err = cfgHelper . SaveIdentityToDisk ( )
if err != nil {
return cli . Exit ( errors . Wrapf ( err , "error saving %s" , identityPath ) , 1 )
}
fmt . Printf ( "Identity written to %s.\n" , identityPath )
err = cfgHelper . SaveConfigToDisk ( )
if err != nil {
return cli . Exit ( errors . Wrapf ( err , "saving %s" , configPath ) , 1 )
}
fmt . Printf ( "Configuration written to %s.\n" , configPath )
fmt . Printf ( "Cluster \"%s\" follower peer initialized.\n\n" , clusterName )
fmt . Printf (
"You can now use \"%s %s run\" to start a follower peer for this cluster.\n" ,
programName ,
clusterName ,
)
fmt . Println ( "(Remember to start your IPFS daemon before)" )
return nil
}
func runCmd ( c * cli . Context ) error {
clusterName := c . String ( clusterNameFlag )
if cfgURL := c . String ( "init" ) ; cfgURL != "" {
err := initCluster ( c , true , cfgURL )
if err != nil {
return err
}
}
2019-12-19 18:50:46 +00:00
absPath , configPath , identityPath := buildPaths ( c , clusterName )
if ! isInitialized ( absPath ) {
printNotInitialized ( clusterName )
return cli . Exit ( "" , 1 )
}
2019-11-30 02:46:32 +00:00
fmt . Printf ( "Starting the IPFS Cluster follower peer for \"%s\".\nCTRL-C to stop it.\n" , clusterName )
2019-12-16 13:47:14 +00:00
fmt . Println ( "Checking if IPFS is online (will wait for 2 minutes)..." )
ctxIpfs , cancelIpfs := context . WithTimeout ( context . Background ( ) , 2 * time . Minute )
defer cancelIpfs ( )
err := cmdutils . WaitForIPFS ( ctxIpfs )
if err != nil {
return cli . Exit ( "timed out waiting for IPFS to be available" , 1 )
}
2019-11-30 02:46:32 +00:00
2019-12-16 12:32:55 +00:00
setLogLevels ( logLevel ) // set to "info" by default.
// Avoid API logs polluting the screen everytime we
// run some "list" command.
2019-11-30 02:46:32 +00:00
ipfscluster . SetFacilityLogLevel ( "restapilog" , "error" )
cfgHelper , err := cmdutils . NewLoadedConfigHelper ( configPath , identityPath )
if err != nil {
2019-12-19 18:50:46 +00:00
return cli . Exit ( errors . Wrapf ( err , "reading the configurations in %s" , absPath ) , 1 )
2019-11-30 02:46:32 +00:00
}
cfgHelper . Manager ( ) . Shutdown ( )
cfgs := cfgHelper . Configs ( )
2021-06-09 17:40:36 +00:00
stmgr , err := cmdutils . NewStateManager ( cfgHelper . GetConsensus ( ) , cfgHelper . GetDatastore ( ) , cfgHelper . Identity ( ) , cfgs )
2020-04-01 18:15:48 +00:00
if err != nil {
return cli . Exit ( errors . Wrap ( err , "creating state manager" ) , 1 )
}
store , err := stmgr . GetStore ( )
if err != nil {
return cli . Exit ( errors . Wrap ( err , "creating datastore" ) , 1 )
}
2019-11-30 02:46:32 +00:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
2020-04-01 18:15:48 +00:00
host , pubsub , dht , err := ipfscluster . NewClusterHost ( ctx , cfgHelper . Identity ( ) , cfgs . Cluster , store )
2019-11-30 02:46:32 +00:00
if err != nil {
return cli . Exit ( errors . Wrap ( err , "error creating libp2p components" ) , 1 )
}
// Always run followers in follower mode.
cfgs . Cluster . FollowerMode = true
2019-12-23 22:41:35 +00:00
// Do not let trusted peers GC this peer
// Defaults to Trusted otherwise.
cfgs . Cluster . RPCPolicy [ "Cluster.RepoGCLocal" ] = ipfscluster . RPCClosed
2019-11-30 02:46:32 +00:00
// Discard API configurations and create our own
2021-09-15 17:53:09 +00:00
apiCfg := rest . NewConfig ( )
cfgs . Restapi = apiCfg
2019-12-19 19:32:09 +00:00
_ = apiCfg . Default ( )
2019-11-30 02:46:32 +00:00
listenSocket , err := socketAddress ( absPath , clusterName )
if err != nil {
return cli . Exit ( err , 1 )
}
2020-02-28 16:16:16 +00:00
apiCfg . HTTPListenAddr = [ ] multiaddr . Multiaddr { listenSocket }
2019-12-16 12:10:12 +00:00
// Allow customization via env vars
err = apiCfg . ApplyEnvVars ( )
if err != nil {
2020-02-03 09:30:04 +00:00
return cli . Exit ( errors . Wrap ( err , "error applying environmental variables to restapi configuration" ) , 1 )
2019-12-16 12:10:12 +00:00
}
2021-09-15 17:53:09 +00:00
rest , err := rest . NewAPI ( ctx , apiCfg )
2019-11-30 02:46:32 +00:00
if err != nil {
return cli . Exit ( errors . Wrap ( err , "creating REST API component" ) , 1 )
}
connector , err := ipfshttp . NewConnector ( cfgs . Ipfshttp )
if err != nil {
return cli . Exit ( errors . Wrap ( err , "creating IPFS Connector component" ) , 1 )
}
2024-01-03 20:30:23 +00:00
var informers [ ] ipfscluster . Informer
if cfgHelper . Manager ( ) . IsLoadedFromJSON ( config . Informer , cfgs . DiskInf . ConfigKey ( ) ) {
diskInf , err := disk . NewInformer ( cfgs . DiskInf )
if err != nil {
return cli . Exit ( errors . Wrap ( err , "creating disk informer" ) , 1 )
}
informers = append ( informers , diskInf )
}
if cfgHelper . Manager ( ) . IsLoadedFromJSON ( config . Informer , cfgs . TagsInf . ConfigKey ( ) ) {
tagsInf , err := tags . New ( cfgs . TagsInf )
if err != nil {
return cli . Exit ( errors . Wrap ( err , "creating numpin informer" ) , 1 )
}
informers = append ( informers , tagsInf )
2019-11-30 02:46:32 +00:00
}
2024-01-03 20:30:23 +00:00
if cfgHelper . Manager ( ) . IsLoadedFromJSON ( config . Informer , cfgs . PinQueueInf . ConfigKey ( ) ) {
pinQueueInf , err := pinqueue . New ( cfgs . PinQueueInf )
if err != nil {
return cli . Exit ( errors . Wrap ( err , "creating pinqueue informer" ) , 1 )
}
informers = append ( informers , pinQueueInf )
}
2021-10-06 09:26:38 +00:00
alloc , err := balanced . New ( cfgs . BalancedAlloc )
2021-09-13 12:14:50 +00:00
if err != nil {
return cli . Exit ( errors . Wrap ( err , "creating metrics allocator" ) , 1 )
}
2019-11-30 02:46:32 +00:00
crdtcons , err := crdt . New (
host ,
dht ,
pubsub ,
cfgs . Crdt ,
store ,
)
if err != nil {
store . Close ( )
return cli . Exit ( errors . Wrap ( err , "creating CRDT component" ) , 1 )
}
2019-12-13 08:50:44 +00:00
tracker := stateless . New ( cfgs . Statelesstracker , host . ID ( ) , cfgs . Cluster . Peername , crdtcons . State )
2019-11-30 02:46:32 +00:00
mon , err := pubsubmon . New ( ctx , cfgs . Pubsubmon , pubsub , nil )
if err != nil {
store . Close ( )
return cli . Exit ( errors . Wrap ( err , "setting up PeerMonitor" ) , 1 )
}
2019-12-13 08:51:15 +00:00
// Hardcode disabled tracing and metrics to avoid mistakenly
2019-11-30 02:46:32 +00:00
// exposing any user data.
tracerCfg := observations . TracingConfig { }
2019-12-19 19:32:09 +00:00
_ = tracerCfg . Default ( )
2019-11-30 02:46:32 +00:00
tracerCfg . EnableTracing = false
cfgs . Tracing = & tracerCfg
tracer , err := observations . SetupTracing ( & tracerCfg )
if err != nil {
return cli . Exit ( errors . Wrap ( err , "error setting up tracer" ) , 1 )
}
2019-12-16 14:07:03 +00:00
// This does nothing since we are not calling SetupMetrics anyways
// But stays just to be explicit.
2019-11-30 02:46:32 +00:00
metricsCfg := observations . MetricsConfig { }
2019-12-19 19:32:09 +00:00
_ = metricsCfg . Default ( )
2019-11-30 02:46:32 +00:00
metricsCfg . EnableStats = false
cfgs . Metrics = & metricsCfg
2019-12-19 17:34:18 +00:00
// We are going to run a cluster peer and should do an
// oderly shutdown if we are interrupted: cancel default
// signal handling and leave things to HandleSignals.
signal . Stop ( signalChan )
close ( signalChan )
2019-11-30 02:46:32 +00:00
cluster , err := ipfscluster . NewCluster (
ctx ,
host ,
dht ,
cfgs . Cluster ,
store ,
crdtcons ,
[ ] ipfscluster . API { rest } ,
connector ,
tracker ,
mon ,
alloc ,
2024-01-03 20:30:23 +00:00
informers ,
2019-11-30 02:46:32 +00:00
tracer ,
)
if err != nil {
store . Close ( )
return cli . Exit ( errors . Wrap ( err , "error creating cluster peer" ) , 1 )
}
2020-04-02 14:29:41 +00:00
return cmdutils . HandleSignals ( ctx , cancel , cluster , host , dht , store )
2019-11-30 02:46:32 +00:00
}
// List
2019-12-19 18:53:30 +00:00
func listCmd ( c * cli . Context ) error {
2019-11-30 02:46:32 +00:00
clusterName := c . String ( "clusterName" )
absPath , configPath , identityPath := buildPaths ( c , clusterName )
2021-06-09 17:40:36 +00:00
if ! isInitialized ( absPath ) {
printNotInitialized ( clusterName )
return cli . Exit ( "" , 1 )
}
2019-12-16 12:10:12 +00:00
2021-06-09 17:40:36 +00:00
err := printStatusOnline ( absPath , clusterName )
if err == nil {
return nil
}
2020-05-14 23:08:30 +00:00
2021-06-09 17:40:36 +00:00
// There was an error. Try offline status
apiErr , ok := err . ( * api . Error )
if ok && apiErr . Code != 0 {
return cli . Exit (
errors . Wrapf (
err ,
"The Peer API seems to be running but returned with code %d" ,
apiErr . Code ,
) , 1 )
}
// We are on offline mode so we cannot rely on IPFS being
// running and most probably our configuration is remote and
// to be loaded from IPFS. Thus we need to find a different
// way to decide whether to load badger/leveldb, and once we
// know, do it with the default settings.
hasLevelDB := false
lDBCfg := & leveldb . Config { }
lDBCfg . SetBaseDir ( absPath )
lDBCfg . Default ( )
levelDBInfo , err := os . Stat ( lDBCfg . GetFolder ( ) )
if err == nil && levelDBInfo . IsDir ( ) {
hasLevelDB = true
}
hasBadger := false
badgerCfg := & badger . Config { }
badgerCfg . SetBaseDir ( absPath )
badgerCfg . Default ( )
badgerInfo , err := os . Stat ( badgerCfg . GetFolder ( ) )
if err == nil && badgerInfo . IsDir ( ) {
hasBadger = true
}
if hasLevelDB && hasBadger {
return cli . Exit ( errors . Wrapf ( err , "found both leveldb (%s) and badger (%s) folders: cannot determine which to use in offline mode" , lDBCfg . GetFolder ( ) , badgerCfg . GetFolder ( ) ) , 1 )
}
// Since things were initialized, assume there is one at least.
dstoreType := "leveldb"
if hasBadger {
dstoreType = "badger"
}
cfgHelper := cmdutils . NewConfigHelper ( configPath , identityPath , "crdt" , dstoreType )
cfgHelper . Manager ( ) . Shutdown ( ) // not needed
cfgHelper . Configs ( ) . Badger . SetBaseDir ( absPath )
cfgHelper . Configs ( ) . LevelDB . SetBaseDir ( absPath )
cfgHelper . Manager ( ) . Default ( ) // we have a default crdt config with either leveldb or badger registered.
cfgHelper . Manager ( ) . ApplyEnvVars ( )
err = printStatusOffline ( cfgHelper )
if err != nil {
return cli . Exit ( errors . Wrap ( err , "error obtaining the pinset" ) , 1 )
2019-11-30 02:46:32 +00:00
}
2021-06-09 17:40:36 +00:00
2019-11-30 02:46:32 +00:00
return nil
}
func printStatusOnline ( absPath , clusterName string ) error {
ctx := context . Background ( )
client , err := getClient ( absPath , clusterName )
if err != nil {
return cli . Exit ( errors . Wrap ( err , "error creating client" ) , 1 )
}
2022-03-22 09:56:16 +00:00
out := make ( chan api . GlobalPinInfo , 1024 )
errCh := make ( chan error , 1 )
go func ( ) {
defer close ( errCh )
errCh <- client . StatusAll ( ctx , 0 , true , out )
} ( )
2019-11-30 02:46:32 +00:00
var pid string
2022-03-22 09:56:16 +00:00
for gpi := range out {
2019-11-30 02:46:32 +00:00
if pid == "" { // do this once
// PeerMap will only have one key
for k := range gpi . PeerMap {
pid = k
break
}
}
pinInfo := gpi . PeerMap [ pid ]
2020-05-15 22:32:28 +00:00
printPin ( gpi . Cid , pinInfo . Status . String ( ) , gpi . Name , pinInfo . Error )
2019-11-30 02:46:32 +00:00
}
2022-03-22 09:56:16 +00:00
err = <- errCh
return err
2019-11-30 02:46:32 +00:00
}
func printStatusOffline ( cfgHelper * cmdutils . ConfigHelper ) error {
mgr , err := cmdutils . NewStateManagerWithHelper ( cfgHelper )
if err != nil {
return err
}
store , err := mgr . GetStore ( )
if err != nil {
return err
}
defer store . Close ( )
st , err := mgr . GetOfflineState ( store )
if err != nil {
return err
}
2022-03-22 09:56:16 +00:00
out := make ( chan api . Pin , 1024 )
errCh := make ( chan error , 1 )
go func ( ) {
defer close ( errCh )
errCh <- st . List ( context . Background ( ) , out )
} ( )
for pin := range out {
2019-11-30 02:46:32 +00:00
printPin ( pin . Cid , "offline" , pin . Name , "" )
}
2022-03-22 09:56:16 +00:00
err = <- errCh
return err
2019-11-30 02:46:32 +00:00
}
2022-04-07 11:53:30 +00:00
func printPin ( c api . Cid , status , name , err string ) {
2019-11-30 02:46:32 +00:00
if err != "" {
name = name + " (" + err + ")"
}
fmt . Printf ( "%-20s %s %s\n" , status , c , name )
}