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  }