diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c531a2e44..0a1269869 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -44,8 +44,8 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 + #- name: Autobuild + # uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -54,7 +54,7 @@ jobs: # and modify them (or add more) to build your code if your project # uses a compiled language - #- run: | + - run: make all # make bootstrap # make release diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml index 53e79d7ba..142575890 100644 --- a/.github/workflows/go-test.yml +++ b/.github/workflows/go-test.yml @@ -8,7 +8,7 @@ jobs: test: strategy: matrix: - go-version: [ 1.20.x ] + go-version: [ 1.21.x ] os: [ ubuntu-latest ] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index e8a5eade6..2db1a4cf4 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/setup-go@v4 with: - go-version: 1.19 + go-version: 1.21 - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 diff --git a/.golangci.yml b/.golangci.yml index d42a24da6..ebfc5505f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -25,7 +25,7 @@ linters: fast: false run: - go: '1.19' + go: '1.21' timeout: 10m skip-dirs: - node_modules @@ -67,7 +67,7 @@ linters-settings: - name: modifies-value-receiver gofumpt: extra-rules: false - lang-version: "1.19" + lang-version: "1.21" issues: max-issues-per-linter: 0 diff --git a/.idea/TUM-Live-Backend.iml b/.idea/TUM-Live-Backend.iml index 8f9c84fa8..fa6749226 100644 --- a/.idea/TUM-Live-Backend.iml +++ b/.idea/TUM-Live-Backend.iml @@ -10,7 +10,9 @@ - + + + diff --git a/api/courseimport.go b/api/courseimport.go index bf6e7377d..023c3fd2a 100644 --- a/api/courseimport.go +++ b/api/courseimport.go @@ -84,6 +84,7 @@ func (r lectureHallRoutes) postSchedule(c *gin.Context) { Streams: nil, Users: nil, Token: token, + StreamKey: strings.ReplaceAll(uuid.NewV4().String(), "-", "")[:15], } var streams []model.Stream @@ -98,12 +99,21 @@ func (r lectureHallRoutes) postSchedule(c *gin.Context) { if err == nil { eventID = uint(eventIDInt) } + + // When defined use course wide stream key + var streamKey string + if course.StreamKey == "" { + streamKey = strings.ReplaceAll(uuid.NewV4().String(), "-", "")[:15] + } else { + streamKey = course.StreamKey + } + streams = append(streams, model.Stream{ Start: event.Start, End: event.End, RoomName: event.RoomName, LectureHallID: lectureHall.ID, - StreamKey: strings.ReplaceAll(uuid.NewV4().String(), "-", "")[:15], + StreamKey: streamKey, TUMOnlineEventID: eventID, }) } diff --git a/api/courses.go b/api/courses.go index 7502d87f5..47ed935de 100644 --- a/api/courses.go +++ b/api/courses.go @@ -73,6 +73,7 @@ func configGinCourseRouter(router *gin.Engine, daoWrapper dao.DaoWrapper) { courses.PUT("/updateDescription/:streamID", routes.updateDescription) courses.DELETE("/deleteLectureSeries/:streamID", routes.deleteLectureSeries) courses.POST("/submitCut", routes.submitCut) + courses.POST("/regenerateKey", routes.regenerateCourseKey) courses.POST("/addUnit", routes.addUnit) courses.POST("/deleteUnit/:unitID", routes.deleteUnit) @@ -1234,8 +1235,13 @@ func (r coursesRoutes) createLecture(c *gin.Context) { for _, date := range req.DateSeries { endTime := date.Add(time.Minute * time.Duration(req.Duration)) - streamKey := uuid.NewV4().String() - streamKey = strings.ReplaceAll(streamKey, "-", "") + // When defined use course wide stream key + var streamKey string + if tumLiveContext.Course.StreamKey == "" { + streamKey = strings.ReplaceAll(uuid.NewV4().String(), "-", "") + } else { + streamKey = tumLiveContext.Course.StreamKey + } lecture := model.Stream{ Name: req.Title, @@ -1391,6 +1397,7 @@ func (r coursesRoutes) createCourse(c *gin.Context) { ChatEnabled: req.EnChat, Visibility: req.Access, Streams: []model.Stream{}, + StreamKey: strings.ReplaceAll(uuid.NewV4().String(), "-", ""), } if tumLiveContext.User.Role != model.AdminType { course.Admins = []model.User{*tumLiveContext.User} @@ -1484,8 +1491,31 @@ func (r coursesRoutes) deleteCourse(c *gin.Context) { dao.Cache.Clear() } +// regenerateCourseKey updates the stream key of the course and updates the key of all the streams +func (r coursesRoutes) regenerateCourseKey(c *gin.Context) { + ctx := c.MustGet("TUMLiveContext").(tools.TUMLiveContext) + course := *ctx.Course + + course.StreamKey = strings.ReplaceAll(uuid.NewV4().String(), "-", "") + err := r.DaoWrapper.CoursesDao.UpdateCourse(c, course) + if err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, "could not update course") + return + } + + for _, s := range course.Streams { + s.StreamKey = course.StreamKey + + err := r.DaoWrapper.StreamsDao.UpdateStream(s) + // Log error but continue on remaining streams + if err != nil { + log.Error("could not modify stream key ", err) + } + } +} + type createCourseRequest struct { - Access string //enrolled, public, hidden or loggedin + Access string //enrolled, public, hidden or logged in CourseID string EnChat bool EnDL bool diff --git a/api/runner_grpc.go b/api/runner_grpc.go new file mode 100644 index 000000000..68373cbe1 --- /dev/null +++ b/api/runner_grpc.go @@ -0,0 +1,73 @@ +package api + +import ( + "context" + "database/sql" + "github.com/TUM-Dev/gocast/dao" + "github.com/TUM-Dev/gocast/model" + log "github.com/sirupsen/logrus" + "github.com/tum-dev/gocast/runner/protobuf" + "google.golang.org/grpc" + "google.golang.org/grpc/keepalive" + "google.golang.org/grpc/reflection" + "net" + "time" +) + +var _ protobuf.FromRunnerServer = (*GrpcRunnerServer)(nil) + +type GrpcRunnerServer struct { + protobuf.UnimplementedFromRunnerServer + + dao.DaoWrapper +} + +func (g GrpcRunnerServer) Register(ctx context.Context, request *protobuf.RegisterRequest) (*protobuf.RegisterResponse, error) { + runner := model.Runner{ + Hostname: request.Hostname, + Port: int(request.Port), + LastSeen: sql.NullTime{Valid: true, Time: time.Now()}, + } + err := g.RunnerDao.Create(ctx, &runner) + if err != nil { + return nil, err + } + return &protobuf.RegisterResponse{}, nil +} + +func (g GrpcRunnerServer) Heartbeat(ctx context.Context, request *protobuf.HeartbeatRequest) (*protobuf.HeartbeatResponse, error) { + //TODO implement me + panic("implement me") +} + +func (g GrpcRunnerServer) RequestSelfStream(ctx context.Context, request *protobuf.SelfStreamRequest) (*protobuf.SelfStreamResponse, error) { + //TODO implement me + panic("implement me") +} + +func (g GrpcRunnerServer) mustEmbedUnimplementedFromRunnerServer() { + //TODO implement me + panic("implement me") +} + +func StartGrpcRunnerServer() { + lis, err := net.Listen("tcp", ":50056") + if err != nil { + log.WithError(err).Error("Failed to init grpc server") + return + } + grpcServer := grpc.NewServer(grpc.KeepaliveParams(keepalive.ServerParameters{ + MaxConnectionIdle: time.Minute, + MaxConnectionAge: time.Minute * 5, + MaxConnectionAgeGrace: time.Second * 5, + Time: time.Minute * 10, + Timeout: time.Second * 20, + })) + protobuf.RegisterFromRunnerServer(grpcServer, &GrpcRunnerServer{DaoWrapper: dao.NewDaoWrapper()}) + reflection.Register(grpcServer) + go func() { + if err = grpcServer.Serve(lis); err != nil { + log.WithError(err).Errorf("Can't serve grpc") + } + }() +} diff --git a/api/stream.go b/api/stream.go index 82b83bb0a..05241aeab 100644 --- a/api/stream.go +++ b/api/stream.go @@ -44,6 +44,8 @@ func configGinStreamRestRouter(router *gin.Engine, daoWrapper dao.DaoWrapper) { streamById.GET("/subtitles/:lang", routes.getSubtitles) streamById.GET("/playlist", routes.getStreamPlaylist) + streamById.POST("/regenerateKey", routes.regenerateKey) + streamById.POST("/restoreKey", routes.restoreKey) thumbs := streamById.Group("/thumbs") { @@ -839,6 +841,33 @@ func (r streamRoutes) updateStreamVisibility(c *gin.Context) { } } +// regenerateKey regenerates the key for a stream. +func (r streamRoutes) regenerateKey(c *gin.Context) { + ctx := c.MustGet("TUMLiveContext").(tools.TUMLiveContext) + stream := *ctx.Stream + + stream.StreamKey = strings.ReplaceAll(uuid.NewV4().String(), "-", "") + err := r.DaoWrapper.StreamsDao.UpdateStream(stream) + if err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, "could not update stream") + return + } +} + +// restoreKey restores the key for a stream to the course key +func (r streamRoutes) restoreKey(c *gin.Context) { + ctx := c.MustGet("TUMLiveContext").(tools.TUMLiveContext) + stream := *ctx.Stream + course := *ctx.Course + + stream.StreamKey = course.StreamKey + err := r.DaoWrapper.StreamsDao.UpdateStream(stream) + if err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, "could not update stream") + return + } +} + func (r streamRoutes) updateChatEnabled(c *gin.Context) { stream, err := r.StreamsDao.GetStreamByID(context.Background(), c.Param("streamID")) if err != nil { diff --git a/api/worker_grpc.go b/api/worker_grpc.go index 0aa754f9e..bb89b1071 100644 --- a/api/worker_grpc.go +++ b/api/worker_grpc.go @@ -19,12 +19,12 @@ import ( "time" go_anel_pwrctrl "github.com/RBG-TUM/go-anel-pwrctrl" - "github.com/getsentry/sentry-go" "github.com/TUM-Dev/gocast/dao" "github.com/TUM-Dev/gocast/model" "github.com/TUM-Dev/gocast/tools" "github.com/TUM-Dev/gocast/tools/camera" "github.com/TUM-Dev/gocast/worker/pb" + "github.com/getsentry/sentry-go" uuid "github.com/satori/go.uuid" log "github.com/sirupsen/logrus" "google.golang.org/grpc" @@ -128,7 +128,7 @@ func (s server) SendSelfStreamRequest(ctx context.Context, request *pb.SelfStrea if request.StreamKey == "" { return nil, errors.New("stream key empty") } - stream, err := s.StreamsDao.GetStreamByKey(ctx, request.StreamKey) + stream, err := s.StreamsDao.GetStreamByKeyAndTime(ctx, request.StreamKey, time.Now()) if err != nil { return nil, err } @@ -136,14 +136,10 @@ func (s server) SendSelfStreamRequest(ctx context.Context, request *pb.SelfStrea if err != nil { return nil, err } - if request.CourseSlug != fmt.Sprintf("%s-%d", course.Slug, stream.ID) { - return nil, fmt.Errorf("bad stream name, should: %s, is: %s", fmt.Sprintf("%s-%d", course.Slug, stream.ID), request.CourseSlug) - } - // reject streams that are more than 30 minutes in the future or more than 30 minutes past - if !(time.Now().After(stream.Start.Add(time.Minute*-30)) && time.Now().Before(stream.End.Add(time.Minute*30))) { - log.WithFields(log.Fields{"streamId": stream.ID}).Warn("Stream rejected, time out of bounds") - return nil, errors.New("stream rejected") + if request.CourseSlug != fmt.Sprintf("%s", course.Slug) { + return nil, fmt.Errorf("bad stream name, should: %s, is: %s", fmt.Sprintf("%s", course.Slug), request.CourseSlug) } + ingestServer, err := s.DaoWrapper.IngestServerDao.GetBestIngestServer() if err != nil { return nil, err diff --git a/cmd/tumlive/tumlive.go b/cmd/tumlive/tumlive.go index 9fa949cc0..533f428c7 100755 --- a/cmd/tumlive/tumlive.go +++ b/cmd/tumlive/tumlive.go @@ -2,17 +2,17 @@ package main import ( "fmt" - "github.com/dgraph-io/ristretto" - "github.com/getsentry/sentry-go" - sentrygin "github.com/getsentry/sentry-go/gin" - "github.com/gin-contrib/gzip" - "github.com/gin-gonic/gin" "github.com/TUM-Dev/gocast/api" "github.com/TUM-Dev/gocast/dao" "github.com/TUM-Dev/gocast/model" "github.com/TUM-Dev/gocast/tools" "github.com/TUM-Dev/gocast/tools/tum" "github.com/TUM-Dev/gocast/web" + "github.com/dgraph-io/ristretto" + "github.com/getsentry/sentry-go" + sentrygin "github.com/getsentry/sentry-go/gin" + "github.com/gin-contrib/gzip" + "github.com/gin-gonic/gin" "github.com/pkg/profile" log "github.com/sirupsen/logrus" "gorm.io/driver/mysql" @@ -32,6 +32,7 @@ type initializer func() var initializers = []initializer{ tools.LoadConfig, api.ServeWorkerGRPC, + api.StartGrpcRunnerServer, tools.InitBranding, } @@ -168,6 +169,7 @@ func main() { &model.Subtitles{}, &model.TranscodingFailure{}, &model.Email{}, + &model.Runner{}, ) if err != nil { sentry.CaptureException(err) diff --git a/dao/dao_base.go b/dao/dao_base.go index fc831d9d0..6f1888853 100644 --- a/dao/dao_base.go +++ b/dao/dao_base.go @@ -36,6 +36,7 @@ type DaoWrapper struct { SubtitlesDao TranscodingFailureDao EmailDao + RunnerDao RunnerDao } func NewDaoWrapper() DaoWrapper { @@ -63,5 +64,6 @@ func NewDaoWrapper() DaoWrapper { SubtitlesDao: NewSubtitlesDao(), TranscodingFailureDao: NewTranscodingFailureDao(), EmailDao: NewEmailDao(), + RunnerDao: NewRunnerDao(), } } diff --git a/dao/runner.go b/dao/runner.go new file mode 100644 index 000000000..682aa1944 --- /dev/null +++ b/dao/runner.go @@ -0,0 +1,47 @@ +package dao + +import ( + "context" + "github.com/TUM-Dev/gocast/model" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +//go:generate mockgen -source=runner.go -destination ../mock_dao/runner.go + +type RunnerDao interface { + // Get Runner by hostname + Get(context.Context, string) (model.Runner, error) + + // Create a new Runner for the database + Create(context.Context, *model.Runner) error + + // Delete a Runner by hostname. + Delete(context.Context, string) error +} + +type runnerDao struct { + db *gorm.DB +} + +func NewRunnerDao() RunnerDao { + return runnerDao{db: DB} +} + +// Get a Runner by id. +func (d runnerDao) Get(c context.Context, hostname string) (res model.Runner, err error) { + return res, DB.WithContext(c).First(&res, "hostname = ?", hostname).Error +} + +// Create a Runner. +func (d runnerDao) Create(c context.Context, it *model.Runner) error { + return DB.WithContext(c).Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "hostname"}}, + UpdateAll: true, + }).Create(&it).Error +} + +// Delete a Runner by hostname. +func (d runnerDao) Delete(c context.Context, hostname string) error { + return DB.WithContext(c).Delete(&model.Runner{}, hostname).Error +} diff --git a/dao/streams.go b/dao/streams.go index b6da68ba5..4b0f434f8 100755 --- a/dao/streams.go +++ b/dao/streams.go @@ -19,7 +19,8 @@ type StreamsDao interface { GetDueStreamsForWorkers() []model.Stream GetDuePremieresForWorkers() []model.Stream - GetStreamByKey(ctx context.Context, key string) (stream model.Stream, err error) + GetStreamByKey(ctx context.Context, key string) (stream model.Stream, err error) //deprecated + GetStreamByKeyAndTime(ctx context.Context, key string, time2 time.Time) (stream model.Stream, err error) GetUnitByID(id string) (model.StreamUnit, error) GetStreamByTumOnlineID(ctx context.Context, id uint) (stream model.Stream, err error) GetStreamsByIds(ids []uint) ([]model.Stream, error) @@ -128,12 +129,32 @@ func (d streamsDao) GetDuePremieresForWorkers() []model.Stream { return res } +// Deprecated: Stream keys are generally no longer unique to +// each stream. Use GetStreamByKeyAndTime instead func (d streamsDao) GetStreamByKey(ctx context.Context, key string) (stream model.Stream, err error) { var res model.Stream err = DB.First(&res, "stream_key = ?", key).Error return res, err } +// GetStreamByKeyAndTime finds stream by key and time t. +// t must be between stream start and end +func (d streamsDao) GetStreamByKeyAndTime(ctx context.Context, key string, t time.Time) (stream model.Stream, err error) { + // first get all streams by key + var streams []model.Stream + result := DB.Where("stream_key = ?", key).Find(&streams) + if result.Error != nil { + return model.Stream{}, err + } + // find stream that encompasses the given time with 30 minutes of padding before and after + for _, s := range streams { + if t.After(stream.Start.Add(time.Minute*-30)) && t.Before(stream.End.Add(time.Minute*30)) { + return s, err + } + } + return model.Stream{}, fmt.Errorf("no stream at %s", t.String()) +} + func (d streamsDao) GetUnitByID(id string) (model.StreamUnit, error) { var unit model.StreamUnit err := DB.First(&unit, "id = ?", id).Error @@ -321,7 +342,9 @@ func (d streamsDao) UpdateStream(stream model.Stream) error { "description": stream.Description, "start": stream.Start, "end": stream.End, - "chat_enabled": stream.ChatEnabled}).Error + "chat_enabled": stream.ChatEnabled, + "stream_key": stream.StreamKey, + }).Error return err } diff --git a/docs/static/tum-live-starter.sql b/docs/static/tum-live-starter.sql index fcd1ae2ad..60fc3cdcd 100644 --- a/docs/static/tum-live-starter.sql +++ b/docs/static/tum-live-starter.sql @@ -244,6 +244,7 @@ CREATE TABLE `courses` ( `vod_chat_enabled` tinyint(1) DEFAULT NULL, `visibility` varchar(191) DEFAULT 'loggedin', `token` longtext DEFAULT NULL, + `streamKey` longtext, `user_created_by_token` tinyint(1) DEFAULT 0, `camera_preset_preferences` longtext DEFAULT NULL, PRIMARY KEY (`id`), @@ -257,7 +258,7 @@ CREATE TABLE `courses` ( LOCK TABLES `courses` WRITE; /*!40000 ALTER TABLE `courses` DISABLE KEYS */; -INSERT INTO `courses` VALUES (1,'2022-04-18 13:40:05.843','2022-04-18 13:46:46.546',NULL,1,'Einführung Brauereiwesen','brauereiwesen',2022,'S','',1,1,1,0,1,0,0,'public','',0,''),(2,'2022-04-18 13:40:54.686','2022-04-18 13:40:54.698',NULL,1,'Spieleentwicklung für Dummies','games101',2022,'S','',1,1,1,0,1,0,0,'loggedin','',0,''),(3,'2022-04-18 13:41:55.741','2022-04-18 13:41:55.754',NULL,1,'Praktikum: Golang','godev',2021,'W','',1,1,1,0,1,0,0,'public','',0,''); +INSERT INTO `courses` VALUES (1,'2022-04-18 13:40:05.843','2022-04-18 13:46:46.546',NULL,1,'Einführung Brauereiwesen','brauereiwesen',2022,'S','',1,1,1,0,1,0,0,'public','', 'ba09dd459e50476da90864fecfa7ae14',0,''),(2,'2022-04-18 13:40:54.686','2022-04-18 13:40:54.698',NULL,1,'Spieleentwicklung für Dummies','games101',2022,'S','',1,1,1,0,1,0,0,'loggedin','','6fe65fe1be4946b68983db45beb7d28f',0,''),(3,'2022-04-18 13:41:55.741','2022-04-18 13:41:55.754',NULL,1,'Praktikum: Golang','godev',2021,'W','',1,1,1,0,1,0,0,'public','','48011344a82249baad57f1a7b17f28ec',0,''); /*!40000 ALTER TABLE `courses` ENABLE KEYS */; UNLOCK TABLES; diff --git a/go.mod b/go.mod index dd030c58c..48bc27529 100644 --- a/go.mod +++ b/go.mod @@ -70,7 +70,9 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.48.0 // indirect golang.org/x/arch v0.4.0 // indirect + golang.org/x/mod v0.10.0 // indirect golang.org/x/oauth2 v0.11.0 // indirect + golang.org/x/tools v0.6.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230814215434-ca7cfce7776a // indirect ) diff --git a/go.sum b/go.sum index 1b9e9cfab..b5f1e9110 100644 --- a/go.sum +++ b/go.sum @@ -490,6 +490,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -681,6 +683,7 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/go.work b/go.work index 61b4b2a9a..74bb747de 100644 --- a/go.work +++ b/go.work @@ -1,8 +1,9 @@ -go 1.19 +go 1.21.1 use ( . ./worker ./worker/edge + runner vod-service ) diff --git a/go.work.sum b/go.work.sum index 542938353..b3668fae9 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,17 +1,254 @@ -cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= -cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= -cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= +cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= +cloud.google.com/go v0.110.6 h1:8uYAkj3YHTP/1iwReuHPxLSbdcyc+dSBbzFMrVwDR6Q= +cloud.google.com/go v0.110.6/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go/accessapproval v1.7.1 h1:/5YjNhR6lzCvmJZAnByYkfEgWjfAKwYP6nkuTk6nKFE= +cloud.google.com/go/accessapproval v1.7.1/go.mod h1:JYczztsHRMK7NTXb6Xw+dwbs/WnOJxbo/2mTI+Kgg68= +cloud.google.com/go/accesscontextmanager v1.8.1 h1:WIAt9lW9AXtqw/bnvrEUaE8VG/7bAAeMzRCBGMkc4+w= +cloud.google.com/go/accesscontextmanager v1.8.1/go.mod h1:JFJHfvuaTC+++1iL1coPiG1eu5D24db2wXCDWDjIrxo= +cloud.google.com/go/aiplatform v1.48.0 h1:M5davZWCTzE043rJCn+ZLW6hSxfG1KAx4vJTtas2/ec= +cloud.google.com/go/aiplatform v1.48.0/go.mod h1:Iu2Q7sC7QGhXUeOhAj/oCK9a+ULz1O4AotZiqjQ8MYA= +cloud.google.com/go/analytics v0.21.3 h1:TFBC1ZAqX9/jL56GEXdLrVe5vT3I22bDVWyDwZX4IEg= +cloud.google.com/go/analytics v0.21.3/go.mod h1:U8dcUtmDmjrmUTnnnRnI4m6zKn/yaA5N9RlEkYFHpQo= +cloud.google.com/go/apigateway v1.6.1 h1:aBSwCQPcp9rZ0zVEUeJbR623palnqtvxJlUyvzsKGQc= +cloud.google.com/go/apigateway v1.6.1/go.mod h1:ufAS3wpbRjqfZrzpvLC2oh0MFlpRJm2E/ts25yyqmXA= +cloud.google.com/go/apigeeconnect v1.6.1 h1:6u/jj0P2c3Mcm+H9qLsXI7gYcTiG9ueyQL3n6vCmFJM= +cloud.google.com/go/apigeeconnect v1.6.1/go.mod h1:C4awq7x0JpLtrlQCr8AzVIzAaYgngRqWf9S5Uhg+wWs= +cloud.google.com/go/apigeeregistry v0.7.1 h1:hgq0ANLDx7t2FDZDJQrCMtCtddR/pjCqVuvQWGrQbXw= +cloud.google.com/go/apigeeregistry v0.7.1/go.mod h1:1XgyjZye4Mqtw7T9TsY4NW10U7BojBvG4RMD+vRDrIw= +cloud.google.com/go/appengine v1.8.1 h1:J+aaUZ6IbTpBegXbmEsh8qZZy864ZVnOoWyfa1XSNbI= +cloud.google.com/go/appengine v1.8.1/go.mod h1:6NJXGLVhZCN9aQ/AEDvmfzKEfoYBlfB80/BHiKVputY= +cloud.google.com/go/area120 v0.8.1 h1:wiOq3KDpdqXmaHzvZwKdpoM+3lDcqsI2Lwhyac7stss= +cloud.google.com/go/area120 v0.8.1/go.mod h1:BVfZpGpB7KFVNxPiQBuHkX6Ed0rS51xIgmGyjrAfzsg= +cloud.google.com/go/artifactregistry v1.14.1 h1:k6hNqab2CubhWlGcSzunJ7kfxC7UzpAfQ1UPb9PDCKI= +cloud.google.com/go/artifactregistry v1.14.1/go.mod h1:nxVdG19jTaSTu7yA7+VbWL346r3rIdkZ142BSQqhn5E= +cloud.google.com/go/asset v1.14.1 h1:vlHdznX70eYW4V1y1PxocvF6tEwxJTTarwIGwOhFF3U= +cloud.google.com/go/asset v1.14.1/go.mod h1:4bEJ3dnHCqWCDbWJ/6Vn7GVI9LerSi7Rfdi03hd+WTQ= +cloud.google.com/go/assuredworkloads v1.11.1 h1:yaO0kwS+SnhVSTF7BqTyVGt3DTocI6Jqo+S3hHmCwNk= +cloud.google.com/go/assuredworkloads v1.11.1/go.mod h1:+F04I52Pgn5nmPG36CWFtxmav6+7Q+c5QyJoL18Lry0= +cloud.google.com/go/automl v1.13.1 h1:iP9iQurb0qbz+YOOMfKSEjhONA/WcoOIjt6/m+6pIgo= +cloud.google.com/go/automl v1.13.1/go.mod h1:1aowgAHWYZU27MybSCFiukPO7xnyawv7pt3zK4bheQE= +cloud.google.com/go/baremetalsolution v1.1.1 h1:0Ge9PQAy6cZ1tRrkc44UVgYV15nw2TVnzJzYsMHXF+E= +cloud.google.com/go/baremetalsolution v1.1.1/go.mod h1:D1AV6xwOksJMV4OSlWHtWuFNZZYujJknMAP4Qa27QIA= +cloud.google.com/go/batch v1.3.1 h1:uE0Q//W7FOGPjf7nuPiP0zoE8wOT3ngoIO2HIet0ilY= +cloud.google.com/go/batch v1.3.1/go.mod h1:VguXeQKXIYaeeIYbuozUmBR13AfL4SJP7IltNPS+A4A= +cloud.google.com/go/beyondcorp v1.0.0 h1:VPg+fZXULQjs8LiMeWdLaB5oe8G9sEoZ0I0j6IMiG1Q= +cloud.google.com/go/beyondcorp v1.0.0/go.mod h1:YhxDWw946SCbmcWo3fAhw3V4XZMSpQ/VYfcKGAEU8/4= +cloud.google.com/go/bigquery v1.53.0 h1:K3wLbjbnSlxhuG5q4pntHv5AEbQM1QqHKGYgwFIqOTg= +cloud.google.com/go/bigquery v1.53.0/go.mod h1:3b/iXjRQGU4nKa87cXeg6/gogLjO8C6PmuM8i5Bi/u4= +cloud.google.com/go/billing v1.16.0 h1:1iktEAIZ2uA6KpebC235zi/rCXDdDYQ0bTXTNetSL80= +cloud.google.com/go/billing v1.16.0/go.mod h1:y8vx09JSSJG02k5QxbycNRrN7FGZB6F3CAcgum7jvGA= +cloud.google.com/go/binaryauthorization v1.6.1 h1:cAkOhf1ic92zEN4U1zRoSupTmwmxHfklcp1X7CCBKvE= +cloud.google.com/go/binaryauthorization v1.6.1/go.mod h1:TKt4pa8xhowwffiBmbrbcxijJRZED4zrqnwZ1lKH51U= +cloud.google.com/go/certificatemanager v1.7.1 h1:uKsohpE0hiobx1Eak9jNcPCznwfB6gvyQCcS28Ah9E8= +cloud.google.com/go/certificatemanager v1.7.1/go.mod h1:iW8J3nG6SaRYImIa+wXQ0g8IgoofDFRp5UMzaNk1UqI= +cloud.google.com/go/channel v1.16.0 h1:dqRkK2k7Ll/HHeYGxv18RrfhozNxuTJRkspW0iaFZoY= +cloud.google.com/go/channel v1.16.0/go.mod h1:eN/q1PFSl5gyu0dYdmxNXscY/4Fi7ABmeHCJNf/oHmc= +cloud.google.com/go/cloudbuild v1.13.0 h1:YBbAWcvE4x6xPWTyS+OU4eiUpz5rCS3VCM/aqmfddPA= +cloud.google.com/go/cloudbuild v1.13.0/go.mod h1:lyJg7v97SUIPq4RC2sGsz/9tNczhyv2AjML/ci4ulzU= +cloud.google.com/go/clouddms v1.6.1 h1:rjR1nV6oVf2aNNB7B5uz1PDIlBjlOiBgR+q5n7bbB7M= +cloud.google.com/go/clouddms v1.6.1/go.mod h1:Ygo1vL52Ov4TBZQquhz5fiw2CQ58gvu+PlS6PVXCpZI= +cloud.google.com/go/cloudtasks v1.12.1 h1:cMh9Q6dkvh+Ry5LAPbD/U2aw6KAqdiU6FttwhbTo69w= +cloud.google.com/go/cloudtasks v1.12.1/go.mod h1:a9udmnou9KO2iulGscKR0qBYjreuX8oHwpmFsKspEvM= +cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= -cloud.google.com/go/firestore v1.9.0 h1:IBlRyxgGySXu5VuW0RgGFlTtLukSnNkpDiEOMkQkmpA= +cloud.google.com/go/contactcenterinsights v1.10.0 h1:YR2aPedGVQPpFBZXJnPkqRj8M//8veIZZH5ZvICoXnI= +cloud.google.com/go/contactcenterinsights v1.10.0/go.mod h1:bsg/R7zGLYMVxFFzfh9ooLTruLRCG9fnzhH9KznHhbM= +cloud.google.com/go/container v1.24.0 h1:N51t/cgQJFqDD/W7Mb+IvmAPHrf8AbPx7Bb7aF4lROE= +cloud.google.com/go/container v1.24.0/go.mod h1:lTNExE2R7f+DLbAN+rJiKTisauFCaoDq6NURZ83eVH4= +cloud.google.com/go/containeranalysis v0.10.1 h1:SM/ibWHWp4TYyJMwrILtcBtYKObyupwOVeceI9pNblw= +cloud.google.com/go/containeranalysis v0.10.1/go.mod h1:Ya2jiILITMY68ZLPaogjmOMNkwsDrWBSTyBubGXO7j0= +cloud.google.com/go/datacatalog v1.16.0 h1:qVeQcw1Cz93/cGu2E7TYUPh8Lz5dn5Ws2siIuQ17Vng= +cloud.google.com/go/datacatalog v1.16.0/go.mod h1:d2CevwTG4yedZilwe+v3E3ZBDRMobQfSG/a6cCCN5R4= +cloud.google.com/go/dataflow v0.9.1 h1:VzG2tqsk/HbmOtq/XSfdF4cBvUWRK+S+oL9k4eWkENQ= +cloud.google.com/go/dataflow v0.9.1/go.mod h1:Wp7s32QjYuQDWqJPFFlnBKhkAtiFpMTdg00qGbnIHVw= +cloud.google.com/go/dataform v0.8.1 h1:xcWso0hKOoxeW72AjBSIp/UfkvpqHNzzS0/oygHlcqY= +cloud.google.com/go/dataform v0.8.1/go.mod h1:3BhPSiw8xmppbgzeBbmDvmSWlwouuJkXsXsb8UBih9M= +cloud.google.com/go/datafusion v1.7.1 h1:eX9CZoyhKQW6g1Xj7+RONeDj1mV8KQDKEB9KLELX9/8= +cloud.google.com/go/datafusion v1.7.1/go.mod h1:KpoTBbFmoToDExJUso/fcCiguGDk7MEzOWXUsJo0wsI= +cloud.google.com/go/datalabeling v0.8.1 h1:zxsCD/BLKXhNuRssen8lVXChUj8VxF3ofN06JfdWOXw= +cloud.google.com/go/datalabeling v0.8.1/go.mod h1:XS62LBSVPbYR54GfYQsPXZjTW8UxCK2fkDciSrpRFdY= +cloud.google.com/go/dataplex v1.9.0 h1:yoBWuuUZklYp7nx26evIhzq8+i/nvKYuZr1jka9EqLs= +cloud.google.com/go/dataplex v1.9.0/go.mod h1:7TyrDT6BCdI8/38Uvp0/ZxBslOslP2X2MPDucliyvSE= +cloud.google.com/go/dataproc v1.12.0 h1:W47qHL3W4BPkAIbk4SWmIERwsWBaNnWm0P2sdx3YgGU= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataproc/v2 v2.0.1 h1:4OpSiPMMGV3XmtPqskBU/RwYpj3yMFjtMLj/exi425Q= +cloud.google.com/go/dataproc/v2 v2.0.1/go.mod h1:7Ez3KRHdFGcfY7GcevBbvozX+zyWGcwLJvvAMwCaoZ4= +cloud.google.com/go/dataqna v0.8.1 h1:ITpUJep04hC9V7C+gcK390HO++xesQFSUJ7S4nSnF3U= +cloud.google.com/go/dataqna v0.8.1/go.mod h1:zxZM0Bl6liMePWsHA8RMGAfmTG34vJMapbHAxQ5+WA8= +cloud.google.com/go/datastore v1.13.0 h1:ktbC66bOQB3HJPQe8qNI1/aiQ77PMu7hD4mzE6uxe3w= +cloud.google.com/go/datastore v1.13.0/go.mod h1:KjdB88W897MRITkvWWJrg2OUtrR5XVj1EoLgSp6/N70= +cloud.google.com/go/datastream v1.10.0 h1:ra/+jMv36zTAGPfi8TRne1hXme+UsKtdcK4j6bnqQiw= +cloud.google.com/go/datastream v1.10.0/go.mod h1:hqnmr8kdUBmrnk65k5wNRoHSCYksvpdZIcZIEl8h43Q= +cloud.google.com/go/deploy v1.13.0 h1:A+w/xpWgz99EYzB6e31gMGAI/P5jTZ2UO7veQK5jQ8o= +cloud.google.com/go/deploy v1.13.0/go.mod h1:tKuSUV5pXbn67KiubiUNUejqLs4f5cxxiCNCeyl0F2g= +cloud.google.com/go/dialogflow v1.40.0 h1:sCJbaXt6ogSbxWQnERKAzos57f02PP6WkGbOZvXUdwc= +cloud.google.com/go/dialogflow v1.40.0/go.mod h1:L7jnH+JL2mtmdChzAIcXQHXMvQkE3U4hTaNltEuxXn4= +cloud.google.com/go/dlp v1.10.1 h1:tF3wsJ2QulRhRLWPzWVkeDz3FkOGVoMl6cmDUHtfYxw= +cloud.google.com/go/dlp v1.10.1/go.mod h1:IM8BWz1iJd8njcNcG0+Kyd9OPnqnRNkDV8j42VT5KOI= +cloud.google.com/go/documentai v1.22.0 h1:dW8ex9yb3oT9s1yD2+yLcU8Zq15AquRZ+wd0U+TkxFw= +cloud.google.com/go/documentai v1.22.0/go.mod h1:yJkInoMcK0qNAEdRnqY/D5asy73tnPe88I1YTZT+a8E= +cloud.google.com/go/domains v0.9.1 h1:rqz6KY7mEg7Zs/69U6m6LMbB7PxFDWmT3QWNXIqhHm0= +cloud.google.com/go/domains v0.9.1/go.mod h1:aOp1c0MbejQQ2Pjf1iJvnVyT+z6R6s8pX66KaCSDYfE= +cloud.google.com/go/edgecontainer v1.1.1 h1:zhHWnLzg6AqzE+I3gzJqiIwHfjEBhWctNQEzqb+FaRo= +cloud.google.com/go/edgecontainer v1.1.1/go.mod h1:O5bYcS//7MELQZs3+7mabRqoWQhXCzenBu0R8bz2rwk= +cloud.google.com/go/errorreporting v0.3.0 h1:kj1XEWMu8P0qlLhm3FwcaFsUvXChV/OraZwA70trRR0= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.6.2 h1:OEJ0MLXXCW/tX1fkxzEZOsv/wRfyFsvDVNaHWBAvoV0= +cloud.google.com/go/essentialcontacts v1.6.2/go.mod h1:T2tB6tX+TRak7i88Fb2N9Ok3PvY3UNbUsMag9/BARh4= +cloud.google.com/go/eventarc v1.13.0 h1:xIP3XZi0Xawx8DEfh++mE2lrIi5kQmCr/KcWhJ1q0J4= +cloud.google.com/go/eventarc v1.13.0/go.mod h1:mAFCW6lukH5+IZjkvrEss+jmt2kOdYlN8aMx3sRJiAI= +cloud.google.com/go/filestore v1.7.1 h1:Eiz8xZzMJc5ppBWkuaod/PUdUZGCFR8ku0uS+Ah2fRw= +cloud.google.com/go/filestore v1.7.1/go.mod h1:y10jsorq40JJnjR/lQ8AfFbbcGlw3g+Dp8oN7i7FjV4= cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= -cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= +cloud.google.com/go/firestore v1.11.0 h1:PPgtwcYUOXV2jFe1bV3nda3RCrOa8cvBjTOn2MQVfW8= +cloud.google.com/go/firestore v1.11.0/go.mod h1:b38dKhgzlmNNGTNZZwe7ZRFEuRab1Hay3/DBsIGKKy4= +cloud.google.com/go/functions v1.15.1 h1:LtAyqvO1TFmNLcROzHZhV0agEJfBi+zfMZsF4RT/a7U= +cloud.google.com/go/functions v1.15.1/go.mod h1:P5yNWUTkyU+LvW/S9O6V+V423VZooALQlqoXdoPz5AE= +cloud.google.com/go/gaming v1.10.1 h1:5qZmZEWzMf8GEFgm9NeC3bjFRpt7x4S6U7oLbxaf7N8= +cloud.google.com/go/gaming v1.10.1/go.mod h1:XQQvtfP8Rb9Rxnxm5wFVpAp9zCQkJi2bLIb7iHGwB3s= +cloud.google.com/go/gkebackup v1.3.0 h1:lgyrpdhtJKV7l1GM15YFt+OCyHMxsQZuSydyNmS0Pxo= +cloud.google.com/go/gkebackup v1.3.0/go.mod h1:vUDOu++N0U5qs4IhG1pcOnD1Mac79xWy6GoBFlWCWBU= +cloud.google.com/go/gkeconnect v0.8.1 h1:a1ckRvVznnuvDWESM2zZDzSVFvggeBaVY5+BVB8tbT0= +cloud.google.com/go/gkeconnect v0.8.1/go.mod h1:KWiK1g9sDLZqhxB2xEuPV8V9NYzrqTUmQR9shJHpOZw= +cloud.google.com/go/gkehub v0.14.1 h1:2BLSb8i+Co1P05IYCKATXy5yaaIw/ZqGvVSBTLdzCQo= +cloud.google.com/go/gkehub v0.14.1/go.mod h1:VEXKIJZ2avzrbd7u+zeMtW00Y8ddk/4V9511C9CQGTY= +cloud.google.com/go/gkemulticloud v1.0.0 h1:MluqhtPVZReoriP5+adGIw+ij/RIeRik8KApCW2WMTw= +cloud.google.com/go/gkemulticloud v1.0.0/go.mod h1:kbZ3HKyTsiwqKX7Yw56+wUGwwNZViRnxWK2DVknXWfw= +cloud.google.com/go/grafeas v0.3.0 h1:oyTL/KjiUeBs9eYLw/40cpSZglUC+0F7X4iu/8t7NWs= +cloud.google.com/go/grafeas v0.3.0/go.mod h1:P7hgN24EyONOTMyeJH6DxG4zD7fwiYa5Q6GUgyFSOU8= +cloud.google.com/go/gsuiteaddons v1.6.1 h1:mi9jxZpzVjLQibTS/XfPZvl+Jr6D5Bs8pGqUjllRb00= +cloud.google.com/go/gsuiteaddons v1.6.1/go.mod h1:CodrdOqRZcLp5WOwejHWYBjZvfY0kOphkAKpF/3qdZY= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= +cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +cloud.google.com/go/iap v1.8.1 h1:X1tcp+EoJ/LGX6cUPt3W2D4H2Kbqq0pLAsldnsCjLlE= +cloud.google.com/go/iap v1.8.1/go.mod h1:sJCbeqg3mvWLqjZNsI6dfAtbbV1DL2Rl7e1mTyXYREQ= +cloud.google.com/go/ids v1.4.1 h1:khXYmSoDDhWGEVxHl4c4IgbwSRR+qE/L4hzP3vaU9Hc= +cloud.google.com/go/ids v1.4.1/go.mod h1:np41ed8YMU8zOgv53MMMoCntLTn2lF+SUzlM+O3u/jw= +cloud.google.com/go/iot v1.7.1 h1:yrH0OSmicD5bqGBoMlWG8UltzdLkYzNUwNVUVz7OT54= +cloud.google.com/go/iot v1.7.1/go.mod h1:46Mgw7ev1k9KqK1ao0ayW9h0lI+3hxeanz+L1zmbbbk= +cloud.google.com/go/kms v1.15.0 h1:xYl5WEaSekKYN5gGRyhjvZKM22GVBBCzegGNVPy+aIs= +cloud.google.com/go/kms v1.15.0/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N/2CDM= +cloud.google.com/go/language v1.10.1 h1:3MXeGEv8AlX+O2LyV4pO4NGpodanc26AmXwOuipEym0= +cloud.google.com/go/language v1.10.1/go.mod h1:CPp94nsdVNiQEt1CNjF5WkTcisLiHPyIbMhvR8H2AW0= +cloud.google.com/go/lifesciences v0.9.1 h1:axkANGx1wiBXHiPcJZAE+TDjjYoJRIDzbHC/WYllCBU= +cloud.google.com/go/lifesciences v0.9.1/go.mod h1:hACAOd1fFbCGLr/+weUKRAJas82Y4vrL3O5326N//Wc= +cloud.google.com/go/logging v1.7.0 h1:CJYxlNNNNAMkHp9em/YEXcfJg+rPDg7YfwoRpMU+t5I= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= -cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= -cloud.google.com/go/storage v1.14.0 h1:6RRlFMv1omScs6iq2hfE3IvgE+l6RfJPampq8UZc5TU= +cloud.google.com/go/longrunning v0.5.0/go.mod h1:0JNuqRShmscVAhIACGtskSAWtqtOoPkwP0YF1oVEchc= +cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI= +cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc= +cloud.google.com/go/managedidentities v1.6.1 h1:2/qZuOeLgUHorSdxSQGtnOu9xQkBn37+j+oZQv/KHJY= +cloud.google.com/go/managedidentities v1.6.1/go.mod h1:h/irGhTN2SkZ64F43tfGPMbHnypMbu4RB3yl8YcuEak= +cloud.google.com/go/maps v1.4.0 h1:PdfgpBLhAoSzZrQXP+/zBc78fIPLZSJp5y8+qSMn2UU= +cloud.google.com/go/maps v1.4.0/go.mod h1:6mWTUv+WhnOwAgjVsSW2QPPECmW+s3PcRyOa9vgG/5s= +cloud.google.com/go/mediatranslation v0.8.1 h1:50cF7c1l3BanfKrpnTCaTvhf+Fo6kdF21DG0byG7gYU= +cloud.google.com/go/mediatranslation v0.8.1/go.mod h1:L/7hBdEYbYHQJhX2sldtTO5SZZ1C1vkapubj0T2aGig= +cloud.google.com/go/memcache v1.10.1 h1:7lkLsF0QF+Mre0O/NvkD9Q5utUNwtzvIYjrOLOs0HO0= +cloud.google.com/go/memcache v1.10.1/go.mod h1:47YRQIarv4I3QS5+hoETgKO40InqzLP6kpNLvyXuyaA= +cloud.google.com/go/metastore v1.12.0 h1:+9DsxUOHvsqvC0ylrRc/JwzbXJaaBpfIK3tX0Lx8Tcc= +cloud.google.com/go/metastore v1.12.0/go.mod h1:uZuSo80U3Wd4zi6C22ZZliOUJ3XeM/MlYi/z5OAOWRA= +cloud.google.com/go/monitoring v1.15.1 h1:65JhLMd+JiYnXr6j5Z63dUYCuOg770p8a/VC+gil/58= +cloud.google.com/go/monitoring v1.15.1/go.mod h1:lADlSAlFdbqQuwwpaImhsJXu1QSdd3ojypXrFSMr2rM= +cloud.google.com/go/networkconnectivity v1.12.1 h1:LnrYM6lBEeTq+9f2lR4DjBhv31EROSAQi/P5W4Q0AEc= +cloud.google.com/go/networkconnectivity v1.12.1/go.mod h1:PelxSWYM7Sh9/guf8CFhi6vIqf19Ir/sbfZRUwXh92E= +cloud.google.com/go/networkmanagement v1.8.0 h1:/3xP37eMxnyvkfLrsm1nv1b2FbMMSAEAOlECTvoeCq4= +cloud.google.com/go/networkmanagement v1.8.0/go.mod h1:Ho/BUGmtyEqrttTgWEe7m+8vDdK74ibQc+Be0q7Fof0= +cloud.google.com/go/networksecurity v0.9.1 h1:TBLEkMp3AE+6IV/wbIGRNTxnqLXHCTEQWoxRVC18TzY= +cloud.google.com/go/networksecurity v0.9.1/go.mod h1:MCMdxOKQ30wsBI1eI659f9kEp4wuuAueoC9AJKSPWZQ= +cloud.google.com/go/notebooks v1.9.1 h1:CUqMNEtv4EHFnbogV+yGHQH5iAQLmijOx191innpOcs= +cloud.google.com/go/notebooks v1.9.1/go.mod h1:zqG9/gk05JrzgBt4ghLzEepPHNwE5jgPcHZRKhlC1A8= +cloud.google.com/go/optimization v1.4.1 h1:pEwOAmO00mxdbesCRSsfj8Sd4rKY9kBrYW7Vd3Pq7cA= +cloud.google.com/go/optimization v1.4.1/go.mod h1:j64vZQP7h9bO49m2rVaTVoNM0vEBEN5eKPUPbZyXOrk= +cloud.google.com/go/orchestration v1.8.1 h1:KmN18kE/xa1n91cM5jhCh7s1/UfIguSCisw7nTMUzgE= +cloud.google.com/go/orchestration v1.8.1/go.mod h1:4sluRF3wgbYVRqz7zJ1/EUNc90TTprliq9477fGobD8= +cloud.google.com/go/orgpolicy v1.11.1 h1:I/7dHICQkNwym9erHqmlb50LRU588NPCvkfIY0Bx9jI= +cloud.google.com/go/orgpolicy v1.11.1/go.mod h1:8+E3jQcpZJQliP+zaFfayC2Pg5bmhuLK755wKhIIUCE= +cloud.google.com/go/osconfig v1.12.1 h1:dgyEHdfqML6cUW6/MkihNdTVc0INQst0qSE8Ou1ub9c= +cloud.google.com/go/osconfig v1.12.1/go.mod h1:4CjBxND0gswz2gfYRCUoUzCm9zCABp91EeTtWXyz0tE= +cloud.google.com/go/oslogin v1.10.1 h1:LdSuG3xBYu2Sgr3jTUULL1XCl5QBx6xwzGqzoDUw1j0= +cloud.google.com/go/oslogin v1.10.1/go.mod h1:x692z7yAue5nE7CsSnoG0aaMbNoRJRXO4sn73R+ZqAs= +cloud.google.com/go/phishingprotection v0.8.1 h1:aK/lNmSd1vtbft/vLe2g7edXK72sIQbqr2QyrZN/iME= +cloud.google.com/go/phishingprotection v0.8.1/go.mod h1:AxonW7GovcA8qdEk13NfHq9hNx5KPtfxXNeUxTDxB6I= +cloud.google.com/go/policytroubleshooter v1.8.0 h1:XTMHy31yFmXgQg57CB3w9YQX8US7irxDX0Fl0VwlZyY= +cloud.google.com/go/policytroubleshooter v1.8.0/go.mod h1:tmn5Ir5EToWe384EuboTcVQT7nTag2+DuH3uHmKd1HU= +cloud.google.com/go/privatecatalog v0.9.1 h1:B/18xGo+E0EMS9LOEQ0zXz7F2asMgmVgTYGSI89MHOA= +cloud.google.com/go/privatecatalog v0.9.1/go.mod h1:0XlDXW2unJXdf9zFz968Hp35gl/bhF4twwpXZAW50JA= +cloud.google.com/go/pubsub v1.33.0 h1:6SPCPvWav64tj0sVX/+npCBKhUi/UjJehy9op/V3p2g= +cloud.google.com/go/pubsub v1.33.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc= +cloud.google.com/go/pubsublite v1.8.1 h1:pX+idpWMIH30/K7c0epN6V703xpIcMXWRjKJsz0tYGY= +cloud.google.com/go/pubsublite v1.8.1/go.mod h1:fOLdU4f5xldK4RGJrBMm+J7zMWNj/k4PxwEZXy39QS0= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.2 h1:IGkbudobsTXAwmkEYOzPCQPApUCsN4Gbq3ndGVhHQpI= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.2/go.mod h1:kR0KjsJS7Jt1YSyWFkseQ756D45kaYNTlDPPaRAvDBU= +cloud.google.com/go/recommendationengine v0.8.1 h1:nMr1OEVHuDambRn+/y4RmNAmnR/pXCuHtH0Y4tCgGRQ= +cloud.google.com/go/recommendationengine v0.8.1/go.mod h1:MrZihWwtFYWDzE6Hz5nKcNz3gLizXVIDI/o3G1DLcrE= +cloud.google.com/go/recommender v1.10.1 h1:UKp94UH5/Lv2WXSQe9+FttqV07x/2p1hFTMMYVFtilg= +cloud.google.com/go/recommender v1.10.1/go.mod h1:XFvrE4Suqn5Cq0Lf+mCP6oBHD/yRMA8XxP5sb7Q7gpA= +cloud.google.com/go/redis v1.13.1 h1:YrjQnCC7ydk+k30op7DSjSHw1yAYhqYXFcOq1bSXRYA= +cloud.google.com/go/redis v1.13.1/go.mod h1:VP7DGLpE91M6bcsDdMuyCm2hIpB6Vp2hI090Mfd1tcg= +cloud.google.com/go/resourcemanager v1.9.1 h1:QIAMfndPOHR6yTmMUB0ZN+HSeRmPjR/21Smq5/xwghI= +cloud.google.com/go/resourcemanager v1.9.1/go.mod h1:dVCuosgrh1tINZ/RwBufr8lULmWGOkPS8gL5gqyjdT8= +cloud.google.com/go/resourcesettings v1.6.1 h1:Fdyq418U69LhvNPFdlEO29w+DRRjwDA4/pFamm4ksAg= +cloud.google.com/go/resourcesettings v1.6.1/go.mod h1:M7mk9PIZrC5Fgsu1kZJci6mpgN8o0IUzVx3eJU3y4Jw= +cloud.google.com/go/retail v1.14.1 h1:gYBrb9u/Hc5s5lUTFXX1Vsbc/9BEvgtioY6ZKaK0DK8= +cloud.google.com/go/retail v1.14.1/go.mod h1:y3Wv3Vr2k54dLNIrCzenyKG8g8dhvhncT2NcNjb/6gE= +cloud.google.com/go/run v1.2.0 h1:kHeIG8q+N6Zv0nDkBjSOYfK2eWqa5FnaiDPH/7/HirE= +cloud.google.com/go/run v1.2.0/go.mod h1:36V1IlDzQ0XxbQjUx6IYbw8H3TJnWvhii963WW3B/bo= +cloud.google.com/go/scheduler v1.10.1 h1:yoZbZR8880KgPGLmACOMCiY2tPk+iX4V/dkxqTirlz8= +cloud.google.com/go/scheduler v1.10.1/go.mod h1:R63Ldltd47Bs4gnhQkmNDse5w8gBRrhObZ54PxgR2Oo= +cloud.google.com/go/secretmanager v1.11.1 h1:cLTCwAjFh9fKvU6F13Y4L9vPcx9yiWPyWXE4+zkuEQs= +cloud.google.com/go/secretmanager v1.11.1/go.mod h1:znq9JlXgTNdBeQk9TBW/FnR/W4uChEKGeqQWAJ8SXFw= +cloud.google.com/go/security v1.15.1 h1:jR3itwycg/TgGA0uIgTItcVhA55hKWiNJxaNNpQJaZE= +cloud.google.com/go/security v1.15.1/go.mod h1:MvTnnbsWnehoizHi09zoiZob0iCHVcL4AUBj76h9fXA= +cloud.google.com/go/securitycenter v1.23.0 h1:XOGJ9OpnDtqg8izd7gYk/XUhj8ytjIalyjjsR6oyG0M= +cloud.google.com/go/securitycenter v1.23.0/go.mod h1:8pwQ4n+Y9WCWM278R8W3nF65QtY172h4S8aXyI9/hsQ= +cloud.google.com/go/servicedirectory v1.11.0 h1:pBWpjCFVGWkzVTkqN3TBBIqNSoSHY86/6RL0soSQ4z8= +cloud.google.com/go/servicedirectory v1.11.0/go.mod h1:Xv0YVH8s4pVOwfM/1eMTl0XJ6bzIOSLDt8f8eLaGOxQ= +cloud.google.com/go/shell v1.7.1 h1:aHbwH9LSqs4r2rbay9f6fKEls61TAjT63jSyglsw7sI= +cloud.google.com/go/shell v1.7.1/go.mod h1:u1RaM+huXFaTojTbW4g9P5emOrrmLE69KrxqQahKn4g= +cloud.google.com/go/spanner v1.47.0 h1:aqiMP8dhsEXgn9K5EZBWxPG7dxIiyM2VaikqeU4iteg= +cloud.google.com/go/spanner v1.47.0/go.mod h1:IXsJwVW2j4UKs0eYDqodab6HgGuA1bViSqW4uH9lfUI= +cloud.google.com/go/speech v1.19.0 h1:MCagaq8ObV2tr1kZJcJYgXYbIn8Ai5rp42tyGYw9rls= +cloud.google.com/go/speech v1.19.0/go.mod h1:8rVNzU43tQvxDaGvqOhpDqgkJTFowBpDvCJ14kGlJYo= +cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= +cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= +cloud.google.com/go/storagetransfer v1.10.0 h1:+ZLkeXx0K0Pk5XdDmG0MnUVqIR18lllsihU/yq39I8Q= +cloud.google.com/go/storagetransfer v1.10.0/go.mod h1:DM4sTlSmGiNczmV6iZyceIh2dbs+7z2Ayg6YAiQlYfA= +cloud.google.com/go/talent v1.6.2 h1:j46ZgD6N2YdpFPux9mc7OAf4YK3tiBCsbLKc8rQx+bU= +cloud.google.com/go/talent v1.6.2/go.mod h1:CbGvmKCG61mkdjcqTcLOkb2ZN1SrQI8MDyma2l7VD24= +cloud.google.com/go/texttospeech v1.7.1 h1:S/pR/GZT9p15R7Y2dk2OXD/3AufTct/NSxT4a7nxByw= +cloud.google.com/go/texttospeech v1.7.1/go.mod h1:m7QfG5IXxeneGqTapXNxv2ItxP/FS0hCZBwXYqucgSk= +cloud.google.com/go/tpu v1.6.1 h1:kQf1jgPY04UJBYYjNUO+3GrZtIb57MfGAW2bwgLbR3A= +cloud.google.com/go/tpu v1.6.1/go.mod h1:sOdcHVIgDEEOKuqUoi6Fq53MKHJAtOwtz0GuKsWSH3E= +cloud.google.com/go/trace v1.10.1 h1:EwGdOLCNfYOOPtgqo+D2sDLZmRCEO1AagRTJCU6ztdg= +cloud.google.com/go/trace v1.10.1/go.mod h1:gbtL94KE5AJLH3y+WVpfWILmqgc6dXcqgNXdOPAQTYk= +cloud.google.com/go/translate v1.8.2 h1:PQHamiOzlehqLBJMnM72lXk/OsMQewZB12BKJ8zXrU0= +cloud.google.com/go/translate v1.8.2/go.mod h1:d1ZH5aaOA0CNhWeXeC8ujd4tdCFw8XoNWRljklu5RHs= +cloud.google.com/go/video v1.19.0 h1:BRyyS+wU+Do6VOXnb8WfPr42ZXti9hzmLKLUCkggeK4= +cloud.google.com/go/video v1.19.0/go.mod h1:9qmqPqw/Ib2tLqaeHgtakU+l5TcJxCJbhFXM7UJjVzU= +cloud.google.com/go/videointelligence v1.11.1 h1:MBMWnkQ78GQnRz5lfdTAbBq/8QMCF3wahgtHh3s/J+k= +cloud.google.com/go/videointelligence v1.11.1/go.mod h1:76xn/8InyQHarjTWsBR058SmlPCwQjgcvoW0aZykOvo= +cloud.google.com/go/vision/v2 v2.7.2 h1:ccK6/YgPfGHR/CyESz1mvIbsht5Y2xRsWCPqmTNydEw= +cloud.google.com/go/vision/v2 v2.7.2/go.mod h1:jKa8oSYBWhYiXarHPvP4USxYANYUEdEsQrloLjrSwJU= +cloud.google.com/go/vmmigration v1.7.1 h1:gnjIclgqbEMc+cF5IJuPxp53wjBIlqZ8h9hE8Rkwp7A= +cloud.google.com/go/vmmigration v1.7.1/go.mod h1:WD+5z7a/IpZ5bKK//YmT9E047AD+rjycCAvyMxGJbro= +cloud.google.com/go/vmwareengine v1.0.0 h1:qsJ0CPlOQu/3MFBGklu752v3AkD+Pdu091UmXJ+EjTA= +cloud.google.com/go/vmwareengine v1.0.0/go.mod h1:Px64x+BvjPZwWuc4HdmVhoygcXqEkGHXoa7uyfTgSI0= +cloud.google.com/go/vpcaccess v1.7.1 h1:ram0GzjNWElmbxXMIzeOZUkQ9J8ZAahD6V8ilPGqX0Y= +cloud.google.com/go/vpcaccess v1.7.1/go.mod h1:FogoD46/ZU+JUBX9D606X21EnxiszYi2tArQwLY4SXs= +cloud.google.com/go/webrisk v1.9.1 h1:Ssy3MkOMOnyRV5H2bkMQ13Umv7CwB/kugo3qkAX83Fk= +cloud.google.com/go/webrisk v1.9.1/go.mod h1:4GCmXKcOa2BZcZPn6DCEvE7HypmEJcJkr4mtM+sqYPc= +cloud.google.com/go/websecurityscanner v1.6.1 h1:CfEF/vZ+xXyAR3zC9iaC/QRdf1MEgS20r5UR17Q4gOg= +cloud.google.com/go/websecurityscanner v1.6.1/go.mod h1:Njgaw3rttgRHXzwCB8kgCYqv5/rGpFCsBOvPbYgszpg= +cloud.google.com/go/workflows v1.11.1 h1:2akeQ/PgtRhrNuD/n1WvJd5zb7YyuDZrlOanBj2ihPg= +cloud.google.com/go/workflows v1.11.1/go.mod h1:Z+t10G1wF7h8LgdY/EmRcQY8ptBD/nvofaL6FqlET6g= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= @@ -30,6 +267,10 @@ github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HR github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= +github.com/apache/arrow/go/v12 v12.0.0 h1:xtZE63VWl7qLdB0JObIXvvhGjoVNrQ9ciIHG2OK5cmc= +github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg= +github.com/apache/thrift v0.16.0 h1:qEy6UW60iVOlUy+b9ZR0d5WzUWYGOo4HfopoyBaNmoY= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA= github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q= @@ -47,8 +288,12 @@ github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk= github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q= @@ -64,10 +309,13 @@ github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdw github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/djherbis/atime v1.1.0 h1:rgwVbP/5by8BvvjBNrbh64Qz33idKT3pSnMSJsxhi0g= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= -github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f h1:7T++XKzy4xg7PKy+bM+Sa9/oe1OC88yz2hXQUISoXfA= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= -github.com/envoyproxy/protoc-gen-validate v0.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8= +github.com/envoyproxy/go-control-plane v0.11.1 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM= +github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= @@ -93,23 +341,31 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= +github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= -github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= -github.com/google/s2a-go v0.1.3 h1:FAgZmpLl/SXurPEZyCMPBIiiYeTbqfjlbdnCNTAkbGE= github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= +github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc= github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= +github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 h1:tlyzajkF3030q6M8SvmJSemC9DTHL/xaMa18b65+JM4= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 h1:lLT7ZLSzGLI08vc9cpd+tYmNWjdKDqyr/2L+f6U12Fk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= @@ -135,6 +391,8 @@ github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG67 github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab h1:BA4a7pe6ZTd9F8kXETBoijjFJ/ntaa//1wiH9BZu4zU= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= @@ -164,6 +422,8 @@ github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/knz/go-libedit v1.10.1 h1:0pHpWtx9vcvC0xGZqEQlQdfSQs7WRlAjuPvk3fOZDCo= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= @@ -171,6 +431,8 @@ github.com/labstack/echo/v4 v4.10.0 h1:5CiyngihEO4HXsz3vVsJn7f8xAlWwRr3aY6Ih280Z github.com/labstack/echo/v4 v4.10.0/go.mod h1:S/T/5fy/GigaXnHTkh0ZGe4LpkkQysvRjFMSUTkDRNQ= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/lyft/protoc-gen-star/v2 v2.0.3 h1:/3+/2sWyXeMLzKd1bX+ixWKgEMsULrIivpDsuaF441o= +github.com/lyft/protoc-gen-star/v2 v2.0.3/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2 h1:JAEbJn3j/FrhdWA9jW8B5ajsLIjeuEHLi8xE4fk997o= @@ -181,6 +443,10 @@ github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S github.com/mediocregopher/radix/v3 v3.8.0 h1:HI8EgkaM7WzsrFpYAkOXIgUKbjNonb2Ne7K6Le61Pmg= github.com/microsoft/go-mssqldb v0.21.0/go.mod h1:+4wZTUnz/SV6nffv+RRRB/ss8jPng5Sho2SmM1l2ts4= github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= @@ -207,12 +473,15 @@ github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/panjf2000/ants/v2 v2.4.2 h1:kesjjo8JipN3vNNg1XaiXaeSs6xJweBTgenkBtsrHf8= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/sftp v1.13.1 h1:I2qBYMChEhIjOgazfJmV3/mZM256btk6wkCDRmW7JYs= github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M= @@ -254,6 +523,8 @@ github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCO github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs= @@ -266,36 +537,71 @@ go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E= go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/otel v1.17.0 h1:MW+phZ6WZ5/uk2nd93ANk/6yJ+dVrvNWUjGhnnFU5jM= +go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0= +go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYOdSKWQ= +go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= +golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.122.0 h1:zDobeejm3E7pEG1mNHvdxvjs5XJoCMzyNH+CmwL94Es= google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= +google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= +google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= +google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y= +google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g= google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= -google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 h1:m8v1xLLLzMe1m5P+gCTF8nJB9epwZQUBERm20Oy1poQ= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e h1:z3vDksarJxsAKM5dmEGv0GHwE2hKJ096wZra71Vs4sw= +google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc h1:g3hIDl0jRNd9PPTs2uBzYuaD5mQuwOkZY0vSc0LR32o= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:8mL13HKkDa+IuJ8yruA3ci0q+0vsUz4m//+ottjwS5o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= diff --git a/mock_dao/audit.go b/mock_dao/audit.go index 122c2c0bf..ee55e78f2 100644 --- a/mock_dao/audit.go +++ b/mock_dao/audit.go @@ -7,8 +7,8 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockAuditDao is a mock of AuditDao interface. diff --git a/mock_dao/bookmark.go b/mock_dao/bookmark.go index 4a77d8138..921c2626b 100644 --- a/mock_dao/bookmark.go +++ b/mock_dao/bookmark.go @@ -7,8 +7,8 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockBookmarkDao is a mock of BookmarkDao interface. diff --git a/mock_dao/camera-preset.go b/mock_dao/camera-preset.go index c05ffa639..f6dc6a44e 100644 --- a/mock_dao/camera-preset.go +++ b/mock_dao/camera-preset.go @@ -7,8 +7,8 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockCameraPresetDao is a mock of CameraPresetDao interface. diff --git a/mock_dao/chat.go b/mock_dao/chat.go index dc92f62a5..d6d17ec08 100644 --- a/mock_dao/chat.go +++ b/mock_dao/chat.go @@ -7,8 +7,8 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockChatDao is a mock of ChatDao interface. diff --git a/mock_dao/courses.go b/mock_dao/courses.go index 7a508b40b..069d1332c 100644 --- a/mock_dao/courses.go +++ b/mock_dao/courses.go @@ -8,9 +8,9 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" dao "github.com/TUM-Dev/gocast/dao" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockCoursesDao is a mock of CoursesDao interface. diff --git a/mock_dao/email.go b/mock_dao/email.go index e27a77d22..b79db2da6 100644 --- a/mock_dao/email.go +++ b/mock_dao/email.go @@ -8,8 +8,8 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockEmailDao is a mock of EmailDao interface. diff --git a/mock_dao/file.go b/mock_dao/file.go index 90a782392..43011c5de 100644 --- a/mock_dao/file.go +++ b/mock_dao/file.go @@ -7,8 +7,8 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockFileDao is a mock of FileDao interface. diff --git a/mock_dao/info-page.go b/mock_dao/info-page.go index 3ce7bf621..07944e569 100644 --- a/mock_dao/info-page.go +++ b/mock_dao/info-page.go @@ -7,8 +7,8 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockInfoPageDao is a mock of InfoPageDao interface. diff --git a/mock_dao/ingest_server.go b/mock_dao/ingest_server.go index 7d38fc8bc..25ed2836b 100644 --- a/mock_dao/ingest_server.go +++ b/mock_dao/ingest_server.go @@ -7,8 +7,8 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockIngestServerDao is a mock of IngestServerDao interface. diff --git a/mock_dao/lecture_halls.go b/mock_dao/lecture_halls.go index 2cec5abff..3dc06957c 100644 --- a/mock_dao/lecture_halls.go +++ b/mock_dao/lecture_halls.go @@ -7,9 +7,9 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" dao "github.com/TUM-Dev/gocast/dao" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockLectureHallsDao is a mock of LectureHallsDao interface. diff --git a/mock_dao/notifications.go b/mock_dao/notifications.go index b9b94b5f7..e81445492 100644 --- a/mock_dao/notifications.go +++ b/mock_dao/notifications.go @@ -7,8 +7,8 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockNotificationsDao is a mock of NotificationsDao interface. diff --git a/mock_dao/progress.go b/mock_dao/progress.go index e0a4c7843..26dd5aeae 100644 --- a/mock_dao/progress.go +++ b/mock_dao/progress.go @@ -7,8 +7,8 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockProgressDao is a mock of ProgressDao interface. diff --git a/mock_dao/runner.go b/mock_dao/runner.go new file mode 100644 index 000000000..b8d947a68 --- /dev/null +++ b/mock_dao/runner.go @@ -0,0 +1,79 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: runner.go + +// Package mock_dao is a generated GoMock package. +package mock_dao + +import ( + context "context" + reflect "reflect" + + model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" +) + +// MockRunnerDao is a mock of RunnerDao interface. +type MockRunnerDao struct { + ctrl *gomock.Controller + recorder *MockRunnerDaoMockRecorder +} + +// MockRunnerDaoMockRecorder is the mock recorder for MockRunnerDao. +type MockRunnerDaoMockRecorder struct { + mock *MockRunnerDao +} + +// NewMockRunnerDao creates a new mock instance. +func NewMockRunnerDao(ctrl *gomock.Controller) *MockRunnerDao { + mock := &MockRunnerDao{ctrl: ctrl} + mock.recorder = &MockRunnerDaoMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRunnerDao) EXPECT() *MockRunnerDaoMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockRunnerDao) Create(arg0 context.Context, arg1 *model.Runner) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockRunnerDaoMockRecorder) Create(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockRunnerDao)(nil).Create), arg0, arg1) +} + +// Delete mocks base method. +func (m *MockRunnerDao) Delete(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockRunnerDaoMockRecorder) Delete(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRunnerDao)(nil).Delete), arg0, arg1) +} + +// Get mocks base method. +func (m *MockRunnerDao) Get(arg0 context.Context, arg1 string) (model.Runner, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0, arg1) + ret0, _ := ret[0].(model.Runner) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockRunnerDaoMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRunnerDao)(nil).Get), arg0, arg1) +} diff --git a/mock_dao/server-notification.go b/mock_dao/server-notification.go index c5a8d4131..e1cb3fb0c 100644 --- a/mock_dao/server-notification.go +++ b/mock_dao/server-notification.go @@ -7,8 +7,8 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockServerNotificationDao is a mock of ServerNotificationDao interface. diff --git a/mock_dao/statistics.go b/mock_dao/statistics.go index 6370ebe3a..349f3d2ea 100644 --- a/mock_dao/statistics.go +++ b/mock_dao/statistics.go @@ -7,9 +7,9 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" dao "github.com/TUM-Dev/gocast/dao" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockStatisticsDao is a mock of StatisticsDao interface. diff --git a/mock_dao/streams.go b/mock_dao/streams.go index f5b32c272..19451d777 100644 --- a/mock_dao/streams.go +++ b/mock_dao/streams.go @@ -9,9 +9,9 @@ import ( reflect "reflect" time "time" - gomock "github.com/golang/mock/gomock" dao "github.com/TUM-Dev/gocast/dao" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockStreamsDao is a mock of StreamsDao interface. @@ -273,6 +273,21 @@ func (mr *MockStreamsDaoMockRecorder) GetStreamByKey(ctx, key interface{}) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStreamByKey", reflect.TypeOf((*MockStreamsDao)(nil).GetStreamByKey), ctx, key) } +// GetStreamByKeyAndTime mocks base method. +func (m *MockStreamsDao) GetStreamByKeyAndTime(ctx context.Context, key string, time2 time.Time) (model.Stream, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetStreamByKeyAndTime", ctx, key, time2) + ret0, _ := ret[0].(model.Stream) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetStreamByKeyAndTime indicates an expected call of GetStreamByKeyAndTime. +func (mr *MockStreamsDaoMockRecorder) GetStreamByKeyAndTime(ctx, key, time2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStreamByKeyAndTime", reflect.TypeOf((*MockStreamsDao)(nil).GetStreamByKeyAndTime), ctx, key, time2) +} + // GetStreamByTumOnlineID mocks base method. func (m *MockStreamsDao) GetStreamByTumOnlineID(ctx context.Context, id uint) (model.Stream, error) { m.ctrl.T.Helper() diff --git a/mock_dao/subtitles.go b/mock_dao/subtitles.go index f8a0249de..ba086919e 100644 --- a/mock_dao/subtitles.go +++ b/mock_dao/subtitles.go @@ -8,8 +8,8 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockSubtitlesDao is a mock of SubtitlesDao interface. diff --git a/mock_dao/token.go b/mock_dao/token.go index 1a3266432..a25fc1963 100644 --- a/mock_dao/token.go +++ b/mock_dao/token.go @@ -7,9 +7,9 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" dao "github.com/TUM-Dev/gocast/dao" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockTokenDao is a mock of TokenDao interface. diff --git a/mock_dao/transcoding-failure.go b/mock_dao/transcoding-failure.go index c222c7fc8..ea4dd3205 100644 --- a/mock_dao/transcoding-failure.go +++ b/mock_dao/transcoding-failure.go @@ -7,8 +7,8 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockTranscodingFailureDao is a mock of TranscodingFailureDao interface. diff --git a/mock_dao/upload_key.go b/mock_dao/upload_key.go index c3b92c9e4..915aa8128 100644 --- a/mock_dao/upload_key.go +++ b/mock_dao/upload_key.go @@ -7,8 +7,8 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockUploadKeyDao is a mock of UploadKeyDao interface. diff --git a/mock_dao/users.go b/mock_dao/users.go index fb97525b3..8f8e8a05a 100644 --- a/mock_dao/users.go +++ b/mock_dao/users.go @@ -8,8 +8,8 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockUsersDao is a mock of UsersDao interface. diff --git a/mock_dao/video-section.go b/mock_dao/video-section.go index 83619ed2d..df80d9d99 100644 --- a/mock_dao/video-section.go +++ b/mock_dao/video-section.go @@ -7,8 +7,8 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockVideoSectionDao is a mock of VideoSectionDao interface. diff --git a/mock_dao/video-seek.go b/mock_dao/video-seek.go index 47bfba104..a5dc2c318 100644 --- a/mock_dao/video-seek.go +++ b/mock_dao/video-seek.go @@ -7,8 +7,8 @@ package mock_dao import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockVideoSeekDao is a mock of VideoSeekDao interface. diff --git a/mock_dao/worker.go b/mock_dao/worker.go index 1095c0e71..9db0b2eb5 100644 --- a/mock_dao/worker.go +++ b/mock_dao/worker.go @@ -8,8 +8,8 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockWorkerDao is a mock of WorkerDao interface. diff --git a/mock_tools/mock_camera/camera.go b/mock_tools/mock_camera/camera.go index 2e2c42486..2fd5f41f1 100644 --- a/mock_tools/mock_camera/camera.go +++ b/mock_tools/mock_camera/camera.go @@ -7,8 +7,8 @@ package mock_camera import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" + gomock "github.com/golang/mock/gomock" ) // MockCam is a mock of Cam interface. diff --git a/mock_tools/presets.go b/mock_tools/presets.go index 4a2c05abe..0645946f6 100644 --- a/mock_tools/presets.go +++ b/mock_tools/presets.go @@ -8,9 +8,9 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" model "github.com/TUM-Dev/gocast/model" camera "github.com/TUM-Dev/gocast/tools/camera" + gomock "github.com/golang/mock/gomock" ) // MockPresetUtility is a mock of PresetUtility interface. diff --git a/model/course.go b/model/course.go index e902bba18..a35ce6e92 100755 --- a/model/course.go +++ b/model/course.go @@ -33,6 +33,7 @@ type Course struct { Users []User `gorm:"many2many:course_users;"` Admins []User `gorm:"many2many:course_admins;"` Token string + StreamKey string // Course-wide stream key UserCreatedByToken bool `gorm:"default:false"` CameraPresetPreferences string // json encoded. e.g. [{lectureHallID:1, presetID:4}, ...] SourcePreferences string // json encoded. e.g. [{lectureHallID:1, sourceMode:0}, ...] diff --git a/model/runner.go b/model/runner.go new file mode 100644 index 000000000..710a96348 --- /dev/null +++ b/model/runner.go @@ -0,0 +1,25 @@ +package model + +import ( + "database/sql" + "errors" + "gorm.io/gorm" +) + +// Runner represents a runner that creates, converts and postprocesses streams and does other heavy lifting. +type Runner struct { + Hostname string `gorm:"primaryKey"` + Port int + LastSeen sql.NullTime +} + +// BeforeCreate returns errors if hostnames and ports of workers are invalid. +func (r *Runner) BeforeCreate(tx *gorm.DB) (err error) { + if r.Hostname == "" { + return errors.New("missing hostname") + } + if r.Port < 0 || r.Port > 65535 { + return errors.New("port out of range") + } + return nil +} diff --git a/runner/.gitignore b/runner/.gitignore new file mode 100644 index 000000000..31eb45db6 --- /dev/null +++ b/runner/.gitignore @@ -0,0 +1 @@ +mediamtx diff --git a/runner/Dockerfile b/runner/Dockerfile new file mode 100644 index 000000000..309827373 --- /dev/null +++ b/runner/Dockerfile @@ -0,0 +1,27 @@ +FROM amd64/golang:1.21-alpine3.18 as builder + +WORKDIR /go/src/github.com/TUM-Dev/gocast/runner +COPY . . + +RUN GO111MODULE=on go mod download +# bundle version into binary if specified in build-args, dev otherwise. +ARG version=dev +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags "-w -extldflags '-static' -X main.V=${version}" -o /runner cmd/runner/main.go + +FROM bluenviron/mediamtx:1.1.0 as mediamtx + +FROM alpine:3.18 +ADD entrypoint.sh /entrypoint.sh +ADD mediamtx.yml /mediamtx.yml +RUN chmod +x /entrypoint.sh + +RUN apk add --no-cache \ + ffmpeg \ + tzdata + +COPY --from=builder /runner /runner +RUN chmod +x /runner +COPY --from=mediamtx /mediamtx /mediamtx +RUN chmod +x /mediamtx + +CMD ["/entrypoint.sh"] \ No newline at end of file diff --git a/runner/Makefile b/runner/Makefile new file mode 100644 index 000000000..541ff27f2 --- /dev/null +++ b/runner/Makefile @@ -0,0 +1,36 @@ +.PHONY: all +all: build + +VERSION := $(shell git rev-parse --short origin/HEAD) + +.PHONY: protoGen +protoGen: + protoc ./runner.proto --go-grpc_out=.. --go_out=.. + +.PHONY: build +build: deps + go build -o main -ldflags="-X 'main.VersionTag=$(VERSION)'" cmd/runner/main.go + +.PHONY: deps +deps: + go get ./... + +.PHONY: install +install: + mv main /bin/runner + +.PHONY: clean +clean: + rm -f main + +.PHONY: test +test: + go test -race ./... + +.PHONY: run +run: + go run cmd/runner/main.go + +.PHONY: lint +lint: + golangci-lint run diff --git a/runner/actions/actions.go b/runner/actions/actions.go new file mode 100644 index 000000000..f8ff7ae68 --- /dev/null +++ b/runner/actions/actions.go @@ -0,0 +1,57 @@ +package actions + +import ( + "context" + "errors" + "fmt" + "log/slog" + "path" +) + +var ( + ErrActionInputWrongType = errors.New("action input has wrong type") + ErrRequiredContextValNotFound = errors.New("required context value not found") +) + +type ActionProvider struct { + Log *slog.Logger + + SegmentDir string // for storing live hls segments locally. This should be fast storage (e.g. ssd). + RecDir string // for storing recordings locally. + MassDir string // for storing final files like Thumbnails, mp4s, ... Mass storage like Ceph. +} + +func (a *ActionProvider) GetRecDir(courseID, streamID uint64, version string) string { + return path.Join(a.RecDir, fmt.Sprintf("%d", courseID), fmt.Sprintf("%d", streamID), version) +} + +func (a *ActionProvider) GetLiveDir(courseID, streamID uint64, version string) string { + return path.Join(a.SegmentDir, fmt.Sprintf("%d", courseID), fmt.Sprintf("%d", streamID), version) +} + +func (a *ActionProvider) GetMassDir(courseID, streamID uint64, version string) string { + return path.Join(a.MassDir, fmt.Sprintf("%d", courseID), fmt.Sprintf("%d", streamID), version) +} + +type ActionType string + +const ( + PrepareAction ActionType = "prepare" + StreamAction = "stream" + TranscodeAction = "transcode" + UploadAction = "upload" +) + +type Action struct { + Type ActionType + Cancel context.CancelCauseFunc + Canceled bool + + ActionFn ActionFn +} + +type ActionFn func(ctx context.Context, log *slog.Logger) (context.Context, error) + +func set(ctx context.Context, key string, val interface{}) context.Context { + return context.WithValue(ctx, key, val) +} diff --git a/runner/actions/prepare.go b/runner/actions/prepare.go new file mode 100644 index 000000000..db77411f3 --- /dev/null +++ b/runner/actions/prepare.go @@ -0,0 +1,43 @@ +package actions + +import ( + "context" + "fmt" + "log/slog" + "os" +) + +// PrepareAction prepares the directory structure for the stream and vod. +func (a *ActionProvider) PrepareAction() *Action { + return &Action{ + Type: PrepareAction, + ActionFn: func(ctx context.Context, log *slog.Logger) (context.Context, error) { + streamID, ok := ctx.Value("stream").(uint64) + if !ok { + return ctx, fmt.Errorf("%w: context doesn't contain stream", ErrRequiredContextValNotFound) + } + courseID, ok := ctx.Value("course").(uint64) + if !ok { + return ctx, fmt.Errorf("%w: context doesn't contain course", ErrRequiredContextValNotFound) + } + version, ok := ctx.Value("version").(string) + if !ok { + return ctx, fmt.Errorf("%w: context doesn't contain version", ErrRequiredContextValNotFound) + } + + dirs := []string{ + a.GetRecDir(courseID, streamID, version), + a.GetLiveDir(courseID, streamID, version), + a.GetMassDir(courseID, streamID, version), + } + for _, dir := range dirs { + log.Info("creating directory", "path", dir) + err := os.MkdirAll(dir, 0755) + if err != nil { + return ctx, fmt.Errorf("create directory: %w", err) + } + } + return ctx, nil + }, + } +} diff --git a/runner/actions/stream.go b/runner/actions/stream.go new file mode 100644 index 000000000..412c08858 --- /dev/null +++ b/runner/actions/stream.go @@ -0,0 +1,97 @@ +package actions + +import ( + "context" + "fmt" + "log/slog" + "os" + "os/exec" + "path/filepath" + "strings" + "time" +) + +var edgeTemplate = "%s://%s/live/%s/%d-%s/playlist.m3u8" // e.g. "https://stream.domain.com/workerhostname123/1-COMB/playlist.m3u8" + +// StreamAction streams a video. in is ignored. out is a []string containing the filenames of the recorded stream. +// ctx must contain the following values: +// - streamID (uint64) // e.g. 1 +// - courseID (uint64) // e.g. 1 +// - version (string) // e.g. "PRES", "CAM", "COMB" +// - source (string) // e.g. "rtmp://localhost:1935/live/abc123" for selfstreams or "rtsp://1.2.3.4/extron1" for auditoriums +// - end (time.Time) // the end of the stream for auditoriums or an end date far in the future for selfstreams. +// after StreamAction is done, the following values are set in ctx: +// - files ([]string) // a list of files that were created during the stream +func (a *ActionProvider) StreamAction() *Action { + return &Action{ + Type: StreamAction, + ActionFn: func(ctx context.Context, log *slog.Logger) (context.Context, error) { + // files will contain all files that were created during the stream + var files []string + + streamID, ok := ctx.Value("stream").(uint64) + if !ok { + return ctx, fmt.Errorf("%w: context doesn't contain stream", ErrRequiredContextValNotFound) + } + courseID, ok := ctx.Value("course").(uint64) + if !ok { + return ctx, fmt.Errorf("%w: context doesn't contain courseID", ErrRequiredContextValNotFound) + } + version, ok := ctx.Value("version").(string) + if !ok { + return ctx, fmt.Errorf("%w: context doesn't contain version", ErrRequiredContextValNotFound) + } + source, ok := ctx.Value("source").(string) + if !ok { + return ctx, fmt.Errorf("%w: context doesn't contain source", ErrRequiredContextValNotFound) + } + end, ok := ctx.Value("end").(time.Time) + if !ok { + return ctx, fmt.Errorf("%w: context doesn't contain end", ErrRequiredContextValNotFound) + } + log.Info("streaming", "source", source, "end", end) + + streamAttempt := 0 + for time.Now().Before(end) && ctx.Err() == nil { + streamAttempt++ + filename := filepath.Join(a.GetRecDir(courseID, streamID, version), fmt.Sprintf("%d.ts", streamAttempt)) + files = append(files, filename) + livePlaylist := filepath.Join(a.GetLiveDir(courseID, streamID, version), "playlist.m3u8") + + cmd := "-y -hide_banner -nostats" + if strings.HasPrefix(source, "rtsp") { + cmd += " -rtsp_transport tcp" + } else if strings.HasPrefix(source, "rtmp") { + cmd += " -rw_timeout 5000000" // timeout selfstream s after 5 seconds of no data + } else { + cmd += " -re" // read input at native framerate, e.g. when streaming a file in realtime + } + + cmd += fmt.Sprintf(" -t %.0f", time.Until(end).Seconds()) + cmd += fmt.Sprintf(" -i %s", source) + cmd += " -c:v copy -c:a copy -f mpegts " + filename // write original stream to file for later processing + cmd += " -c:v libx264 -preset veryfast -tune zerolatency -maxrate 2500k -bufsize 3000k -g 60 -r 30 -x264-params keyint=60:scenecut=0 -c:a aac -ar 44100 -b:a 128k -f hls" + // todo optional stream target + cmd += " -hls_time 2 -hls_list_size 3600 -hls_playlist_type event -hls_flags append_list -hls_segment_filename " + filepath.Join(a.GetLiveDir(courseID, streamID, version), "/%05d.ts") + cmd += " " + livePlaylist + + c := exec.CommandContext(ctx, "ffmpeg", strings.Split(cmd, " ")...) + c.Stderr = os.Stderr + log.Info("constructed stream command", "cmd", c.String()) + err := c.Start() + if err != nil { + log.Warn("streamAction: ", err) + time.Sleep(5 * time.Second) // little backoff to prevent dossing source + continue + } + err = c.Wait() + if err != nil { + log.Warn("stream command exited", "err", err) + time.Sleep(5 * time.Second) // little backoff to prevent dossing source + continue + } + } + return set(ctx, "files", files), nil + }, + } +} diff --git a/runner/actions/transcode.go b/runner/actions/transcode.go new file mode 100644 index 000000000..a1059d9e2 --- /dev/null +++ b/runner/actions/transcode.go @@ -0,0 +1,36 @@ +package actions + +import ( + "context" + "log/slog" + "os/exec" + "time" +) + +func (a *ActionProvider) TranscodeAction() *Action { + return &Action{ + Type: TranscodeAction, + ActionFn: func(ctx context.Context, log *slog.Logger) (context.Context, error) { + files, ok := ctx.Value("files").([]string) + if !ok { + return ctx, ErrActionInputWrongType + } + log.Info("transcoding", "files", files) + time.Sleep(time.Second) + return ctx, nil + // parse output from previous streamAction + fileName, ok := ctx.Value("files").([]string) + if !ok { + return ctx, ErrActionInputWrongType + } + _ = "/mass/" + ctx.Value("streamID").(string) + ".mp4" + c := exec.CommandContext(ctx, "ffmpeg", fileName...) //, "...", output) + err := c.Start() + if err != nil { + return ctx, err + } + err = c.Wait() + return ctx, err + }, + } +} diff --git a/runner/actions/upload.go b/runner/actions/upload.go new file mode 100644 index 000000000..27dae23cc --- /dev/null +++ b/runner/actions/upload.go @@ -0,0 +1,15 @@ +package actions + +import ( + "context" + "log/slog" +) + +func (a *ActionProvider) UploadAction() *Action { + return &Action{ + Type: UploadAction, + ActionFn: func(ctx context.Context, log *slog.Logger) (context.Context, error) { + return ctx, nil + }, + } +} diff --git a/runner/cmd/runner/main.go b/runner/cmd/runner/main.go new file mode 100644 index 000000000..9addd6d70 --- /dev/null +++ b/runner/cmd/runner/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "github.com/tum-dev/gocast/runner" + + "log/slog" + "os" + "os/signal" + "syscall" +) + +// V (Version) is bundled into binary with -ldflags "-X ..." +var V = "dev" + +func main() { + // ... + r := runner.NewRunner(V) + go r.Run() + + shouldShutdown := false // set to true once we receive a shutdown signal + + currentCount := 0 + go func() { + for { + currentCount = <-r.JobCount // wait for a job to finish + slog.Info("current job count", "count", currentCount) + if shouldShutdown && currentCount == 0 { // if we should shut down and no jobs are running, exit. + slog.Info("No jobs left, shutting down") + os.Exit(0) + } + } + }() + + osSignal := make(chan os.Signal, 1) + signal.Notify(osSignal, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1) + s := <-osSignal + slog.Info("Received signal", "signal", s) + shouldShutdown = true + r.Drain() + + if currentCount == 0 { + slog.Info("No jobs left, shutting down") + os.Exit(0) + } + + blocking := make(chan struct{}) + _ = <-blocking + +} diff --git a/runner/entrypoint.sh b/runner/entrypoint.sh new file mode 100644 index 000000000..913b534ee --- /dev/null +++ b/runner/entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +exec /mediamtx & +exec /runner diff --git a/runner/go.mod b/runner/go.mod new file mode 100644 index 000000000..52e71a8ec --- /dev/null +++ b/runner/go.mod @@ -0,0 +1,19 @@ +module github.com/tum-dev/gocast/runner + +go 1.21.0 + +require ( + github.com/caarlos0/env v3.5.0+incompatible + github.com/dusted-go/logging v1.1.1 + github.com/golang/protobuf v1.5.3 + google.golang.org/grpc v1.58.2 + google.golang.org/protobuf v1.31.0 +) + +require ( + github.com/stretchr/testify v1.8.4 // indirect + golang.org/x/net v0.12.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect +) diff --git a/runner/go.sum b/runner/go.sum new file mode 100644 index 000000000..6a3f15052 --- /dev/null +++ b/runner/go.sum @@ -0,0 +1,33 @@ +github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs= +github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dusted-go/logging v1.1.1 h1:x1aSyarDh2iq7MIhCZkjbepsZD9FmAURK93jRFm2Oco= +github.com/dusted-go/logging v1.1.1/go.mod h1:Lim3Rk6x2MYwpvPZ6XzPk6ZeX4Wgz9lVOptrL7ngr5w= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/runner/handlers.go b/runner/handlers.go new file mode 100644 index 000000000..eec5a4387 --- /dev/null +++ b/runner/handlers.go @@ -0,0 +1,39 @@ +package runner + +import ( + "context" + "errors" + "github.com/tum-dev/gocast/runner/actions" + "github.com/tum-dev/gocast/runner/protobuf" +) + +func contextFromStreamReq(req *protobuf.StreamRequest, ctx context.Context) context.Context { + ctx = context.WithValue(ctx, "stream", req.GetStream()) + ctx = context.WithValue(ctx, "course", req.GetCourse()) + ctx = context.WithValue(ctx, "version", req.GetVersion()) + ctx = context.WithValue(ctx, "source", req.GetSource()) + return context.WithValue(ctx, "end", req.GetEnd().AsTime()) +} + +func (r *Runner) RequestStream(ctx context.Context, req *protobuf.StreamRequest) (*protobuf.StreamResponse, error) { + // don't reuse context from grpc, it will be canceled when the request is done. + ctx = context.Background() + ctx = contextFromStreamReq(req, ctx) + a := []*actions.Action{ + r.actions.PrepareAction(), + r.actions.StreamAction(), + r.actions.TranscodeAction(), + r.actions.UploadAction(), + } + jobID := r.AddJob(ctx, a) + + return &protobuf.StreamResponse{Job: jobID}, nil +} + +func (r *Runner) RequestStreamEnd(ctx context.Context, request *protobuf.StreamEndRequest) (*protobuf.StreamEndResponse, error) { + if job, ok := r.jobs[request.GetJobID()]; ok { + job.Cancel(errors.New("canceled by user request"), actions.StreamAction, actions.UploadAction) + return &protobuf.StreamEndResponse{}, nil + } + return nil, errors.New("job not found") +} diff --git a/runner/hls.go b/runner/hls.go new file mode 100644 index 000000000..ddb9ef584 --- /dev/null +++ b/runner/hls.go @@ -0,0 +1,28 @@ +package runner + +import ( + "log/slog" + "net/http" +) + +type HLSServer struct { + log *slog.Logger + fs http.Handler +} + +func NewHLSServer(LiveDir string, log *slog.Logger) *HLSServer { + return &HLSServer{fs: http.FileServer(http.Dir(LiveDir)), log: log} +} + +func (h *HLSServer) Start() error { + http.Handle("/", h) + h.log.Info("starting hls server", "port", 8187) + return http.ListenAndServe(":8187", nil) +} + +func (h *HLSServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + + h.log.Info("serving request", "path", r.URL.Path, "method", r.Method) + h.fs.ServeHTTP(w, r) +} diff --git a/runner/mediamtx.yml b/runner/mediamtx.yml new file mode 100644 index 000000000..122b6ce51 --- /dev/null +++ b/runner/mediamtx.yml @@ -0,0 +1,104 @@ + +############################################### +# General parameters + +# Sets the verbosity of the program; available values are "error", "warn", "info", "debug". +logLevel: debug +# Destinations of log messages; available values are "stdout", "file" and "syslog". +logDestinations: [stdout] + +# Timeout of read operations. +readTimeout: 10s +# Timeout of write operations. +writeTimeout: 10s +# Number of read buffers. +# A higher value allows a wider throughput, a lower value allows to save RAM. +readBufferCount: 512 +# Maximum size of payload of outgoing UDP packets. +# This can be decreased to avoid fragmentation on networks with a low UDP MTU. +udpMaxPayloadSize: 1472 + +# HTTP URL to perform external authentication. +# Every time a user wants to authenticate, the server calls this URL +# with the POST method and a body containing: +# { +# "ip": "ip", +# "user": "user", +# "password": "password", +# "path": "path", +# "protocol": "rtsp|rtmp|hls|webrtc", +# "id": "id", +# "action": "read|publish", +# "query": "query" +# } +# If the response code is 20x, authentication is accepted, otherwise +# it is discarded. +externalAuthenticationURL: http://localhost:8060/on_publish + +# Enable the HTTP API. +api: no +# Address of the API listener. +apiAddress: 127.0.0.1:9997 + +# Enable Prometheus-compatible metrics. +metrics: no +# Address of the metrics listener. +metricsAddress: 127.0.0.1:9998 + + +############################################### +# RTSP parameters + +# Disable support for the RTSP protocol. +rtspDisable: yes + +############################################### +# RTMP parameters + +# Disable support for the RTMP protocol. +rtmpDisable: no +# Address of the RTMP listener. This is needed only when encryption is "no" or "optional". +rtmpAddress: :1935 +# Encrypt connections with TLS (RTMPS). +# Available values are "no", "strict", "optional". +rtmpEncryption: "no" + +############################################### +# HLS parameters + +# Disable support for the HLS protocol. +hlsDisable: yes + +############################################### +# WebRTC parameters + +# Disable support for the WebRTC protocol. +webrtcDisable: yes + +############################################### +# Path parameters + +# These settings are path-dependent, and the map key is the name of the path. +# It's possible to use regular expressions by using a tilde as prefix. +# For example, "~^(test1|test2)$" will match both "test1" and "test2". +# For example, "~^prefix" will match all paths that start with "prefix". +# The settings under the path "all" are applied to all paths that do not match +# another entry. +paths: + all: + # Source of the stream. This can be: + # * publisher -> the stream is published by a RTSP or RTMP client + # * rtsp://existing-url -> the stream is pulled from another RTSP server / camera + # * rtsps://existing-url -> the stream is pulled from another RTSP server / camera with RTSPS + # * rtmp://existing-url -> the stream is pulled from another RTMP server / camera + # * rtmps://existing-url -> the stream is pulled from another RTMP server / camera with RTMPS + # * http://existing-url/stream.m3u8 -> the stream is pulled from another HLS server + # * https://existing-url/stream.m3u8 -> the stream is pulled from another HLS server with HTTPS + # * udp://ip:port -> the stream is pulled from UDP, by listening on the specified IP and port + # * redirect -> the stream is provided by another path or server + # * rpiCamera -> the stream is provided by a Raspberry Pi Camera + source: publisher + + # If the source is an RTSP or RTSPS URL, this is the protocol that will be used to + # pull the stream. available values are "automatic", "udp", "multicast", "tcp". + sourceProtocol: automatic diff --git a/runner/pkg/logging/grpc.go b/runner/pkg/logging/grpc.go new file mode 100644 index 000000000..8e95905a3 --- /dev/null +++ b/runner/pkg/logging/grpc.go @@ -0,0 +1,19 @@ +package logging + +import ( + "context" + "google.golang.org/grpc" + "log/slog" +) + +// GetGrpcLogInterceptor returns a grpc.ServerOption that logs all requests +func GetGrpcLogInterceptor(logger *slog.Logger) grpc.ServerOption { + return grpc.ChainUnaryInterceptor(grpcLogger(logger)) +} + +func grpcLogger(l *slog.Logger) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { + l.Info("gRPC call", "method", info.FullMethod, "request", req) + return handler(ctx, req) + } +} diff --git a/runner/pkg/logging/logging.go b/runner/pkg/logging/logging.go new file mode 100644 index 000000000..51e6d13d9 --- /dev/null +++ b/runner/pkg/logging/logging.go @@ -0,0 +1,43 @@ +package logging + +import ( + "log/slog" + "os" + "strings" + + "github.com/dusted-go/logging/prettylog" +) + +// GetLogger creates a *slog.Logger based on the environment, sets it as the default logger and returns it. +func GetLogger(v string) *slog.Logger { + lvlStr := os.Getenv("LOG_LEVEL") + var level slog.Level + switch strings.ToLower(lvlStr) { + case "debug": + level = slog.LevelDebug + case "info": + level = slog.LevelInfo + case "warn": + level = slog.LevelWarn + case "error": + level = slog.LevelError + default: + level = slog.LevelDebug + } + var logger *slog.Logger + if os.Getenv("LOG_FMT") == "json" { + logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + AddSource: true, + Level: level, + })) + } else { + logger = slog.New(prettylog.NewHandler(&slog.HandlerOptions{ + Level: level, + ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { + return a // don't replace any attributes + }, + })) + } + slog.SetDefault(logger) + return logger +} diff --git a/runner/pkg/netutil/netutil.go b/runner/pkg/netutil/netutil.go new file mode 100644 index 000000000..f3ae319ed --- /dev/null +++ b/runner/pkg/netutil/netutil.go @@ -0,0 +1,16 @@ +package netutil + +import "net" + +// GetFreePort returns a free port for tcp use. +func GetFreePort() (port int, err error) { + var a *net.TCPAddr + if a, err = net.ResolveTCPAddr("tcp", "localhost:0"); err == nil { + var l *net.TCPListener + if l, err = net.ListenTCP("tcp", a); err == nil { + defer l.Close() + return l.Addr().(*net.TCPAddr).Port, nil + } + } + return port, err +} diff --git a/runner/protobuf/runner.pb.go b/runner/protobuf/runner.pb.go new file mode 100644 index 000000000..15441a1f8 --- /dev/null +++ b/runner/protobuf/runner.pb.go @@ -0,0 +1,850 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.12.4 +// source: runner.proto + +package protobuf + +import ( + timestamp "github.com/golang/protobuf/ptypes/timestamp" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type StreamRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Stream uint64 `protobuf:"varint,1,opt,name=stream,proto3" json:"stream,omitempty"` + Course uint64 `protobuf:"varint,2,opt,name=course,proto3" json:"course,omitempty"` + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` + End *timestamp.Timestamp `protobuf:"bytes,4,opt,name=end,proto3" json:"end,omitempty"` + Source string `protobuf:"bytes,5,opt,name=source,proto3" json:"source,omitempty"` +} + +func (x *StreamRequest) Reset() { + *x = StreamRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runner_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StreamRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StreamRequest) ProtoMessage() {} + +func (x *StreamRequest) ProtoReflect() protoreflect.Message { + mi := &file_runner_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StreamRequest.ProtoReflect.Descriptor instead. +func (*StreamRequest) Descriptor() ([]byte, []int) { + return file_runner_proto_rawDescGZIP(), []int{0} +} + +func (x *StreamRequest) GetStream() uint64 { + if x != nil { + return x.Stream + } + return 0 +} + +func (x *StreamRequest) GetCourse() uint64 { + if x != nil { + return x.Course + } + return 0 +} + +func (x *StreamRequest) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *StreamRequest) GetEnd() *timestamp.Timestamp { + if x != nil { + return x.End + } + return nil +} + +func (x *StreamRequest) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +type StreamResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Job string `protobuf:"bytes,1,opt,name=job,proto3" json:"job,omitempty"` +} + +func (x *StreamResponse) Reset() { + *x = StreamResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runner_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StreamResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StreamResponse) ProtoMessage() {} + +func (x *StreamResponse) ProtoReflect() protoreflect.Message { + mi := &file_runner_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StreamResponse.ProtoReflect.Descriptor instead. +func (*StreamResponse) Descriptor() ([]byte, []int) { + return file_runner_proto_rawDescGZIP(), []int{1} +} + +func (x *StreamResponse) GetJob() string { + if x != nil { + return x.Job + } + return "" +} + +type StreamEndRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + JobID string `protobuf:"bytes,1,opt,name=jobID,proto3" json:"jobID,omitempty"` + KeepVod bool `protobuf:"varint,2,opt,name=keepVod,proto3" json:"keepVod,omitempty"` +} + +func (x *StreamEndRequest) Reset() { + *x = StreamEndRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runner_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StreamEndRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StreamEndRequest) ProtoMessage() {} + +func (x *StreamEndRequest) ProtoReflect() protoreflect.Message { + mi := &file_runner_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StreamEndRequest.ProtoReflect.Descriptor instead. +func (*StreamEndRequest) Descriptor() ([]byte, []int) { + return file_runner_proto_rawDescGZIP(), []int{2} +} + +func (x *StreamEndRequest) GetJobID() string { + if x != nil { + return x.JobID + } + return "" +} + +func (x *StreamEndRequest) GetKeepVod() bool { + if x != nil { + return x.KeepVod + } + return false +} + +type StreamEndResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *StreamEndResponse) Reset() { + *x = StreamEndResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runner_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StreamEndResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StreamEndResponse) ProtoMessage() {} + +func (x *StreamEndResponse) ProtoReflect() protoreflect.Message { + mi := &file_runner_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StreamEndResponse.ProtoReflect.Descriptor instead. +func (*StreamEndResponse) Descriptor() ([]byte, []int) { + return file_runner_proto_rawDescGZIP(), []int{3} +} + +type RegisterRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hostname string `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"` + Port int32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` +} + +func (x *RegisterRequest) Reset() { + *x = RegisterRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runner_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RegisterRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterRequest) ProtoMessage() {} + +func (x *RegisterRequest) ProtoReflect() protoreflect.Message { + mi := &file_runner_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterRequest.ProtoReflect.Descriptor instead. +func (*RegisterRequest) Descriptor() ([]byte, []int) { + return file_runner_proto_rawDescGZIP(), []int{4} +} + +func (x *RegisterRequest) GetHostname() string { + if x != nil { + return x.Hostname + } + return "" +} + +func (x *RegisterRequest) GetPort() int32 { + if x != nil { + return x.Port + } + return 0 +} + +type RegisterResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *RegisterResponse) Reset() { + *x = RegisterResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runner_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RegisterResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterResponse) ProtoMessage() {} + +func (x *RegisterResponse) ProtoReflect() protoreflect.Message { + mi := &file_runner_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterResponse.ProtoReflect.Descriptor instead. +func (*RegisterResponse) Descriptor() ([]byte, []int) { + return file_runner_proto_rawDescGZIP(), []int{5} +} + +type HeartbeatRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` + Workload uint32 `protobuf:"varint,2,opt,name=Workload,proto3" json:"Workload,omitempty"` + Version string `protobuf:"bytes,3,opt,name=Version,proto3" json:"Version,omitempty"` + CPU string `protobuf:"bytes,4,opt,name=CPU,proto3" json:"CPU,omitempty"` + Memory string `protobuf:"bytes,5,opt,name=Memory,proto3" json:"Memory,omitempty"` + Disk string `protobuf:"bytes,6,opt,name=Disk,proto3" json:"Disk,omitempty"` + Uptime string `protobuf:"bytes,7,opt,name=Uptime,proto3" json:"Uptime,omitempty"` +} + +func (x *HeartbeatRequest) Reset() { + *x = HeartbeatRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runner_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HeartbeatRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HeartbeatRequest) ProtoMessage() {} + +func (x *HeartbeatRequest) ProtoReflect() protoreflect.Message { + mi := &file_runner_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HeartbeatRequest.ProtoReflect.Descriptor instead. +func (*HeartbeatRequest) Descriptor() ([]byte, []int) { + return file_runner_proto_rawDescGZIP(), []int{6} +} + +func (x *HeartbeatRequest) GetID() uint64 { + if x != nil { + return x.ID + } + return 0 +} + +func (x *HeartbeatRequest) GetWorkload() uint32 { + if x != nil { + return x.Workload + } + return 0 +} + +func (x *HeartbeatRequest) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *HeartbeatRequest) GetCPU() string { + if x != nil { + return x.CPU + } + return "" +} + +func (x *HeartbeatRequest) GetMemory() string { + if x != nil { + return x.Memory + } + return "" +} + +func (x *HeartbeatRequest) GetDisk() string { + if x != nil { + return x.Disk + } + return "" +} + +func (x *HeartbeatRequest) GetUptime() string { + if x != nil { + return x.Uptime + } + return "" +} + +type HeartbeatResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *HeartbeatResponse) Reset() { + *x = HeartbeatResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runner_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HeartbeatResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HeartbeatResponse) ProtoMessage() {} + +func (x *HeartbeatResponse) ProtoReflect() protoreflect.Message { + mi := &file_runner_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HeartbeatResponse.ProtoReflect.Descriptor instead. +func (*HeartbeatResponse) Descriptor() ([]byte, []int) { + return file_runner_proto_rawDescGZIP(), []int{7} +} + +type SelfStreamRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StreamKey string `protobuf:"bytes,1,opt,name=streamKey,proto3" json:"streamKey,omitempty"` +} + +func (x *SelfStreamRequest) Reset() { + *x = SelfStreamRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_runner_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SelfStreamRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SelfStreamRequest) ProtoMessage() {} + +func (x *SelfStreamRequest) ProtoReflect() protoreflect.Message { + mi := &file_runner_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SelfStreamRequest.ProtoReflect.Descriptor instead. +func (*SelfStreamRequest) Descriptor() ([]byte, []int) { + return file_runner_proto_rawDescGZIP(), []int{8} +} + +func (x *SelfStreamRequest) GetStreamKey() string { + if x != nil { + return x.StreamKey + } + return "" +} + +type SelfStreamResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Stream uint64 `protobuf:"varint,1,opt,name=stream,proto3" json:"stream,omitempty"` + Course uint64 `protobuf:"varint,2,opt,name=course,proto3" json:"course,omitempty"` + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` +} + +func (x *SelfStreamResponse) Reset() { + *x = SelfStreamResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_runner_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SelfStreamResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SelfStreamResponse) ProtoMessage() {} + +func (x *SelfStreamResponse) ProtoReflect() protoreflect.Message { + mi := &file_runner_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SelfStreamResponse.ProtoReflect.Descriptor instead. +func (*SelfStreamResponse) Descriptor() ([]byte, []int) { + return file_runner_proto_rawDescGZIP(), []int{9} +} + +func (x *SelfStreamResponse) GetStream() uint64 { + if x != nil { + return x.Stream + } + return 0 +} + +func (x *SelfStreamResponse) GetCourse() uint64 { + if x != nil { + return x.Course + } + return 0 +} + +func (x *SelfStreamResponse) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +var File_runner_proto protoreflect.FileDescriptor + +var file_runner_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9f, 0x01, 0x0a, 0x0d, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x73, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x75, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x06, 0x63, 0x6f, 0x75, 0x72, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, + 0x65, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x22, 0x0a, 0x0e, 0x53, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, + 0x03, 0x6a, 0x6f, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6a, 0x6f, 0x62, 0x22, + 0x42, 0x0a, 0x10, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x6b, 0x65, 0x65, + 0x70, 0x56, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6b, 0x65, 0x65, 0x70, + 0x56, 0x6f, 0x64, 0x22, 0x13, 0x0a, 0x11, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x6e, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x41, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x68, + 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, + 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0xae, 0x01, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x02, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, + 0x12, 0x18, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x43, 0x50, + 0x55, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x43, 0x50, 0x55, 0x12, 0x16, 0x0a, 0x06, + 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x4d, 0x65, + 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x44, 0x69, 0x73, 0x6b, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x44, 0x69, 0x73, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x55, 0x70, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, + 0x22, 0x13, 0x0a, 0x11, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x31, 0x0a, 0x11, 0x53, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4b, 0x65, 0x79, 0x22, 0x5e, 0x0a, 0x12, 0x53, 0x65, 0x6c, 0x66, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x75, 0x72, 0x73, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x63, 0x6f, 0x75, 0x72, 0x73, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x32, 0x9f, 0x01, 0x0a, 0x08, 0x54, 0x6f, 0x52, + 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x44, 0x0a, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x10, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x6e, 0x64, 0x12, + 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x45, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x6e, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xeb, 0x01, 0x0a, 0x0a, 0x46, + 0x72, 0x6f, 0x6d, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x43, 0x0a, 0x08, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x46, + 0x0a, 0x09, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x1a, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x11, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x53, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1b, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x11, 0x5a, 0x0f, 0x72, 0x75, 0x6e, 0x6e, + 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_runner_proto_rawDescOnce sync.Once + file_runner_proto_rawDescData = file_runner_proto_rawDesc +) + +func file_runner_proto_rawDescGZIP() []byte { + file_runner_proto_rawDescOnce.Do(func() { + file_runner_proto_rawDescData = protoimpl.X.CompressGZIP(file_runner_proto_rawDescData) + }) + return file_runner_proto_rawDescData +} + +var file_runner_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_runner_proto_goTypes = []interface{}{ + (*StreamRequest)(nil), // 0: protobuf.StreamRequest + (*StreamResponse)(nil), // 1: protobuf.StreamResponse + (*StreamEndRequest)(nil), // 2: protobuf.StreamEndRequest + (*StreamEndResponse)(nil), // 3: protobuf.StreamEndResponse + (*RegisterRequest)(nil), // 4: protobuf.RegisterRequest + (*RegisterResponse)(nil), // 5: protobuf.RegisterResponse + (*HeartbeatRequest)(nil), // 6: protobuf.HeartbeatRequest + (*HeartbeatResponse)(nil), // 7: protobuf.HeartbeatResponse + (*SelfStreamRequest)(nil), // 8: protobuf.SelfStreamRequest + (*SelfStreamResponse)(nil), // 9: protobuf.SelfStreamResponse + (*timestamp.Timestamp)(nil), // 10: google.protobuf.Timestamp +} +var file_runner_proto_depIdxs = []int32{ + 10, // 0: protobuf.StreamRequest.end:type_name -> google.protobuf.Timestamp + 0, // 1: protobuf.ToRunner.RequestStream:input_type -> protobuf.StreamRequest + 2, // 2: protobuf.ToRunner.RequestStreamEnd:input_type -> protobuf.StreamEndRequest + 4, // 3: protobuf.FromRunner.Register:input_type -> protobuf.RegisterRequest + 6, // 4: protobuf.FromRunner.Heartbeat:input_type -> protobuf.HeartbeatRequest + 8, // 5: protobuf.FromRunner.RequestSelfStream:input_type -> protobuf.SelfStreamRequest + 1, // 6: protobuf.ToRunner.RequestStream:output_type -> protobuf.StreamResponse + 3, // 7: protobuf.ToRunner.RequestStreamEnd:output_type -> protobuf.StreamEndResponse + 5, // 8: protobuf.FromRunner.Register:output_type -> protobuf.RegisterResponse + 7, // 9: protobuf.FromRunner.Heartbeat:output_type -> protobuf.HeartbeatResponse + 9, // 10: protobuf.FromRunner.RequestSelfStream:output_type -> protobuf.SelfStreamResponse + 6, // [6:11] is the sub-list for method output_type + 1, // [1:6] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_runner_proto_init() } +func file_runner_proto_init() { + if File_runner_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_runner_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StreamRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runner_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StreamResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runner_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StreamEndRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runner_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StreamEndResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runner_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RegisterRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runner_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RegisterResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runner_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HeartbeatRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runner_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HeartbeatResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runner_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SelfStreamRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_runner_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SelfStreamResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_runner_proto_rawDesc, + NumEnums: 0, + NumMessages: 10, + NumExtensions: 0, + NumServices: 2, + }, + GoTypes: file_runner_proto_goTypes, + DependencyIndexes: file_runner_proto_depIdxs, + MessageInfos: file_runner_proto_msgTypes, + }.Build() + File_runner_proto = out.File + file_runner_proto_rawDesc = nil + file_runner_proto_goTypes = nil + file_runner_proto_depIdxs = nil +} diff --git a/runner/protobuf/runner_grpc.pb.go b/runner/protobuf/runner_grpc.pb.go new file mode 100644 index 000000000..33a656cf9 --- /dev/null +++ b/runner/protobuf/runner_grpc.pb.go @@ -0,0 +1,303 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.12.4 +// source: runner.proto + +package protobuf + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// ToRunnerClient is the client API for ToRunner service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ToRunnerClient interface { + // Requests a stream from a lecture hall + RequestStream(ctx context.Context, in *StreamRequest, opts ...grpc.CallOption) (*StreamResponse, error) + RequestStreamEnd(ctx context.Context, in *StreamEndRequest, opts ...grpc.CallOption) (*StreamEndResponse, error) +} + +type toRunnerClient struct { + cc grpc.ClientConnInterface +} + +func NewToRunnerClient(cc grpc.ClientConnInterface) ToRunnerClient { + return &toRunnerClient{cc} +} + +func (c *toRunnerClient) RequestStream(ctx context.Context, in *StreamRequest, opts ...grpc.CallOption) (*StreamResponse, error) { + out := new(StreamResponse) + err := c.cc.Invoke(ctx, "/protobuf.ToRunner/RequestStream", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *toRunnerClient) RequestStreamEnd(ctx context.Context, in *StreamEndRequest, opts ...grpc.CallOption) (*StreamEndResponse, error) { + out := new(StreamEndResponse) + err := c.cc.Invoke(ctx, "/protobuf.ToRunner/RequestStreamEnd", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ToRunnerServer is the server API for ToRunner service. +// All implementations must embed UnimplementedToRunnerServer +// for forward compatibility +type ToRunnerServer interface { + // Requests a stream from a lecture hall + RequestStream(context.Context, *StreamRequest) (*StreamResponse, error) + RequestStreamEnd(context.Context, *StreamEndRequest) (*StreamEndResponse, error) + mustEmbedUnimplementedToRunnerServer() +} + +// UnimplementedToRunnerServer must be embedded to have forward compatible implementations. +type UnimplementedToRunnerServer struct { +} + +func (UnimplementedToRunnerServer) RequestStream(context.Context, *StreamRequest) (*StreamResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RequestStream not implemented") +} +func (UnimplementedToRunnerServer) RequestStreamEnd(context.Context, *StreamEndRequest) (*StreamEndResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RequestStreamEnd not implemented") +} +func (UnimplementedToRunnerServer) mustEmbedUnimplementedToRunnerServer() {} + +// UnsafeToRunnerServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ToRunnerServer will +// result in compilation errors. +type UnsafeToRunnerServer interface { + mustEmbedUnimplementedToRunnerServer() +} + +func RegisterToRunnerServer(s grpc.ServiceRegistrar, srv ToRunnerServer) { + s.RegisterService(&ToRunner_ServiceDesc, srv) +} + +func _ToRunner_RequestStream_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StreamRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ToRunnerServer).RequestStream(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protobuf.ToRunner/RequestStream", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ToRunnerServer).RequestStream(ctx, req.(*StreamRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ToRunner_RequestStreamEnd_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StreamEndRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ToRunnerServer).RequestStreamEnd(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protobuf.ToRunner/RequestStreamEnd", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ToRunnerServer).RequestStreamEnd(ctx, req.(*StreamEndRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ToRunner_ServiceDesc is the grpc.ServiceDesc for ToRunner service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ToRunner_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "protobuf.ToRunner", + HandlerType: (*ToRunnerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "RequestStream", + Handler: _ToRunner_RequestStream_Handler, + }, + { + MethodName: "RequestStreamEnd", + Handler: _ToRunner_RequestStreamEnd_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "runner.proto", +} + +// FromRunnerClient is the client API for FromRunner service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type FromRunnerClient interface { + // Register is a request to the server to join the runners pool. + Register(ctx context.Context, in *RegisterRequest, opts ...grpc.CallOption) (*RegisterResponse, error) + Heartbeat(ctx context.Context, in *HeartbeatRequest, opts ...grpc.CallOption) (*HeartbeatResponse, error) + RequestSelfStream(ctx context.Context, in *SelfStreamRequest, opts ...grpc.CallOption) (*SelfStreamResponse, error) +} + +type fromRunnerClient struct { + cc grpc.ClientConnInterface +} + +func NewFromRunnerClient(cc grpc.ClientConnInterface) FromRunnerClient { + return &fromRunnerClient{cc} +} + +func (c *fromRunnerClient) Register(ctx context.Context, in *RegisterRequest, opts ...grpc.CallOption) (*RegisterResponse, error) { + out := new(RegisterResponse) + err := c.cc.Invoke(ctx, "/protobuf.FromRunner/Register", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *fromRunnerClient) Heartbeat(ctx context.Context, in *HeartbeatRequest, opts ...grpc.CallOption) (*HeartbeatResponse, error) { + out := new(HeartbeatResponse) + err := c.cc.Invoke(ctx, "/protobuf.FromRunner/Heartbeat", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *fromRunnerClient) RequestSelfStream(ctx context.Context, in *SelfStreamRequest, opts ...grpc.CallOption) (*SelfStreamResponse, error) { + out := new(SelfStreamResponse) + err := c.cc.Invoke(ctx, "/protobuf.FromRunner/RequestSelfStream", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// FromRunnerServer is the server API for FromRunner service. +// All implementations must embed UnimplementedFromRunnerServer +// for forward compatibility +type FromRunnerServer interface { + // Register is a request to the server to join the runners pool. + Register(context.Context, *RegisterRequest) (*RegisterResponse, error) + Heartbeat(context.Context, *HeartbeatRequest) (*HeartbeatResponse, error) + RequestSelfStream(context.Context, *SelfStreamRequest) (*SelfStreamResponse, error) + mustEmbedUnimplementedFromRunnerServer() +} + +// UnimplementedFromRunnerServer must be embedded to have forward compatible implementations. +type UnimplementedFromRunnerServer struct { +} + +func (UnimplementedFromRunnerServer) Register(context.Context, *RegisterRequest) (*RegisterResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Register not implemented") +} +func (UnimplementedFromRunnerServer) Heartbeat(context.Context, *HeartbeatRequest) (*HeartbeatResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Heartbeat not implemented") +} +func (UnimplementedFromRunnerServer) RequestSelfStream(context.Context, *SelfStreamRequest) (*SelfStreamResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RequestSelfStream not implemented") +} +func (UnimplementedFromRunnerServer) mustEmbedUnimplementedFromRunnerServer() {} + +// UnsafeFromRunnerServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to FromRunnerServer will +// result in compilation errors. +type UnsafeFromRunnerServer interface { + mustEmbedUnimplementedFromRunnerServer() +} + +func RegisterFromRunnerServer(s grpc.ServiceRegistrar, srv FromRunnerServer) { + s.RegisterService(&FromRunner_ServiceDesc, srv) +} + +func _FromRunner_Register_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RegisterRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(FromRunnerServer).Register(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protobuf.FromRunner/Register", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(FromRunnerServer).Register(ctx, req.(*RegisterRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _FromRunner_Heartbeat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HeartbeatRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(FromRunnerServer).Heartbeat(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protobuf.FromRunner/Heartbeat", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(FromRunnerServer).Heartbeat(ctx, req.(*HeartbeatRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _FromRunner_RequestSelfStream_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SelfStreamRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(FromRunnerServer).RequestSelfStream(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protobuf.FromRunner/RequestSelfStream", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(FromRunnerServer).RequestSelfStream(ctx, req.(*SelfStreamRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// FromRunner_ServiceDesc is the grpc.ServiceDesc for FromRunner service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var FromRunner_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "protobuf.FromRunner", + HandlerType: (*FromRunnerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Register", + Handler: _FromRunner_Register_Handler, + }, + { + MethodName: "Heartbeat", + Handler: _FromRunner_Heartbeat_Handler, + }, + { + MethodName: "RequestSelfStream", + Handler: _FromRunner_RequestSelfStream_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "runner.proto", +} diff --git a/runner/runner.go b/runner/runner.go new file mode 100644 index 000000000..44473ff3b --- /dev/null +++ b/runner/runner.go @@ -0,0 +1,244 @@ +package runner + +import ( + "context" + "github.com/caarlos0/env" + "github.com/google/uuid" + "github.com/tum-dev/gocast/runner/actions" + "github.com/tum-dev/gocast/runner/pkg/logging" + "github.com/tum-dev/gocast/runner/pkg/netutil" + "github.com/tum-dev/gocast/runner/protobuf" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/keepalive" + "google.golang.org/grpc/reflection" + + "fmt" + "log/slog" + "net" + "os" + "time" +) + +type config struct { + LogFmt string `env:"LOG_FMT" envDefault:"txt"` + LogLevel string `env:"LOG_LEVEL" envDefault:"debug"` + Port int `env:"PORT" envDefault:"0"` + StoragePath string `env:"STORAGE_PATH" envDefault:"storage/mass"` + SegmentPath string `env:"SEGMENT_PATH" envDefault:"storage/live"` + RecPath string `env:"REC_PATH" envDefault:"storage/rec"` + GocastServer string `env:"GOCAST_SERVER" envDefault:"localhost:50056"` + Hostname string `env:"REALHOST" envDefault:"localhost"` +} + +type Runner struct { + cfg config + log *slog.Logger + + JobCount chan int + draining bool + jobs map[string]*Job + + actions actions.ActionProvider + hlsServer *HLSServer + + protobuf.UnimplementedToRunnerServer +} + +func NewRunner(v string) *Runner { + log := logging.GetLogger(v) + var cfg config + if err := env.Parse(&cfg); err != nil { + log.Error("error parsing config", "error", err) + } + log.Info("config loaded", "config", cfg) + + return &Runner{ + log: log, + JobCount: make(chan int, 1), + draining: false, + cfg: cfg, + jobs: make(map[string]*Job), + actions: actions.ActionProvider{ + Log: log, + SegmentDir: cfg.SegmentPath, + RecDir: cfg.RecPath, + MassDir: cfg.StoragePath, + }, + hlsServer: NewHLSServer(cfg.SegmentPath, log.WithGroup("HLSServer")), + } +} + +func (r *Runner) Run() { + r.log.Info("Running!") + if r.cfg.Port == 0 { + r.log.Info("Getting free port") + p, err := netutil.GetFreePort() + if err != nil { + r.log.Error("Failed to get free port", "error", err) + os.Exit(1) + } + r.cfg.Port = p + } + r.log.Info("using port", "port", r.cfg.Port) + + go r.InitApiGrpc() + go r.hlsServer.Start() + + r.RegisterWithGocast(5) + r.log.Info("successfully connected to gocast") +} + +func (r *Runner) Drain() { + r.log.Info("Runner set to drain.") + r.draining = true +} + +func (r *Runner) InitApiGrpc() { + r.log.Info("Starting gRPC server", "port", r.cfg.Port) + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", r.cfg.Port)) + if err != nil { + r.log.Error("failed to listen", "error", err) + os.Exit(1) + } + grpcServer := grpc.NewServer(grpc.KeepaliveParams(keepalive.ServerParameters{ + MaxConnectionIdle: time.Minute, + MaxConnectionAge: time.Minute, + MaxConnectionAgeGrace: time.Second * 5, + Time: time.Minute * 10, + Timeout: time.Second * 20, + }), logging.GetGrpcLogInterceptor(r.log)) + protobuf.RegisterToRunnerServer(grpcServer, r) + + reflection.Register(grpcServer) + if err := grpcServer.Serve(lis); err != nil { + r.log.Error("failed to serve", "error", err) + os.Exit(1) + } + +} + +const registerRetries = 5 + +func (r *Runner) RegisterWithGocast(retries int) { + r.log.Debug("connecting with gocast", slog.Group("conn", "host", r.cfg.GocastServer, "retriesLeft", retries)) + if retries == 0 { + r.log.Error("no more retries left, can't connect to gocast") + os.Exit(1) + } + con, err := r.dialIn() + if err != nil { + r.log.Warn("error connecting to gocast", "error", err, "sleeping(s)", registerRetries-retries) + time.Sleep(time.Second * time.Duration(registerRetries-retries)) + r.RegisterWithGocast(retries - 1) + return + } + _, err = con.Register(context.Background(), &protobuf.RegisterRequest{Hostname: r.cfg.Hostname, Port: int32(r.cfg.Port)}) + if err != nil { + r.log.Warn("error registering with gocast", "error", err, "sleeping(s)", registerRetries-retries) + time.Sleep(time.Second * time.Duration(registerRetries-retries)) + r.RegisterWithGocast(retries - 1) + return + } +} + +// dialIn connects to manager instance and returns a client +func (r *Runner) dialIn() (protobuf.FromRunnerClient, error) { + credentials := insecure.NewCredentials() + conn, err := grpc.Dial(r.cfg.GocastServer, grpc.WithTransportCredentials(credentials)) + if err != nil { + return nil, err + } + return protobuf.NewFromRunnerClient(conn), nil +} + +type Job struct { + ID string + Actions []*actions.Action + + Log *slog.Logger +} + +// Run triggers all actions in the job sequentially. +func (j *Job) Run(ctx context.Context) { + for i := range j.Actions { + if j.Actions[i].Canceled { + j.Log.Info("skipping action because it was canceled", "action", j.Actions[i].Type) + continue + } + // create new context to make each action cancelable individually + actionContext, cancel := context.WithCancelCause(ctx) + j.Actions[i].Cancel = cancel + j.Log.Info("running action", "action", j.Actions[i].Type) + c, err := j.Actions[i].ActionFn(actionContext, j.Log.With("action", j.Actions[i].Type)) + if err != nil { + // ensure context is canceled even on error + j.Log.Error("action failed", "error", err, "action", j.Actions[i].Type) + j.Actions[i].Cancel(err) + return + } + // pass context to next action without cancel + ctx = context.WithoutCancel(c) + + j.Actions[i].Cancel(nil) + } +} + +func (j *Job) Cancel(reason error, actionTypes ...actions.ActionType) { + for i := len(j.Actions) - 1; i >= 0; i-- { // cancel actions in reverse order to ensure all actions are canceled when they run + for _, actionType := range actionTypes { + if j.Actions[i].Type == actionType { + if j.Actions[i].Cancel != nil { + // action already running -> cancel context + j.Actions[i].Cancel(reason) + } + // set canceled flag -> stop action from being started + j.Actions[i].Canceled = true + } + } + } + j.Log.Info("job canceled", "reason", reason) +} + +// AddJob adds a job to the runner and starts it. +func (r *Runner) AddJob(ctx context.Context, a []*actions.Action) string { + jobID := uuid.New().String() + r.jobs[jobID] = &Job{ + ID: jobID, + Actions: a, + + Log: enrichLogger(r.log, ctx).With("jobID", jobID), + } + // notify main loop about current job count + r.JobCount <- len(r.jobs) + done := make(chan struct{}) + + go func() { + defer func() { done <- struct{}{} }() + r.jobs[jobID].Run(ctx) + }() + go func() { + select { + case d := <-done: + // update job count and remove job from map after it's done + r.log.Info("job cancelled", "jobID", jobID, "reason", ctx.Err(), "cancelReason", d) + delete(r.jobs, jobID) + r.JobCount <- len(r.jobs) + } + }() + return jobID +} + +// enrichLogger adds StreamID, CourseID, Version to the logger if present in the context +func enrichLogger(log *slog.Logger, ctx context.Context) *slog.Logger { + if streamID, ok := ctx.Value("stream").(uint64); ok { + log = log.With("streamID", streamID) + } + if courseID, ok := ctx.Value("course").(uint64); ok { + log = log.With("courseID", courseID) + } + if version, ok := ctx.Value("version").(string); ok { + log = log.With("version", version) + } + return log +} diff --git a/runner/runner.proto b/runner/runner.proto new file mode 100644 index 000000000..0cad3659a --- /dev/null +++ b/runner/runner.proto @@ -0,0 +1,70 @@ +syntax = "proto3"; +package protobuf; +option go_package = "runner/protobuf"; + +import "google/protobuf/timestamp.proto"; + +service ToRunner { + // Requests a stream from a lecture hall + rpc RequestStream (StreamRequest) returns (StreamResponse) {} + rpc RequestStreamEnd (StreamEndRequest) returns (StreamEndResponse) {} +} + +message StreamRequest { + uint64 stream = 1; + uint64 course = 2; + string version = 3; + google.protobuf.Timestamp end = 4; + string source = 5; +} + + +message StreamResponse { + string job = 1; +} + +message StreamEndRequest { + string jobID = 1; + bool keepVod = 2; +} + +message StreamEndResponse {} + +// FromRunner service defines communication from runners to gocast +service FromRunner { + // Register is a request to the server to join the runners pool. + rpc Register (RegisterRequest) returns (RegisterResponse) {} + rpc Heartbeat (HeartbeatRequest) returns (HeartbeatResponse) {} + rpc RequestSelfStream (SelfStreamRequest) returns (SelfStreamResponse) {} +} + +message RegisterRequest { + string hostname = 1; + int32 port = 2; +} + +message RegisterResponse { +} + +message HeartbeatRequest { + uint64 ID = 1; + uint32 Workload = 2; + string Version = 3; + string CPU = 4; + string Memory = 5; + string Disk = 6; + string Uptime = 7; +} + +message HeartbeatResponse {} + +message SelfStreamRequest { + string streamKey = 1; +} + +message SelfStreamResponse { + uint64 stream = 1; + uint64 course = 2; + string version = 3; +} + diff --git a/tools/stream-signing.go b/tools/stream-signing.go index 60ff1af01..0c61f034c 100644 --- a/tools/stream-signing.go +++ b/tools/stream-signing.go @@ -2,8 +2,8 @@ package tools import ( "fmt" - "github.com/golang-jwt/jwt/v4" "github.com/TUM-Dev/gocast/model" + "github.com/golang-jwt/jwt/v4" "strings" "time" ) @@ -33,7 +33,7 @@ func SetSignedPlaylists(s *model.Stream, user *model.User, allowDownloading bool } for _, playlist := range playlists { - if strings.Contains(playlist.Playlist, "lrz.de") { // todo: remove after migration from lrz services + if strings.Contains(playlist.Playlist, "localhost") || strings.Contains(playlist.Playlist, "lrz.de") { // todo: remove after migration from lrz services continue } diff --git a/tools/tum/events.go b/tools/tum/events.go index f56b83f3b..6e8cce998 100644 --- a/tools/tum/events.go +++ b/tools/tum/events.go @@ -4,10 +4,10 @@ import ( "context" "errors" "fmt" - "github.com/antchfx/xmlquery" "github.com/TUM-Dev/gocast/dao" "github.com/TUM-Dev/gocast/model" "github.com/TUM-Dev/gocast/tools" + "github.com/antchfx/xmlquery" uuid "github.com/satori/go.uuid" log "github.com/sirupsen/logrus" "strconv" @@ -92,6 +92,13 @@ func GetEventsForCourses(courses []model.Course, daoWrapper dao.DaoWrapper) { stream, err := daoWrapper.StreamsDao.GetStreamByTumOnlineID(context.Background(), event.SingleEventID) if err != nil { // Lecture does not exist yet log.Info("Adding course") + // When defined use course wide stream key + var streamKey string + if course.StreamKey == "" { + streamKey = strings.ReplaceAll(uuid.NewV4().String(), "-", "") + } else { + streamKey = course.StreamKey + } course.Streams = append(course.Streams, model.Stream{ CourseID: course.ID, Start: event.Start, @@ -100,7 +107,7 @@ func GetEventsForCourses(courses []model.Course, daoWrapper dao.DaoWrapper) { RoomCode: event.RoomCode, EventTypeName: event.SingleEventTypeName, TUMOnlineEventID: event.SingleEventID, - StreamKey: strings.ReplaceAll(uuid.NewV4().String(), "-", ""), + StreamKey: streamKey, PlaylistUrl: "", LiveNow: false, }) diff --git a/web/template/home.gohtml b/web/template/home.gohtml index 114b6fd55..e3fc93ee7 100644 --- a/web/template/home.gohtml +++ b/web/template/home.gohtml @@ -788,7 +788,9 @@ + {{template "footer" .VersionTag}} + diff --git a/web/template/partial/course/manage/course_actions.gohtml b/web/template/partial/course/manage/course_actions.gohtml index b1006bcaa..d538b9caa 100644 --- a/web/template/partial/course/manage/course_actions.gohtml +++ b/web/template/partial/course/manage/course_actions.gohtml @@ -11,6 +11,10 @@ +

Stream Server: - + + @click="() => global.copyToClipboard('{{$ingestBase}}{{$course.Slug}}'+'?secret=' + lectureData.streamKey)">
Stream Key: - + + @click="() => global.copyToClipboard(lectureData.courseSlug)">
{{if ne $user.Role 1}} Want to stream from a lecture hall instead? Please reach out to the RBG. @@ -387,10 +387,19 @@ after:transition-all dark:border-gray-600 peer-checked:bg-blue-600 dark:peer-checked:bg-indigo-600"> Chat Enabled +     + +