| 34 | } |
| 35 | |
| 36 | func (a *MetadataAPI) BatchUpdateMetadata(ctx context.Context, req *agentproto.BatchUpdateMetadataRequest) (*agentproto.BatchUpdateMetadataResponse, error) { |
| 37 | const ( |
| 38 | // maxAllKeysLen is the maximum length of all metadata keys. This is |
| 39 | // 6144 to stay below the Postgres NOTIFY limit of 8000 bytes, with some |
| 40 | // headway for the timestamp and JSON encoding. Any values that would |
| 41 | // exceed this limit are discarded (the rest are still inserted) and an |
| 42 | // error is returned. |
| 43 | maxAllKeysLen = 6144 // 1024 * 6 |
| 44 | |
| 45 | maxValueLen = 2048 |
| 46 | maxErrorLen = maxValueLen |
| 47 | ) |
| 48 | |
| 49 | var ( |
| 50 | collectedAt = a.now() |
| 51 | allKeysLen = 0 |
| 52 | dbUpdate = database.UpdateWorkspaceAgentMetadataParams{ |
| 53 | WorkspaceAgentID: a.AgentID, |
| 54 | // These need to be `make(x, 0, len(req.Metadata))` instead of |
| 55 | // `make(x, len(req.Metadata))` because we may not insert all |
| 56 | // metadata if the keys are large. |
| 57 | Key: make([]string, 0, len(req.Metadata)), |
| 58 | Value: make([]string, 0, len(req.Metadata)), |
| 59 | Error: make([]string, 0, len(req.Metadata)), |
| 60 | CollectedAt: make([]time.Time, 0, len(req.Metadata)), |
| 61 | } |
| 62 | ) |
| 63 | for _, md := range req.Metadata { |
| 64 | md.Result.Value = strings.TrimSpace(md.Result.Value) |
| 65 | md.Result.Error = strings.TrimSpace(md.Result.Error) |
| 66 | metadataError := md.Result.Error |
| 67 | |
| 68 | allKeysLen += len(md.Key) |
| 69 | if allKeysLen > maxAllKeysLen { |
| 70 | // We still insert the rest of the metadata, and we return an error |
| 71 | // after the insert. |
| 72 | a.Log.Warn( |
| 73 | ctx, "discarded extra agent metadata due to excessive key length", |
| 74 | slog.F("collected_at", collectedAt), |
| 75 | slog.F("all_keys_len", allKeysLen), |
| 76 | slog.F("max_all_keys_len", maxAllKeysLen), |
| 77 | ) |
| 78 | break |
| 79 | } |
| 80 | |
| 81 | // We overwrite the error if the provided payload is too long. |
| 82 | if len(md.Result.Value) > maxValueLen { |
| 83 | metadataError = fmt.Sprintf("value of %d bytes exceeded %d bytes", len(md.Result.Value), maxValueLen) |
| 84 | md.Result.Value = md.Result.Value[:maxValueLen] |
| 85 | } |
| 86 | |
| 87 | if len(md.Result.Error) > maxErrorLen { |
| 88 | metadataError = fmt.Sprintf("error of %d bytes exceeded %d bytes", len(md.Result.Error), maxErrorLen) |
| 89 | md.Result.Error = "" |
| 90 | } |
| 91 | |
| 92 | // We don't want a misconfigured agent to fill the database. |
| 93 | dbUpdate.Key = append(dbUpdate.Key, md.Key) |