github.com/kristofferahl/go-centry@v1.5.0/cmd/centry/serve.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "os" 8 "strings" 9 10 api "github.com/kristofferahl/go-centry/internal/pkg/api" 11 "github.com/kristofferahl/go-centry/internal/pkg/config" 12 "github.com/kristofferahl/go-centry/internal/pkg/io" 13 "github.com/sirupsen/logrus" 14 "github.com/urfave/cli/v2" 15 ) 16 17 // ServeCommand is a Command implementation that applies stuff 18 type ServeCommand struct { 19 Manifest *config.Manifest 20 Log *logrus.Entry 21 } 22 23 // ToCLICommand returns a CLI command 24 func (sc *ServeCommand) ToCLICommand() *cli.Command { 25 return withCommandDefaults(&cli.Command{ 26 Name: "serve", 27 Usage: "Exposes commands over HTTP", 28 UsageText: "", 29 Hidden: false, 30 Action: func(c *cli.Context) error { 31 ec := sc.Run(c.Args().Slice()) 32 if ec > 0 { 33 return cli.Exit("failed to start the server", ec) 34 } 35 return nil 36 }, 37 }) 38 } 39 40 // Run starts an HTTP server and blocks 41 func (sc *ServeCommand) Run(args []string) int { 42 sc.Log.Debugf("serving HTTP api") 43 44 s := api.NewServer(api.Config{ 45 Log: sc.Log, 46 BasicAuth: configureBasicAuth(), 47 }) 48 49 s.Router.HandleFunc("/", sc.indexHandler()).Methods("GET") 50 s.Router.HandleFunc("/commands/", sc.executeHandler()).Methods("POST") 51 52 err := s.RunAndBlock() 53 if err != nil { 54 return 1 55 } 56 57 return 0 58 } 59 60 func configureBasicAuth() *api.BasicAuth { 61 var auth *api.BasicAuth 62 baUsername := os.Getenv("CENTRY_SERVE_USERNAME") 63 baPassword := os.Getenv("CENTRY_SERVE_PASSWORD") 64 65 if baUsername != "" && baPassword != "" { 66 auth = &api.BasicAuth{ 67 Username: baUsername, 68 Password: baPassword, 69 } 70 } 71 72 return auth 73 } 74 75 func (sc *ServeCommand) indexHandler() func(w http.ResponseWriter, r *http.Request) { 76 return func(w http.ResponseWriter, r *http.Request) { 77 statusCode := http.StatusOK 78 response := api.IndexResponse{} 79 80 w.Header().Set("Content-Type", "application/json") 81 w.WriteHeader(statusCode) 82 83 js, err := json.Marshal(response) 84 if err == nil { 85 w.Write(js) 86 } 87 } 88 } 89 90 func (sc *ServeCommand) executeHandler() func(w http.ResponseWriter, r *http.Request) { 91 return func(w http.ResponseWriter, r *http.Request) { 92 statusCode := http.StatusOK 93 response := api.ExecuteResponse{} 94 95 var body api.ExecuteRequest 96 97 decoder := json.NewDecoder(r.Body) 98 err := decoder.Decode(&body) 99 if err != nil { 100 statusCode = http.StatusBadRequest 101 } 102 103 args := []string{} 104 args = append(args, sc.Manifest.Path) 105 args = append(args, strings.Fields(body.Args)...) 106 107 // Build 108 io, buf := io.BufferedCombined() 109 context := NewContext(API, io) 110 111 context.commandEnabledFunc = func(cmd config.Command) bool { 112 serveAnnotation, _ := cmd.Annotation(config.CommandAnnotationAPINamespace, "serve") 113 if serveAnnotation == nil || serveAnnotation.Value != config.TrueString { 114 return false 115 } 116 117 return true 118 } 119 120 context.optionEnabledFunc = func(opt config.Option) bool { 121 serveAnnotation, _ := opt.Annotation(config.CommandAnnotationAPINamespace, "serve") 122 if serveAnnotation == nil || serveAnnotation.Value != config.TrueString { 123 return false 124 } 125 126 return true 127 } 128 129 runtime, err := NewRuntime(args, context) 130 if err != nil { 131 response.Centry = fmt.Sprintf("%s %s", context.manifest.Config.Name, context.manifest.Config.Version) 132 response.Result = fmt.Sprintf("Unable to create runtime %v", err) 133 response.ExitCode = 1 134 sc.Log.Error(response.Result) 135 } else { 136 // Run 137 exitCode := runtime.Execute() 138 139 response.Centry = fmt.Sprintf("%s %s", context.manifest.Config.Name, context.manifest.Config.Version) 140 response.Result = buf.String() 141 response.ExitCode = exitCode 142 } 143 144 w.Header().Set("Content-Type", "application/json") 145 w.WriteHeader(statusCode) 146 147 js, err := json.Marshal(response) 148 if err == nil { 149 w.Write(js) 150 } 151 } 152 }