2019-10-01 20:37:38 +00:00
// Command "src-expose" serves directories as git repositories over HTTP.
package main
import (
"flag"
"fmt"
2019-10-09 20:53:03 +00:00
"io"
2019-10-01 20:37:38 +00:00
"log"
2019-10-02 14:35:53 +00:00
"net"
2019-10-01 20:37:38 +00:00
"os"
"strings"
"time"
"github.com/peterbourgon/ff/ffcli"
"gopkg.in/yaml.v2"
2022-02-07 15:03:45 +00:00
"github.com/sourcegraph/sourcegraph/lib/errors"
2019-10-01 20:37:38 +00:00
)
2019-10-09 20:53:03 +00:00
var errSilent = errors . New ( "silent error" )
2019-10-01 20:37:38 +00:00
2019-10-09 20:53:03 +00:00
type usageError struct {
Msg string
2019-10-01 20:37:38 +00:00
}
func ( e * usageError ) Error ( ) string {
2019-10-09 20:53:03 +00:00
return e . Msg
2019-10-01 20:37:38 +00:00
}
2020-02-07 20:27:27 +00:00
func explainAddr ( addr string ) string {
2019-10-02 14:35:53 +00:00
_ , port , err := net . SplitHostPort ( addr )
if err != nil {
port = "3434"
}
2019-10-25 20:27:29 +00:00
2020-02-07 20:27:27 +00:00
return fmt . Sprintf ( ` Serving the repositories at http : //%s.
2019-10-25 20:27:29 +00:00
FIRST RUN NOTE : If src - expose has not yet been setup on Sourcegraph , then you
need to configure Sourcegraph to sync with src - expose . Paste the following
configuration as an Other External Service in Sourcegraph :
{
// url is the http url to src-expose (listening on %s)
// url should be reachable by Sourcegraph.
// "http://host.docker.internal:%s" works from Sourcegraph when using Docker for Desktop.
"url" : "http://host.docker.internal:%s" ,
"repos" : [ "src-expose" ] // This may change in versions later than 3.9
}
2020-02-07 20:27:27 +00:00
` , addr , addr , port , port )
}
func explainSnapshotter ( s * Snapshotter ) string {
var dirs [ ] string
for _ , d := range s . Dirs {
dirs = append ( dirs , "- " + d . Dir )
}
return fmt . Sprintf ( "Periodically syncing directories as git repositories to %s.\n%s\n" , s . Destination , strings . Join ( dirs , "\n" ) )
2019-10-02 14:35:53 +00:00
}
2019-10-09 20:53:03 +00:00
func usageErrorOutput ( cmd * ffcli . Command , cmdPath string , err error ) string {
var w strings . Builder
_ , _ = fmt . Fprintf ( & w , "%q %s\nSee '%s --help'.\n" , cmdPath , err . Error ( ) , cmdPath )
if cmd . Usage != "" {
_ , _ = fmt . Fprintf ( & w , "\nUsage: %s\n" , cmd . Usage )
}
if cmd . ShortHelp != "" {
_ , _ = fmt . Fprintf ( & w , "\n%s\n" , cmd . ShortHelp )
}
return w . String ( )
}
2019-10-01 20:37:38 +00:00
2019-10-09 20:53:03 +00:00
func shortenErrHelp ( cmd * ffcli . Command , cmdPath string ) {
// We want to keep the long help, but in the case of exec requesting help we show shorter help output
if cmd . Exec == nil {
return
2019-10-01 20:37:38 +00:00
}
2019-10-09 20:53:03 +00:00
cmdPath = strings . TrimSpace ( cmdPath + " " + cmd . Name )
exec := cmd . Exec
cmd . Exec = func ( args [ ] string ) error {
err := exec ( args )
2024-06-06 13:02:14 +00:00
if errors . HasType [ * usageError ] ( err ) {
2019-10-09 20:53:03 +00:00
var w io . Writer
if cmd . FlagSet != nil {
w = cmd . FlagSet . Output ( )
} else {
w = os . Stderr
}
_ , _ = fmt . Fprint ( w , usageErrorOutput ( cmd , cmdPath , err ) )
return errSilent
}
return err
}
for _ , child := range cmd . Subcommands {
shortenErrHelp ( child , cmdPath )
}
}
func main ( ) {
log . SetPrefix ( "" )
2019-10-01 20:37:38 +00:00
var (
2019-10-09 20:53:03 +00:00
globalFlags = flag . NewFlagSet ( "src-expose" , flag . ExitOnError )
2019-10-25 19:53:21 +00:00
globalQuiet = globalFlags . Bool ( "quiet" , false , "" )
2019-10-09 20:53:03 +00:00
globalVerbose = globalFlags . Bool ( "verbose" , false , "" )
globalBefore = globalFlags . String ( "before" , "" , "A command to run before sync. It is run from the current working directory." )
globalReposDir = globalFlags . String ( "repos-dir" , "" , "src-expose's git directories. src-expose creates a git repo per directory synced. The git repo is then served to Sourcegraph. The repositories are stored and served relative to this directory. Default: ~/.sourcegraph/src-expose-repos" )
globalConfig = globalFlags . String ( "config" , "" , "If set will be used instead of command line arguments to specify configuration." )
2020-03-11 07:25:43 +00:00
globalAddr = globalFlags . String ( "addr" , ":3434" , "address on which to serve (end with : for unused port)" )
2019-10-01 20:37:38 +00:00
)
2019-10-25 19:53:21 +00:00
newLogger := func ( prefix string ) * log . Logger {
if * globalQuiet {
2021-06-07 12:33:02 +00:00
return log . New ( io . Discard , "" , log . LstdFlags )
2020-06-26 09:19:09 +00:00
}
return log . New ( os . Stderr , prefix , log . LstdFlags )
}
newVerbose := func ( prefix string ) * log . Logger {
if ! * globalVerbose {
2021-06-07 12:33:02 +00:00
return log . New ( io . Discard , "" , log . LstdFlags )
2019-10-25 19:53:21 +00:00
}
return log . New ( os . Stderr , prefix , log . LstdFlags )
}
2020-03-13 12:28:39 +00:00
globalSnapshotter := func ( ) ( * Snapshotter , error ) {
2019-10-01 20:37:38 +00:00
var s Snapshotter
2019-10-09 20:53:03 +00:00
if * globalConfig != "" {
2021-06-07 12:33:02 +00:00
b , err := os . ReadFile ( * globalConfig )
2019-10-01 20:37:38 +00:00
if err != nil {
2021-07-12 19:51:38 +00:00
return nil , errors . Errorf ( "could read configuration at %s: %w" , * globalConfig , err )
2019-10-01 20:37:38 +00:00
}
if err := yaml . Unmarshal ( b , & s ) ; err != nil {
2021-07-12 19:51:38 +00:00
return nil , errors . Errorf ( "could not parse configuration at %s: %w" , * globalConfig , err )
2019-10-01 20:37:38 +00:00
}
2020-03-13 12:28:39 +00:00
}
if s . Destination == "" {
s . Destination = * globalReposDir
}
if * globalBefore != "" {
s . Before = * globalBefore
}
return & s , nil
}
parseSnapshotter := func ( args [ ] string ) ( * Snapshotter , error ) {
s , err := globalSnapshotter ( )
if err != nil {
return nil , err
}
if * globalConfig != "" {
if len ( args ) != 0 {
return nil , & usageError { "does not take arguments if --config is specified" }
}
2019-10-01 20:37:38 +00:00
} else {
if len ( args ) == 0 {
2019-10-11 08:38:32 +00:00
return nil , & usageError { "requires at least 1 argument or --config to be specified." }
2019-10-01 20:37:38 +00:00
}
for _ , dir := range args {
2019-10-09 20:53:03 +00:00
s . Dirs = append ( s . Dirs , & SyncDir { Dir : dir } )
2019-10-01 20:37:38 +00:00
}
}
if err := s . SetDefaults ( ) ; err != nil {
return nil , err
}
2020-03-13 12:28:39 +00:00
return s , nil
2019-10-01 20:37:38 +00:00
}
serve := & ffcli . Command {
Name : "serve" ,
Usage : "src-expose [flags] serve [flags] [path/to/dir/containing/git/dirs]" ,
ShortHelp : "Serve git repos for Sourcegraph to list and clone." ,
LongHelp : ` src - expose serve will serve the git repositories over HTTP . These can be git
cloned , and they can be discovered by Sourcegraph .
2020-03-13 12:28:39 +00:00
See "src-expose -h" for the flags that can be passed .
2019-10-09 20:53:03 +00:00
src - expose will default to serving ~ / . sourcegraph / src - expose - repos ` ,
2019-10-01 20:37:38 +00:00
Exec : func ( args [ ] string ) error {
var repoDir string
switch len ( args ) {
case 0 :
2020-03-13 12:28:39 +00:00
s , err := globalSnapshotter ( )
if err != nil {
return err
2019-10-22 06:25:59 +00:00
}
if err := s . SetDefaults ( ) ; err != nil {
return err
}
repoDir = s . Destination
2019-10-01 20:37:38 +00:00
case 1 :
repoDir = args [ 0 ]
default :
2019-10-09 20:53:03 +00:00
return & usageError { "requires zero or one arguments" }
2019-10-01 20:37:38 +00:00
}
2020-06-26 09:19:09 +00:00
s := & Serve {
Addr : * globalAddr ,
Root : repoDir ,
Info : newLogger ( "serve: " ) ,
Debug : newVerbose ( "DBUG serve: " ) ,
}
return s . Start ( )
2019-10-01 20:37:38 +00:00
} ,
}
2019-10-09 20:53:03 +00:00
sync := & ffcli . Command {
Name : "sync" ,
Usage : "src-expose [flags] sync [flags] <src1> [<src2> ...]" ,
ShortHelp : "Do a one-shot sync of directories" ,
2019-10-01 20:37:38 +00:00
Exec : func ( args [ ] string ) error {
2020-03-13 12:28:39 +00:00
s , err := parseSnapshotter ( args )
2019-10-01 20:37:38 +00:00
if err != nil {
return err
}
2019-10-25 19:53:21 +00:00
return s . Run ( newLogger ( "sync: " ) )
2019-10-01 20:37:38 +00:00
} ,
}
root := & ffcli . Command {
2019-10-09 20:53:03 +00:00
Name : "src-expose" ,
Usage : "src-expose [flags] <src1> [<src2> ...]" ,
ShortHelp : "Periodically sync directories src1, src2, ... and serve them." ,
LongHelp : ` Periodically sync directories src1 , src2 , ... and serve them .
2020-03-13 12:28:39 +00:00
See "src-expose -h" for the flags that can be passed .
2019-10-09 20:53:03 +00:00
For more advanced uses specify -- config pointing to a yaml file .
2020-08-05 07:44:51 +00:00
See https : //github.com/sourcegraph/sourcegraph/tree/main/dev/src-expose/examples`,
2019-10-09 20:53:03 +00:00
Subcommands : [ ] * ffcli . Command { serve , sync } ,
2019-10-01 20:37:38 +00:00
FlagSet : globalFlags ,
Exec : func ( args [ ] string ) error {
2020-03-13 12:28:39 +00:00
s , err := parseSnapshotter ( args )
2019-10-09 20:53:03 +00:00
if err != nil {
return err
2019-10-01 20:37:38 +00:00
}
if * globalVerbose {
b , _ := yaml . Marshal ( s )
_ , _ = os . Stdout . Write ( b )
fmt . Println ( )
}
2019-10-25 19:53:21 +00:00
if ! * globalQuiet {
2020-02-07 20:27:27 +00:00
fmt . Println ( explainSnapshotter ( s ) )
fmt . Println ( explainAddr ( * globalAddr ) )
2019-10-25 19:53:21 +00:00
}
2019-10-01 20:37:38 +00:00
go func ( ) {
2020-06-26 09:19:09 +00:00
s := & Serve {
Addr : * globalAddr ,
Root : s . Destination ,
Info : newLogger ( "serve: " ) ,
Debug : newVerbose ( "DBUG serve: " ) ,
}
if err := s . Start ( ) ; err != nil {
2019-10-01 20:37:38 +00:00
log . Fatal ( err )
}
} ( )
2019-10-25 19:53:21 +00:00
logger := newLogger ( "sync: " )
2019-10-01 20:37:38 +00:00
for {
2019-10-25 19:53:21 +00:00
if err := s . Run ( logger ) ; err != nil {
2019-10-01 20:37:38 +00:00
return err
}
time . Sleep ( s . Duration )
}
} ,
}
2019-10-09 20:53:03 +00:00
shortenErrHelp ( root , "" )
2019-10-01 20:37:38 +00:00
if err := root . Run ( os . Args [ 1 : ] ) ; err != nil {
2021-07-12 19:51:38 +00:00
if ! errors . IsAny ( err , flag . ErrHelp , errSilent ) {
2019-10-09 20:53:03 +00:00
_ , _ = fmt . Fprintf ( root . FlagSet . Output ( ) , "\nerror: %v\n" , err )
2019-10-01 20:37:38 +00:00
}
2019-10-09 20:53:03 +00:00
os . Exit ( 1 )
2019-10-01 20:37:38 +00:00
}
}