go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/examples/k8s/helloworld/main.go (about) 1 // Copyright 2019 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "context" 19 "encoding/base64" 20 "fmt" 21 "io" 22 "net/http" 23 "time" 24 25 "github.com/gomodule/redigo/redis" 26 "go.opentelemetry.io/otel" 27 "google.golang.org/protobuf/types/known/emptypb" 28 29 "go.chromium.org/luci/common/logging" 30 "go.chromium.org/luci/grpc/prpc" 31 32 "go.chromium.org/luci/gae/service/datastore" 33 34 "go.chromium.org/luci/server" 35 "go.chromium.org/luci/server/auth" 36 "go.chromium.org/luci/server/gaeemulation" 37 "go.chromium.org/luci/server/gerritauth" 38 "go.chromium.org/luci/server/module" 39 "go.chromium.org/luci/server/redisconn" 40 "go.chromium.org/luci/server/router" 41 "go.chromium.org/luci/server/secrets" 42 43 "go.chromium.org/luci/examples/k8s/helloworld/apipb" 44 ) 45 46 var tracer = otel.Tracer("go.chromium.org/luci/example") 47 48 func main() { 49 // Additional modules that extend the server functionality. 50 modules := []module.Module{ 51 gaeemulation.NewModuleFromFlags(), 52 redisconn.NewModuleFromFlags(), 53 secrets.NewModuleFromFlags(), 54 gerritauth.NewModuleFromFlags(), 55 } 56 57 server.Main(nil, modules, func(srv *server.Server) error { 58 // gRPC example. 59 apipb.RegisterGreeterServer(srv, &greeterServer{}) 60 61 // Logging and tracing example. 62 srv.Routes.GET("/", nil, func(c *router.Context) { 63 logging.Debugf(c.Request.Context(), "Hello debug world") 64 65 ctx, span := tracer.Start(c.Request.Context(), "Testing") 66 logging.Infof(ctx, "Hello info world") 67 time.Sleep(100 * time.Millisecond) 68 span.End() 69 70 logging.Warningf(c.Request.Context(), "Hello warning world") 71 c.Writer.Write([]byte("Hello, world")) 72 73 logging.WithError(fmt.Errorf("boom")).Errorf(c.Request.Context(), "Hello error world") 74 }) 75 76 // Authentication example. 77 mw := router.NewMiddlewareChain(auth.Authenticate( 78 &auth.GoogleOAuth2Method{ 79 Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"}, 80 }, 81 &gerritauth.Method, 82 )) 83 srv.Routes.GET("/who", mw, func(c *router.Context) { 84 logging.Infof(c.Request.Context(), "Authenticated as %s", auth.CurrentIdentity(c.Request.Context())) 85 fmt.Fprintf(c.Writer, "Authenticated as %s\n", auth.CurrentIdentity(c.Request.Context())) 86 if info := gerritauth.GetAssertedInfo(c.Request.Context()); info != nil { 87 fmt.Fprintf(c.Writer, "Gerrit user: %v\n", info.User) 88 fmt.Fprintf(c.Writer, "Gerrit CL: %v\n", info.Change) 89 } 90 }) 91 92 // Allow cross-origin calls, in particular calls using Gerrit auth headers. 93 srv.ConfigurePRPC(func(p *prpc.Server) { 94 p.AccessControl = func(context.Context, string) prpc.AccessControlDecision { 95 return prpc.AccessControlDecision{ 96 AllowCrossOriginRequests: true, 97 AllowCredentials: true, 98 AllowHeaders: []string{gerritauth.Method.Header}, 99 } 100 } 101 }) 102 103 // Redis example. 104 // 105 // To run Redis for tests locally (in particular on OSX): 106 // docker run --name redis -p 6379:6379 --restart always --detach redis 107 // 108 // Then launch the example with "... -redis-addr :6379". 109 // 110 // Note that it makes Redis port available on 0.0.0.0. This is a necessity 111 // when using Docker-for-Mac. Don't put any sensitive stuff there (or make 112 // sure your firewall is configured to block external connections). 113 srv.Routes.GET("/redis", nil, func(c *router.Context) { 114 conn, err := redisconn.Get(c.Request.Context()) 115 if err != nil { 116 http.Error(c.Writer, err.Error(), 500) 117 return 118 } 119 defer conn.Close() 120 n, err := redis.Int(conn.Do("INCR", "testKey")) 121 if err != nil { 122 http.Error(c.Writer, err.Error(), 500) 123 return 124 } 125 fmt.Fprintf(c.Writer, "%d\n", n) 126 }) 127 128 srv.Routes.GET("/inc", nil, func(c *router.Context) { 129 ctx := c.Request.Context() 130 131 ent := TestEntity{ID: "test"} 132 if err := datastore.Get(ctx, &ent); err != nil && err != datastore.ErrNoSuchEntity { 133 http.Error(c.Writer, err.Error(), 500) 134 return 135 } 136 ent.Value += 1 137 if err := datastore.Put(ctx, &ent); err != nil { 138 http.Error(c.Writer, err.Error(), 500) 139 return 140 } 141 142 fmt.Fprintf(c.Writer, "%d\n", ent.Value) 143 }) 144 145 srv.Routes.GET("/sign", nil, func(c *router.Context) { 146 key, sig, err := auth.GetSigner(c.Request.Context()).SignBytes(c.Request.Context(), []byte("test")) 147 if err != nil { 148 http.Error(c.Writer, err.Error(), 500) 149 return 150 } 151 fmt.Fprintf(c.Writer, "Key: %s\nSig: %s\n", key, base64.RawStdEncoding.EncodeToString(sig)) 152 }) 153 154 // Shared http.Client with monitoring instrumentation. 155 tr, err := auth.GetRPCTransport(srv.Context, auth.NoAuth) 156 if err != nil { 157 return err 158 } 159 client := &http.Client{Transport: tr} 160 161 srv.Routes.GET("/example.com", nil, func(c *router.Context) { 162 var blob []byte 163 resp, err := client.Get("http://example.com") 164 if err == nil { 165 defer resp.Body.Close() 166 blob, err = io.ReadAll(resp.Body) 167 } 168 if err != nil { 169 http.Error(c.Writer, err.Error(), 500) 170 return 171 } 172 c.Writer.Write(blob) 173 }) 174 175 return nil 176 }) 177 } 178 179 type TestEntity struct { 180 ID string `gae:"$id"` 181 Value int64 `gae:",noindex"` 182 183 _ datastore.PropertyMap `gae:"-,extra"` 184 } 185 186 type greeterServer struct{} 187 188 func (*greeterServer) SayHi(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) { 189 logging.Infof(ctx, "Hi %s", auth.CurrentIdentity(ctx)) 190 time.Sleep(100 * time.Millisecond) 191 return &emptypb.Empty{}, nil 192 }