From 2c1180c9420675a46ce2caea57121ca10765faed Mon Sep 17 00:00:00 2001 From: Jeremy Lewi Date: Sun, 24 Nov 2024 20:55:32 -0800 Subject: [PATCH] Fix sqlite busy errors (#347) Fix sqlite busy errors with session based learning There were a couple different errors * We were still enqueing block ids and not sessions in some places * When we call Enqueue we should filter out sessions that aren't suitable for learning; this should minimize DB contention * Improve logging By default sqlite doesn't have any retries for SQLITE_BUSY errors. We could configure a timeout for retries using a PRAGMA; we should probably do that but I want to hold off to see if this PR fixes the contention. * Filed #348 to follow up on this Use ko to create docker images as part of the development process * This is convenient when we want to build a docker image from our latest changes without doing a relase. * Change the Dockerfile to not include any arguments so that it is compatible with the image ko builds --- app/.ko.yaml | 15 ++ app/Dockerfile | 3 +- app/pkg/analyze/analyzer.go | 18 +-- app/pkg/analyze/analyzer_test.go | 15 +- app/pkg/analyze/dbs.go | 17 +++ app/pkg/analyze/session_builder.go | 16 +- app/pkg/analyze/session_builder_test.go | 4 +- app/pkg/analyze/session_manager.go | 140 +++++++++++------- app/pkg/learn/learner.go | 120 ++++++++++----- ...devcontainer-01JDDK3R7CDQP3ZBEM1TQRK466.md | 51 +++++++ developer_guides/devcontainer.md | 15 ++ manifests/statefulset.yaml | 1 + 12 files changed, 286 insertions(+), 129 deletions(-) create mode 100644 app/.ko.yaml create mode 100644 developer_guides/devcontainer-01JDDK3R7CDQP3ZBEM1TQRK466.md create mode 100644 developer_guides/devcontainer.md diff --git a/app/.ko.yaml b/app/.ko.yaml new file mode 100644 index 00000000..8041a7b7 --- /dev/null +++ b/app/.ko.yaml @@ -0,0 +1,15 @@ +# https://ko.build/configuration/#setting-build-flags-and-ldflags +defaultFlags: + # https://ko.build/configuration/#naming-images + # --base-import-paths removes the md5hash from the image name + # but it appends to the repo path a name based on the path of the module which in this case is "app" + # TODO(jeremy): This doesn't seem to be working I still have to set the flag when I run ko + - --base-import-paths +builds: + - id: foyle + ldflags: + - -s + - -w + - -X 'github.com/jlewi/foyle/app/cmd.date={{.Date}}' + - -X 'github.com/jlewi/foyle/app/cmd.version=dev' + - -X 'github.com/jlewi/foyle/app/cmd.commit={{.Git.ShortCommit}}' \ No newline at end of file diff --git a/app/Dockerfile b/app/Dockerfile index 8e3c2b6d..cd8b812d 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -29,4 +29,5 @@ FROM ${RUNTIME_IMAGE} COPY --from=builder /workspace/app/foyle / -ENTRYPOINT ["/foyle", "serve"] +# N.B. Don't set any arguments because we want to be compatible with the images ko builds +ENTRYPOINT ["/foyle"] diff --git a/app/pkg/analyze/analyzer.go b/app/pkg/analyze/analyzer.go index 6f151ae2..066a0d4b 100644 --- a/app/pkg/analyze/analyzer.go +++ b/app/pkg/analyze/analyzer.go @@ -153,7 +153,7 @@ type blockItem struct { } // PostSessionEvent interface for functions to post session events. -type PostSessionEvent func(id string) error +type PostSessionEvent func(session *logspb.Session) error // Run runs the analyzer; continually processing logs. // learnNotifier is an optional function that will be called when a block is updated. @@ -466,14 +466,6 @@ func (a *Analyzer) processLogEvent(ctx context.Context, entry *api.LogEntry) { }); err != nil { log.Error(err, "Failed to update block with execution", "blockId", bid) } - // We need to enqueue the block for processing since it was executed. - // The learner will decide whether the blockLog has all the information it needs otherwise it will - // disregard the block item and wait for further events. - if a.learnNotifier != nil { - if err := a.learnNotifier(bid); err != nil { - log.Error(err, "Error notifying block event", "blockId", bid) - } - } case v1alpha1.LogEventType_ACCEPTED: fallthrough case v1alpha1.LogEventType_REJECTED: @@ -666,14 +658,6 @@ func (a *Analyzer) handleBlockEvents(ctx context.Context) { if err != nil { log.Error(err, "Error processing block", "blockId", blockItem.id) } - // We need to enqueue the block for processing since it was executed. - // The learner will decide whether the blockLog has all the information it needs otherwise it will - // disregard the block item and wait for further events. - if a.learnNotifier != nil { - if err := a.learnNotifier(blockItem.id); err != nil { - log.Error(err, "Error notifying block event", "blockId", blockItem.id) - } - } if a.signalBlockDone != nil { a.signalBlockDone <- blockItem.id } diff --git a/app/pkg/analyze/analyzer_test.go b/app/pkg/analyze/analyzer_test.go index 94398155..b3038b7c 100644 --- a/app/pkg/analyze/analyzer_test.go +++ b/app/pkg/analyze/analyzer_test.go @@ -174,15 +174,15 @@ type fakeNotifier struct { counts map[string]int } -func (f *fakeNotifier) PostBlockEvent(blockID string) error { +func (f *fakeNotifier) PostSession(session *logspb.Session) error { if f.counts == nil { f.counts = make(map[string]int) } - if _, ok := f.counts[blockID]; !ok { - f.counts[blockID] = 0 + if _, ok := f.counts[session.GetContextId()]; !ok { + f.counts[session.GetContextId()] = 0 } - f.counts[blockID] += 1 + f.counts[session.GetContextId()] += 1 return nil } @@ -276,7 +276,7 @@ func Test_Analyzer(t *testing.T) { a.signalBlockDone = blockProccessed fakeNotifier := &fakeNotifier{} - if err := a.Run(context.Background(), []string{rawDir}, fakeNotifier.PostBlockEvent); err != nil { + if err := a.Run(context.Background(), []string{rawDir}, fakeNotifier.PostSession); err != nil { t.Fatalf("Analyze failed: %v", err) } @@ -323,11 +323,6 @@ func Test_Analyzer(t *testing.T) { t.Errorf("Expected ExecutedBlock to be set") } - // Check the block notifier was called twice; once after the generated block and once after the executed block - if fakeNotifier.counts[expectedBlockID] != 2 { - t.Errorf("Expected block notifier to be called twice but got %d", fakeNotifier.counts[expectedBlockID]) - } - // Now append some logs to the logFile and see that they get processed f, err := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY, 0644) if err != nil { diff --git a/app/pkg/analyze/dbs.go b/app/pkg/analyze/dbs.go index 40b8b7ed..8b37955a 100644 --- a/app/pkg/analyze/dbs.go +++ b/app/pkg/analyze/dbs.go @@ -1,9 +1,15 @@ package analyze import ( + "context" + "github.com/cockroachdb/pebble" "github.com/jlewi/foyle/app/pkg/dbutil" + "github.com/jlewi/foyle/app/pkg/logs" logspb "github.com/jlewi/foyle/protos/go/foyle/logs" + "github.com/pkg/errors" + "modernc.org/sqlite" + sqlite3 "modernc.org/sqlite/lib" ) // NewLockingBlocksDB helper function to create a new LockingDB for BlockLog. @@ -39,3 +45,14 @@ func getLogEntriesVersion(m *logspb.LogEntries) string { func setLogEntriesVersion(m *logspb.LogEntries, version string) { m.ResourceVersion = version } + +func logDBErrors(ctx context.Context, err error) { + log := logs.FromContext(ctx) + var sqlLiteErr *sqlite.Error + if errors.As(err, &sqlLiteErr) { + if sqlLiteErr.Code() == sqlite3.SQLITE_BUSY { + sqlLiteBusyErrs.Inc() + log.Error(err, "SQLITE_BUSY") + } + } +} diff --git a/app/pkg/analyze/session_builder.go b/app/pkg/analyze/session_builder.go index 33516661..2fc3ef63 100644 --- a/app/pkg/analyze/session_builder.go +++ b/app/pkg/analyze/session_builder.go @@ -65,18 +65,22 @@ func (p *sessionBuilder) processLogEvent(entry *api.LogEntry, notifier PostSessi return } + var session *logspb.Session updateFunc := func(s *logspb.Session) error { - return updateSessionFromEvent(event, entry.Time(), s) + err := updateSessionFromEvent(event, entry.Time(), s) + // Make a copy of the updated session because we will process it down below + session = s + return err } if err := p.sessions.Update(context.Background(), event.GetContextId(), updateFunc); err != nil { - log.Error(err, "Failed to update session", "event", event) + log.Error(err, "Failed to update session", "event", event, "contextId", event.GetContextId()) return } if event.Type == v1alpha1.LogEventType_SESSION_END { - if err := notifier(event.GetContextId()); err != nil { - log.Error(err, "Failed to send session process event") + if err := notifier(session); err != nil { + log.Error(err, "Failed to send session process event", "contextId", event.GetContextId()) } } } @@ -90,7 +94,7 @@ func (p *sessionBuilder) processLLMUsage(entry *api.LogEntry) { } contextId, ok := entry.GetString("contextId") if !ok { - log.Error(errors.New("Failed to handle LLMUsage log entry"), "LLMUsage is missing contextId", "entry", entry) + log.Error(errors.New("Failed to handle LLMUsage log entry"), "LLMUsage is missing contextId", "entry", entry, "contextId", contextId) return } @@ -99,7 +103,7 @@ func (p *sessionBuilder) processLLMUsage(entry *api.LogEntry) { } if err := p.sessions.Update(context.Background(), contextId, updateFunc); err != nil { - log.Error(err, "Failed to update session", "usage", usage) + log.Error(err, "Failed to update session", "usage", usage, "contextId", contextId) } } diff --git a/app/pkg/analyze/session_builder_test.go b/app/pkg/analyze/session_builder_test.go index 0d442bbe..2648c613 100644 --- a/app/pkg/analyze/session_builder_test.go +++ b/app/pkg/analyze/session_builder_test.go @@ -72,8 +72,8 @@ func setup() (testTuple, error) { } // Process the log entry -func testNotifier(contextId string) error { - fmt.Printf("Received session end event for context: %v", contextId) +func testNotifier(session *logspb.Session) error { + fmt.Printf("Received session end event for context: %v", session.GetContextId()) return nil } diff --git a/app/pkg/analyze/session_manager.go b/app/pkg/analyze/session_manager.go index ce4aa0b5..dc87917d 100644 --- a/app/pkg/analyze/session_manager.go +++ b/app/pkg/analyze/session_manager.go @@ -8,6 +8,9 @@ import ( "os" "path/filepath" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "connectrpc.com/connect" "github.com/jlewi/foyle/app/pkg/logs" "github.com/jlewi/foyle/app/pkg/runme/converters" @@ -28,6 +31,21 @@ const ( SQLLiteDriver = "sqlite" ) +var ( + sessCounter = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "session_updates", + Help: "Number of sessions updated", + }, + []string{"status"}, + ) + + sqlLiteBusyErrs = promauto.NewCounter(prometheus.CounterOpts{ + Name: "sqlite_busy", + Help: "Number of operations that failed because sqlite was busy", + }) +) + // GetDDL return the DDL for the database. // This is a hack because the DDL statements for the sessions and eval results tables are in the same file and package. // The Evaluator needs to be able to get the DDL in order to create the eval results table. We should clean this up @@ -68,6 +86,7 @@ func (db *SessionsManager) Get(ctx context.Context, contextID string) (*logspb.S sessRow, err := queries.GetSession(ctx, contextID) if err != nil { + logDBErrors(ctx, err) return nil, err } @@ -92,81 +111,96 @@ func (db *SessionsManager) Update(ctx context.Context, contextID string, updateF } log = log.WithValues("contextId", contextID) + sessCounter.WithLabelValues("start").Inc() + tx, err := db.db.BeginTx(ctx, &sql.TxOptions{}) if err != nil { + // DO NOT COMMIT + sessCounter.WithLabelValues("failedstart").Inc() return errors.Wrapf(err, "Failed to start transaction") } - queries := db.queries.WithTx(tx) - // Read the record - sessRow, err := queries.GetSession(ctx, contextID) + err = func() error { + queries := db.queries.WithTx(tx) + // Read the record + sessRow, err := queries.GetSession(ctx, contextID) - // If the session doesn't exist then we do nothing because session is initializeed to empty session - session := &logspb.Session{ - ContextId: contextID, - } - if err != nil { - if err != sql.ErrNoRows { - if txErr := tx.Rollback(); txErr != nil { - log.Error(txErr, "Failed to rollback transaction") - } - return errors.Wrapf(err, "Failed to get session with id %v", contextID) + // If the session doesn't exist then we do nothing because session is initializeed to empty session + session := &logspb.Session{ + ContextId: contextID, } - } else { - // Deserialize the proto - if err := proto.Unmarshal(sessRow.Proto, session); err != nil { - if txErr := tx.Rollback(); txErr != nil { - log.Error(txErr, "Failed to rollback transaction") + if err != nil { + logDBErrors(ctx, err) + if err != sql.ErrNoRows { + // DO NOT COMMIT + sessCounter.WithLabelValues("failedget").Inc() + return errors.Wrapf(err, "Failed to get session with id %v", contextID) + } + // ErrNoRows means the session doesn't exist so we just continue with the empty session + } else { + // Deserialize the proto + if err := proto.Unmarshal(sessRow.Proto, session); err != nil { + return errors.Wrapf(err, "Failed to deserialize session") } - return errors.Wrapf(err, "Failed to deserialize session") } - } - if err := updateFunc(session); err != nil { - if txErr := tx.Rollback(); txErr != nil { - log.Error(txErr, "Failed to rollback transaction") + // DO NOT COMMIT + sessCounter.WithLabelValues("callupdatefunc").Inc() + + if err := updateFunc(session); err != nil { + return errors.Wrapf(err, "Failed to update session") } - return errors.Wrapf(err, "Failed to update session") - } - newRow, err := protoToRow(session) - if err != nil { - if txErr := tx.Rollback(); txErr != nil { - log.Error(txErr, "Failed to rollback transaction") + newRow, err := protoToRow(session) + if err != nil { + return errors.Wrapf(err, "Failed to convert session proto to table row") } - return errors.Wrapf(err, "Failed to convert session proto to table row") - } - if newRow.Contextid != contextID { - if txErr := tx.Rollback(); txErr != nil { - log.Error(txErr, "Failed to rollback transaction") + if newRow.Contextid != contextID { + return errors.WithStack(errors.Errorf("contextID in session doesn't match contextID. Update was called with contextID: %v but session has contextID: %v", contextID, newRow.Contextid)) } - return errors.WithStack(errors.Errorf("contextID in session doesn't match contextID. Update was called with contextID: %v but session has contextID: %v", contextID, newRow.Contextid)) - } - update := fsql.UpdateSessionParams{ - Contextid: contextID, - Proto: newRow.Proto, - Starttime: newRow.Starttime, - Endtime: newRow.Endtime, - Selectedid: newRow.Selectedid, - Selectedkind: newRow.Selectedkind, - TotalInputTokens: newRow.TotalInputTokens, - TotalOutputTokens: newRow.TotalOutputTokens, - NumGenerateTraces: newRow.NumGenerateTraces, - } + update := fsql.UpdateSessionParams{ + Contextid: contextID, + Proto: newRow.Proto, + Starttime: newRow.Starttime, + Endtime: newRow.Endtime, + Selectedid: newRow.Selectedid, + Selectedkind: newRow.Selectedkind, + TotalInputTokens: newRow.TotalInputTokens, + TotalOutputTokens: newRow.TotalOutputTokens, + NumGenerateTraces: newRow.NumGenerateTraces, + } - if err := queries.UpdateSession(ctx, update); err != nil { + // DO NOT COMMIT + sessCounter.WithLabelValues("callupdatesession").Inc() + if err := queries.UpdateSession(ctx, update); err != nil { + logDBErrors(ctx, err) + return errors.Wrapf(err, "Failed to update session") + } + return nil + }() + + if err == nil { + if err := tx.Commit(); err != nil { + logDBErrors(ctx, err) + log.Error(err, "Failed to commit transaction") + sessCounter.WithLabelValues("commitfail").Inc() + return errors.Wrapf(err, "Failed to commit transaction") + } + sessCounter.WithLabelValues("success").Inc() + } else { + logDBErrors(ctx, err) + sessCounter.WithLabelValues("fail").Inc() + log.Error(err, "Failed to update session") if txErr := tx.Rollback(); txErr != nil { log.Error(txErr, "Failed to rollback transaction") } - return errors.Wrapf(err, "Failed to update session") - } - - if err := tx.Commit(); err != nil { - return errors.Wrapf(err, "Failed to commit transaction") + return err } + // DO NOT COMMIT + sessCounter.WithLabelValues("done").Inc() return nil } diff --git a/app/pkg/learn/learner.go b/app/pkg/learn/learner.go index fc8dc9ea..501b8c2e 100644 --- a/app/pkg/learn/learner.go +++ b/app/pkg/learn/learner.go @@ -7,6 +7,9 @@ import ( "strings" "sync" + "github.com/go-logr/zapr" + "go.uber.org/zap" + "github.com/jlewi/foyle/app/pkg/analyze" "github.com/jlewi/foyle/app/pkg/runme/converters" logspb "github.com/jlewi/foyle/protos/go/foyle/logs" @@ -97,11 +100,20 @@ func (l *Learner) Start(ctx context.Context, postFunc PostLearnEvent) error { } // Enqueue adds an example id to be reconciled -func (l *Learner) Enqueue(id string) error { +func (l *Learner) Enqueue(session *logspb.Session) error { if l.queue.ShuttingDown() { return errors.New("Queue is shutting down; can't enqueue anymore items") } - l.queue.Add(id) + + if !isLearnable(session) { + // Filter out the sessions that aren't learnable + // We don't want to enqueue them because that will put unnecessary load on the DB + return nil + } + + log := zapr.NewLogger(zap.L()) + log.V(logs.Debug).Info("Enqueue example", "contextId", session.GetContextId()) + l.queue.Add(session.GetContextId()) enqueuedCounter.Inc() return nil } @@ -158,48 +170,16 @@ func (l *Learner) Reconcile(ctx context.Context, id string) error { return errors.Wrapf(err, "Unable to learn from session %s; failed to retrieve session", id) } - // Make sure there is a cell execution log event. - // Right now we rely on the cell execution log event to get the actual cell content. - // So we can't learn from sessions without cell execution events. TN013 has some ideas for how we could - // learn in the event of non cell execution events. - var execEvent *v1alpha1.LogEvent - for _, event := range session.LogEvents { - if event.GetType() != v1alpha1.LogEventType_EXECUTE { - continue - } - - // We don't want to learn from failed events. - if event.ExecuteStatus != v1alpha1.LogEvent_SUCCEEDED { - continue - } - - // We want to learn from the final successful event. - execEvent = event - } - - if execEvent == nil { - // Since the cell wasn't successfully executed we don't learn from it - sessProcessed.WithLabelValues("noexec").Inc() - return nil - } - - if session.GetFullContext() == nil { - sessProcessed.WithLabelValues("nocontext").Inc() - return errors.Errorf("Unable to learn from session %s; session has no context", session.GetContextId()) - } - - if session.GetFullContext().GetNotebook() == nil { - sessProcessed.WithLabelValues("nonotebook").Inc() - return errors.Errorf("Unable to learn from session %s; session has no notebook", session.GetContextId()) - } - - if session.GetFullContext().GetSelected() == 0 { - // If its the first cell we can't learn from it because what would we use as context to predict it? - sessProcessed.WithLabelValues("firstcell").Inc() + // N.B. isLearnable should be called in enQueue and the item should be filtered out if we can't learn from it + // But we call it here just in case. + if !isLearnable(session) { + // N.B. This shouldn't happen because the session shouldn't have been enequed if it isn't learnable + log.Info("Session is not learnable", "sessionId", id) return nil } sessProcessed.WithLabelValues("learn").Inc() + expectedFiles := l.getExampleFiles(session.GetContextId()) log.Info("Found new training example", "sessionId", session.GetContextId()) @@ -210,6 +190,7 @@ func (l *Learner) Reconcile(ctx context.Context, id string) error { return errors.Wrapf(err, "No training files found for example %s", session.GetContextId()) } + execEvent := getLastExecEvent(session) var executedCell *parserv1.Cell var execID string for _, c := range execEvent.Cells { @@ -389,3 +370,62 @@ func sessionToQuery(session *logspb.Session) (*v1alpha1.GenerateRequest, error) return req, nil } + +// isLearnable returns true if the session is eligible for learning and false otherwise +func isLearnable(session *logspb.Session) bool { + // Make sure there is a cell execution log event. + // Right now we rely on the cell execution log event to get the actual cell content. + // So we can't learn from sessions without cell execution events. TN013 has some ideas for how we could + // learn in the event of non cell execution events. + execEvent := getLastExecEvent(session) + + if execEvent == nil { + // Since the cell wasn't successfully executed we don't learn from it + sessProcessed.WithLabelValues("noexec").Inc() + return false + } + + log := zapr.NewLogger(zap.L()) + if session.GetFullContext() == nil { + sessProcessed.WithLabelValues("nocontext").Inc() + log.Error(errors.New("Session missing fullcontext"), "contextId", session.GetContextId()) + return false + } + + if session.GetFullContext().GetNotebook() == nil { + sessProcessed.WithLabelValues("nonotebook").Inc() + log.Error(errors.New("Session missing notebook"), "contextId", session.GetContextId()) + return false + } + + if session.GetFullContext().GetSelected() == 0 { + // If its the first cell we can't learn from it because what would we use as context to predict it? + sessProcessed.WithLabelValues("firstcell").Inc() + return false + } + return true +} + +// getLasExecEvent returns the last successful execution event in the session +// or null if there isn't one. +func getLastExecEvent(session *logspb.Session) *v1alpha1.LogEvent { + // Make sure there is a cell execution log event. + // Right now we rely on the cell execution log event to get the actual cell content. + // So we can't learn from sessions without cell execution events. TN013 has some ideas for how we could + // learn in the event of non cell execution events. + var execEvent *v1alpha1.LogEvent + for _, event := range session.LogEvents { + if event.GetType() != v1alpha1.LogEventType_EXECUTE { + continue + } + + // We don't want to learn from failed events. + if event.ExecuteStatus != v1alpha1.LogEvent_SUCCEEDED { + continue + } + + // We want to learn from the final successful event. + execEvent = event + } + return execEvent +} diff --git a/developer_guides/devcontainer-01JDDK3R7CDQP3ZBEM1TQRK466.md b/developer_guides/devcontainer-01JDDK3R7CDQP3ZBEM1TQRK466.md new file mode 100644 index 00000000..51b802a8 --- /dev/null +++ b/developer_guides/devcontainer-01JDDK3R7CDQP3ZBEM1TQRK466.md @@ -0,0 +1,51 @@ +--- +runme: + document: + relativePath: devcontainer.md + session: + id: 01JDDK3R7CDQP3ZBEM1TQRK466 + updated: 2024-11-23 15:10:17-08:00 +--- + +# Building a dev instance of a container + +* Use ko to build a docker image with some changes you want to test without doing a release + +```bash {"id":"01JDDG5YXKP5JT14J1A8X6S6JB"} +# TODO(jeremy): Should we pute these in a different repository +cd ../app +# TODO(jeremy): WHy do we have to set ko_config_path; why isn't it picked up automatically? +KO_DOCKER_REPO=ghcr.io/jlewi/foyle \ + ko build --base-import-paths ./ + +# Ran on 2024-11-23 15:09:59-08:00 for 13.913s exited with 0 +2024/11/23 15:10:01 Using base cgr.dev/chainguard/static:latest@sha256:5ff428f8a48241b93a4174dbbc135a4ffb2381a9e10bdbbc5b9db145645886d5 for github.com/jlewi/foyle/app +2024/11/23 15:10:02 Using build config foyle for github.com/jlewi/foyle/app +2024/11/23 15:10:04 git is in a dirty state +Please check in your pipeline what can be changing the following files: +AM app/.ko.yaml + M app/Dockerfile + M app/pkg/analyze/session_manager.go + M manifests/statefulset.yaml +?? developer_guides/devcontainer-01JDDK3R7CDQP3ZBEM1TQRK466.md +?? developer_guides/devcontainer.md + +2024/11/23 15:10:04 Building github.com/jlewi/foyle/app for linux/amd64 +2024/11/23 15:10:10 Publishing ghcr.io/jlewi/foyle/app:latest +2024/11/23 15:10:10 existing blob: sha256:31395f960e91fef49bf76d6ef18ab0b06f538025931d1cc57d6e729487da71c4 +2024/11/23 15:10:10 existing blob: sha256:250c06f7c38e52dc77e5c7586c3e40280dc7ff9bb9007c396e06d96736cf8542 +2024/11/23 15:10:11 pushed blob: sha256:b63296218d0e42ae111b040e4f88e45416291572612eee7b905300f52ba7b92a +2024/11/23 15:10:11 pushed blob: sha256:80953368c6fb45c3f2761dde20c75785aa31343a7b2c1307ce36e4a7bd82da93 +2024/11/23 15:10:11 pushed blob: sha256:e0be50e8b3b26f747fb8923cabac200955fcd31b2a710febd812226e3ce4b2ad +2024/11/23 15:10:12 ghcr.io/jlewi/foyle/app:sha256-9be786a0993fc946df13618a771ec90b5c7f9d082e37ba665dbf2a8541682ae0.sbom: digest: sha256:6e7f4b619c5d31f2495d701c9bd80c8fb06580efbd37be0a7b5f8324e7889bc5 size: 375 +2024/11/23 15:10:12 Published SBOM ghcr.io/jlewi/foyle/app:sha256-9be786a0993fc946df13618a771ec90b5c7f9d082e37ba665dbf2a8541682ae0.sbom +2024/11/23 15:10:13 pushed blob: sha256:1733c90cb8f756f25ef803fb450f8c26c2be9cb63e629cd2cde5538e54dddd14 +2024/11/23 15:10:13 ghcr.io/jlewi/foyle/app:latest: digest: sha256:9be786a0993fc946df13618a771ec90b5c7f9d082e37ba665dbf2a8541682ae0 size: 1337 +2024/11/23 15:10:13 Published ghcr.io/jlewi/foyle/app@sha256:9be786a0993fc946df13618a771ec90b5c7f9d082e37ba665dbf2a8541682ae0 +ghcr.io/jlewi/foyle/app@sha256:9be786a0993fc946df13618a771ec90b5c7f9d082e37ba665dbf2a8541682ae0 + +``` + +It looks like the output of your previous `ko build` command indicated that your Git repository is in a dirty state with untracked files. Before proceeding, it's a good idea to either commit or clean up these changes. Here are the next steps you can take: + +1. Check the status of your Git repository to see what files are causing the dirty state. \ No newline at end of file diff --git a/developer_guides/devcontainer.md b/developer_guides/devcontainer.md new file mode 100644 index 00000000..6ce5ebbe --- /dev/null +++ b/developer_guides/devcontainer.md @@ -0,0 +1,15 @@ +# Building a dev instance of a container + +* Use ko to build a docker image with some changes you want to test without doing a release + +```bash {"id":"01JDDG5YXKP5JT14J1A8X6S6JB"} +# TODO(jeremy): Should we pute these in a different repository +cd ../app +# TODO(jeremy): WHy do we have to set ko_config_path; why isn't it picked up automatically? +KO_DOCKER_REPO=ghcr.io/jlewi/foyle \ + ko build --base-import-paths ./ +``` + +It looks like the output of your previous `ko build` command indicated that your Git repository is in a dirty state with untracked files. Before proceeding, it's a good idea to either commit or clean up these changes. Here are the next steps you can take: + +1. Check the status of your Git repository to see what files are causing the dirty state. \ No newline at end of file diff --git a/manifests/statefulset.yaml b/manifests/statefulset.yaml index 2dffd84c..75a2edc9 100644 --- a/manifests/statefulset.yaml +++ b/manifests/statefulset.yaml @@ -38,6 +38,7 @@ spec: - name: foyle image: ghcr.io/jlewi/foyle:latest args: + - serve - --config=/config/foyle.yaml resources: requests: