KHI Log Parser Support Guidelines
SkillMonitoring & opsGuidelines, package patterns, and task implementations for adding new log type support or modifying existing log parsers in KHI.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the KHI Log Parser Support Guidelines skill
What this skill tells your AI
The instructions your AI receives, as published by googlecloudplatform/khi in .agents/skills/khi_parser/SKILL.md and read by ahel’s review.
This guide outlines the patterns, package boundaries, implementation steps, and best practices for adding support for new log types or modifying existing log parsers in KHI.
1. Package Structure & Boundaries
When implementing a new log parser or modifying an existing one, you MUST separate the contract (IDs, public types, and configurations) from the implementation (the actual task logic). This guarantees that task IDs are fully initialized before implementation and prevents circular import dependencies.
The parser package must reside under pkg/task/inspection/ and adhere to the following structure:
pkg/task/inspection/<log_type_name>/
├── contract/
│ ├── taskid.go // Defines all TaskIDs and TaskReferences.
│ ├── extractor.go // (Optional) Defines field extraction functions and strongly-typed data structs.
│ ├── timeline_type.go // (Optional) Defines timeline types and verb types.
│ ├── timeline_path.go // (Optional) Helper functions to build hierarchical paths.
│ └── log_type.go // (Optional) Defines log-specific types or constants.
└── impl/
├── form_task.go // (Optional) Implements form-related parameter tasks.
├── query_task.go // (Optional) Implements log query/filter tasks.
├── ingester_task.go // (Optional) Implements the LogIngester task.
├── <name>_mapper.go // (Optional) Implements LogToTimelineMapper tasks (can be multiple).
└── registration.go // Implements task registration to the KHI registry.
Key Package Boundaries
[!IMPORTANT]
- Contract Package (
contract/): MUST NOT import theimplpackage. External packages can freely import thecontractpackage to depend on parser task IDs, Extractor functions, or TimelineType constants.- Implementation Package (
impl/): Implements the actual tasks. It imports thecontractpackage. External packages MUST NOT import theimplpackage.- Registration: Tasks inside the
implpackage are registered throughimpl/registration.go. There is no root-levelregistration.gofile in this directory.
2. The Log Parsing Steps
A complete log parser in KHI generally consists of distinct DAG tasks:
flowchart TD
FormTask[1. Form Task] -->|Provides Parameters| QueryTask[2. Log Query Task]
QueryTask -->|Provides Raw Logs| IngesterTask[3. Log Ingest Task]
QueryTask -->|Provides Raw Logs| GrouperTask[Log Grouper Task]
IngesterTask -->|Provides Ingested Logs| MapperTask[4. Timeline Mapper Task]
GrouperTask -->|Provides Grouped Map| MapperTask
Step 1: Form Tasks (Form-related)
Exposes interactive input fields (e.g., text boxes, multi-select checkboxes) to let users configure parameters before running the inspection.
- Utility:
formtask.NewTextFormTaskBuilderorformtask.NewSetFormTaskBuilder.
Step 2: Log Query Tasks
Queries logs from the data source (e.g., Google Cloud Logging or local files) using parameters provided by the Form tasks.
- Utility:
googlecloudcommon_contract.NewListLogEntriesTask(for any logs on Cloud Logging) orinspection_task.NewInspectionTask. - Google Cloud API Calling: When calling Google Cloud APIs directly or through fetchers, refer to googlecloud-api for mandatory
CallOptionInjectorusage and client configuration.
Step 3: Log Ingestion Tasks
Extracts information directly from the log's NodeReader using Extractor functions, populating basic log metadata on LogChangeSet (such as Timestamp from l.Timestamp, Severity, LogType, and Summary).
- Utility:
inspectiontaskbase.NewLogIngesterTask.
Step 4: Log Grouping & Timeline Mapping Tasks
- Log Grouper Task: Groups logs by a key (e.g., entity name, correlation ID) by calling Extractor functions on raw logs.
- Utility:
inspectiontaskbase.NewLogGrouperTask.
- Utility:
- Timeline Mapping Task: Maps the grouped logs to resource timelines as events or state revisions.
- Utility:
inspectiontaskbase.NewLogToTimelineMapperTask.
- Utility:
3. Step-by-Step Implementation Code Samples
Let's look at a concrete example of supporting a custom log type called customapp.
A. The Contract Package (pkg/task/inspection/customapp/contract/)
taskid.go
Defines the TaskIDs and TaskReferences for the pipeline steps.
package customapp_contract
import (
inspectiontaskbase "github.com/GoogleCloudPlatform/khi/pkg/core/inspection/taskbase"
"github.com/GoogleCloudPlatform/khi/pkg/core/task/taskid"
"github.com/GoogleCloudPlatform/khi/pkg/model/log"
)
const TaskIDPrefix = "customapp.khi.google.com/"
// 1. Form Task ID
var InputFilterKeywordTaskID = taskid.NewDefaultImplementationID[string](TaskIDPrefix + "input-keyword")
// 2. Log Query Task ID
var LogQueryTaskID = taskid.NewDefaultImplementationID[[]*log.Log](TaskIDPrefix + "query")
// 3. Log Ingestion Task ID
var LogIngesterTaskID = taskid.NewDefaultImplementationID[[]*log.Log](TaskIDPrefix + "log-ingester")
// 4. Log Grouper & Timeline Mapper Task IDs
var LogGrouperTaskID = taskid.NewDefaultImplementationID[inspectiontaskbase.LogGroupMap](TaskIDPrefix + "log-grouper")
var LogToTimelineMapperTaskID = taskid.NewDefaultImplementationID[struct{}](TaskIDPrefix + "timeline-mapper")
extractor.go
Defines the strongly-typed data structures and extraction functions.
[!IMPORTANT]
- Package-level FieldPath declarations: Pre-compiled
structured.FieldPathvalues created bystructured.CompileFieldPathare constant across log entries and MUST be declared as package-level variables in avar (...)block immediately below theimportblock. Never compileFieldPathinside functions or hot parsing loops.- Non-pointer Return Values: Extraction methods (
ExtractXXX) MUST return value types (FieldSet), not pointers (*FieldSet). Returning values eliminates heap allocation overhead when extractors are called millions of times across high-volume log streams.- Mock Support: Extraction functions MUST check
structured.GetMock[FieldSetType](reader)at the top of the function to allow unit tests to override extraction viatestlog.NewMockLog/structured.NewMockNode.
Pattern 1: Direct Extractor Pattern (Single Log Source)
Used when the log format is fixed to a single ingest format (e.g., GKE Autoscaler, serial port, K8s control plane).
package customapp_contract
import (
"github.com/GoogleCloudPlatform/khi/pkg/common/structured"
)
var (
pathAppName = structured.CompileFieldPath("app_name")
pathRequestID = structured.CompileFieldPath("request_id")
pathPayload = structured.CompileFieldPath("payload")
)
// CustomAppFieldSet holds structured log data extracted from the log entry.
type CustomAppFieldSet struct {
AppName string
RequestID string
Payload string
}
// ExtractCustomApp extracts CustomAppFieldSet from a raw log node reader.
func ExtractCustomApp(reader *structured.NodeReader) (CustomAppFieldSet, error) {
if mock, ok := structured.GetMock[CustomAppFieldSet](reader); ok {
return mock, nil
}
return CustomAppFieldSet{
AppName: reader.ReadStringOrDefault(pathAppName, "unknown-app"),
RequestID: reader.ReadStringOrDefault(pathRequestID, ""),
Payload: reader.ReadStringOrDefault(pathPayload, ""),
}, nil
}
Pattern 2: Injected Extractor Pattern (Multi-source Ingestion)
Used when the same log entity can originate from different sources with distinct field layouts (for example, K8s audit logs ingested from GCP Cloud Logging vs. OSS Kubernetes JSONL files).
The common contract defines an extractor function type and a wrapper function that retrieves the task-injected extractor from context:
package commonlogk8saudit_contract
import (
"context"
"github.com/GoogleCloudPlatform/khi/pkg/common/structured"
coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task"
)
// K8sAuditLogExtractor is a function type for extracting K8sAuditLogFieldSet from a NodeReader.
type K8sAuditLogExtractor func(reader *structured.NodeReader) (K8sAuditLogFieldSet, error)
// ExtractK8sAuditLog extracts K8s audit log data using the injected extractor from the task context.
func ExtractK8sAuditLog(ctx context.Context, reader *structured.NodeReader) (K8sAuditLogFieldSet, error) {
if mock, ok := structured.GetMock[K8sAuditLogFieldSet](reader); ok {
return mock, nil
}
if extractor, found := coretask.GetTaskResultOptional(ctx, K8sAuditLogExtractorRef); found && extractor != nil {
return extractor(reader)
}
return K8sAuditLogFieldSet{}, nil
}
timeline_type.go
Defines custom timeline types and resource verbs.
package customapp_contract
import (
"github.com/GoogleCloudPlatform/khi/pkg/model/khifile/v6/style"
)
var (
// TimelineTypeCustomApp is the timeline type style for Custom App resources.
TimelineTypeCustomApp = style.MustRegisterTimelineType(
"customapp",
"Custom Application",
"dns",
0.6,
style.ColorWhite,
style.ColorBlack,
style.MustForceConvertSRGBHex("#4285F4"),
true,
1000,
style.AlphabeticalSortPolicy(),
)
// VerbCustomAppProcess is the verb style for Custom App state updates.
VerbCustomAppProcess = style.MustRegisterVerb("Process", style.MustForceConvertSRGBHex("#0F9D58"), style.ColorWhite, true)
)
log_type.go
Defines custom log types.
package customapp_contract
import (
"github.com/GoogleCloudPlatform/khi/pkg/model/khifile/v6/style"
)
var (
// LogTypeCustomApp is the log type style for Custom App logs.
LogTypeCustomApp = style.MustRegisterLogType(
"customapp",
"Custom Application Logs",
style.MustForceConvertSRGBHex("#4285F4"),
style.ColorWhite,
)
)
timeline_path.go (Optional)
Defines helper functions to build hierarchical timeline paths.
For custom application timelines, you can define helpers to construct paths consistently. If your custom application runs as part of a Kubernetes Pod, you can build a sub-timeline path nested directly under the standard Kubernetes Pod timeline by referencing standard K8s timeline types from inspectioncore_contract.
- MustXXXTimeline func must receive the context as its first argument.
- If the MustXXXTimeline func isn't for a root timeline, it must receive the parent timeline path as its second argument.
package customapp_contract
import (
"context"
"github.com/GoogleCloudPlatform/khi/pkg/common/khictx"
khifilev6 "github.com/GoogleCloudPlatform/khi/pkg/model/khifile/v6"
inspectioncore_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/inspectioncore/contract"
)
// MustCustomAppTimeline returns the hierarchical timeline path for a standalone Custom App.
// Constructs a path like: customapp/<appName>
func MustCustomAppTimeline(ctx context.Context, appName string) *khifilev6.TimelinePath {
builder := khictx.MustGetValue(ctx, inspectioncore_contract.Builder)
return builder.TimelineAccumulator.GetPath(nil, khifilev6.PathSegment{
Name: appName,
Type: TimelineTypeCustomApp,
})
}
// MustCustomAppPodTimeline returns the hierarchical timeline path for Custom App logs nested under a Pod.
// Constructs a path like: <apiVersion>/<kind>/<namespace>/<podName>/customapp
func MustCustomAppPodTimeline(ctx context.Context, podTimelinePath *khifilev6.TimelinePath) *khifilev6.TimelinePath {
if podTimelinePath == nil || podTimelinePath.Type.GetId() != inspectioncore_contract.TimelineTypeResource.GetId() {
panic("parent timeline path must be Resource type")
}
builder := khictx.MustGetValue(ctx, inspectioncore_contract.Builder)
return builder.TimelineAccumulator.GetPath(podTimelinePath, khifilev6.PathSegment{
Name: "customapp",
Type: TimelineTypeCustomApp,
})
}
B. The Implementation Package (pkg/task/inspection/customapp/impl/)
form_task.go (Step 1)
Implements form tasks to get user-defined input.
package customapp_impl
import (
"context"
"github.com/GoogleCloudPlatform/khi/pkg/core/inspection/formtask"
customapp_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/customapp/contract"
googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract"
)
const formPriority = googlecloudcommon_contract.FormBasePriority + 5000
// InputFilterKeywordTask defines a text input form task for filtering logs.
var InputFilterKeywordTask = formtask.NewTextFormTaskBuilder(
customapp_contract.InputFilterKeywordTaskID,
formPriority,
"Filter Keyword",
).
WithDescription("Keyword to filter Custom App logs.").
WithDefaultValueFunc(func(ctx context.Context, previousValues []string) (string, error) {
if len(previousValues) > 0 {
return previousValues[0], nil
}
return "default-keyword", nil
}).
Build()
query_task.go (Step 2)
Implements querying logs from Google Cloud Logging based on parameters.
package customapp_impl
import (
"context"
"fmt"
coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task"
"github.com/GoogleCloudPlatform/khi/pkg/core/task/taskid"
"github.com/GoogleCloudPlatform/khi/pkg/model/log"
googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract"
googlecloudk8scommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudk8scommon/contract"
customapp_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/customapp/contract"
inspectioncore_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/inspectioncore/contract"
)
// LogQueryTask executes Cloud Logging filter to fetch logs.
var LogQueryTask = googlecloudcommon_contract.NewListLogEntriesTask(&customAppLogQueryTaskSetting{})
type customAppLogQueryTaskSetting struct{}
func (s *customAppLogQueryTaskSetting) TaskID() taskid.TaskImplementationID[[]*log.Log] {
return customapp_contract.LogQueryTaskID
}
func (s *customAppLogQueryTaskSetting) Dependencies() []taskid.UntypedTaskReference {
return []taskid.UntypedTaskReference{
googlecloudk8scommon_contract.ClusterIdentityTaskID.Ref(),
customapp_contract.InputFilterKeywordTaskID.Ref(),
}
}
func (s *customAppLogQueryTaskSetting) Description() *googlecloudcommon_contract.ListLogEntriesTaskDescription {
return &googlecloudcommon_contract.ListLogEntriesTaskDescription{
QueryName: "Custom App logs",
ExampleQuery: `resource.type="gke_cluster" AND log_id("custom-app")`,
}
}
func (s *customAppLogQueryTaskSetting) LogFilters(ctx context.Context, taskMode inspectioncore_contract.InspectionTaskModeType) ([]string, error) {
keyword := coretask.GetTaskResult(ctx, customapp_contract.InputFilterKeywordTaskID.Ref())
query := fmt.Sprintf(`resource.type="gke_cluster" AND log_id("custom-app") AND textPayload:"%s"`, keyword)
return []string{query}, nil
}
func (s *customAppLogQueryTaskSetting) DefaultResourceNames(ctx context.Context) ([]string, error) {
clusterIdentity := coretask.GetTaskResult(ctx, googlecloudk8scommon_contract.ClusterIdentityTaskID.Ref())
return []string{fmt.Sprintf("projects/%s", clusterIdentity.ProjectID)}, nil
}
func (s *customAppLogQueryTaskSetting) TimePartitionCount(ctx context.Context) (int, error) {
return 5, nil
}
var _ googlecloudcommon_contract.ListLogEntriesTaskSetting = (*customAppLogQueryTaskSetting)(nil)
parser_tasks.go (Steps 3, 4)
Defines log ingestion, log grouping, and timeline mapping.
package customapp_impl
import (
"context"
"fmt"
"github.com/GoogleCloudPlatform/khi/pkg/common/khictx"
inspectiontaskbase "github.com/GoogleCloudPlatform/khi/pkg/core/inspection/taskbase"
"github.com/GoogleCloudPlatform/khi/pkg/core/task/taskid"
khifilev6 "github.com/GoogleCloudPlatform/khi/pkg/model/khifile/v6"
"github.com/GoogleCloudPlatform/khi/pkg/model/log"
customapp_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/customapp/contract"
googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract"
inspectioncore_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/inspectioncore/contract"
)
// CustomAppLogIngester V2 LogIngester (Step 3).
type CustomAppLogIngester struct{}
func (i *CustomAppLogIngester) RawLogTask() taskid.TaskReference[[]*log.Log] {
return customapp_contract.LogQueryTaskID.Ref()
}
func (i *CustomAppLogIngester) Dependencies() []taskid.UntypedTaskReference {
return []taskid.UntypedTaskReference{}
}
func (i *CustomAppLogIngester) ProcessLog(ctx context.Context, l *log.Log) (*khifilev6.LogChangeSet, error) {
cs, err := khifilev6.NewLogChangeSet(l)
if err != nil {
return nil, err
}
cs.SetLogType(customapp_contract.LogTypeCustomApp)
// Usually l.Timestamp from ingestion is used. However, if the log contains its own
// custom payload field with a more precise timestamp, extract and set that instead.
cs.SetTimestamp(l.Timestamp)
// Extract custom fields to generate summary.
if customFS, err := customapp_contract.ExtractCustomApp(l.NodeReader); err == nil {
cs.SetSummary(fmt.Sprintf("[%s] %s", customFS.AppName, customFS.Payload))
}
return cs, nil
}
var LogIngesterTask = inspectiontaskbase.NewLogIngesterTask(
customapp_contract.LogIngesterTaskID,
&CustomAppLogIngester{},
)
// LogGrouperTask groups logs by AppName (helper for Step 4).
var LogGrouperTask = inspectiontaskbase.NewLogGrouperTask(
customapp_contract.LogGrouperTaskID,
customapp_contract.LogQueryTaskID.Ref(),
func(ctx context.Context, l *log.Log) string {
if customFS, err := customapp_contract.ExtractCustomApp(l.NodeReader); err == nil {
return customFS.AppName
}
return "unknown-app"
},
)
// CustomAppTimelineMapper maps logs to timeline (Step 4).
type CustomAppTimelineMapper struct {
inspectiontaskbase.StatelessMapperBase // Embed stateless helper.
}
func (m *CustomAppTimelineMapper) LogIngesterTask() taskid.TaskReference[[]*log.Log] {
return customapp_contract.LogIngesterTaskID.Ref()
}
func (m *CustomAppTimelineMapper) Dependencies() []taskid.UntypedTaskReference {
return []taskid.UntypedTaskReference{}
}
func (m *CustomAppTimelineMapper) GroupedLogTask() taskid.TaskReference[inspectiontaskbase.LogGroupMap] {
return customapp_contract.LogGrouperTaskID.Ref()
}
func (m *CustomAppTimelineMapper) ProcessLogByGroup(ctx context.Context, l *log.Log, _ struct{}) (*khifilev6.TimelineChangeSet, struct{}, error) {
customFS, err := customapp_contract.ExtractCustomApp(l.NodeReader)
if err != nil {
return nil, struct{}{}, err
}
builder := khictx.MustGetValue(ctx, inspectioncore_contract.CurrentV6Builder)
targetPath := builder.TimelineAccumulator.GetPath(nil, khifilev6.PathSegment{
Name: customFS.AppName,
Type: customapp_contract.TimelineTypeCustomApp,
})
cs := khifilev6.NewTimelineChangeSet(l)
// Record a revision on timeline for state change.
cs.AddRevision(targetPath, &khifilev6.StagingRevision{
ChangedTime: l.Timestamp,
ResourceBody: customFS.Payload,
VerbType: customapp_contract.VerbCustomAppProcess,
})
return cs, struct{}{}, nil
}
var LogToTimelineMapperTask = inspectiontaskbase.NewLogToTimelineMapperTask(
customapp_contract.LogToTimelineMapperTaskID,
&CustomAppTimelineMapper{},
inspectioncore_contract.FeatureTaskLabel(
"Custom App Logs",
"Parser and timeline mapping for Custom App logs.",
9000,
false,
),
)
var _ inspectiontaskbase.LogToTimelineMapper[struct{}] = (*CustomAppTimelineMapper)(nil)
C. Specialized Pattern: ManifestLogToTimelineMapper (Multi-Group Merge Mapper)
For advanced scenarios requiring the tracking and synchronization of multiple related resource logs chronologically (such as a parent Pod and its subresources like Status or Binding), KHI provides NewManifestLogToTimelineMapper.
This mapper automatically merges logs from multiple roles into a single stream sorted strictly by timestamp, and passes the state T across all events.
Key Interfaces and Structures
RelatedGroupSet: Groups related logs by role name (e.g.,"source" -> PodGroup,"target" -> BindingGroup).MultiGroupLogEvent: Contains the currently yieldingLog, the role (GroupRole), and the helper methods:GetLastBodyReader(role string) (*structured.NodeReader, bool): Retrieves the latest manifest body of the specified role as aNodeReaderat the time of the event using highly optimizedO(log N)binary search.GetLastBodyYAML(role string) (string, bool): Retrieves the latest manifest body as a YAML string.
Code Sample: Single-Pass Stateful Manifest Mapper
package myapp_impl
import (
"context"
"time"
"github.com/GoogleCloudPlatform/khi/pkg/core/task/taskid"
pb "github.com/GoogleCloudPlatform/khi/pkg/generated/khifile/v6"
khifilev6 "github.com/GoogleCloudPlatform/khi/pkg/model/khifile/v6"
"github.com/GoogleCloudPlatform/khi/pkg/model/log"
commonlogk8saudit_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/commonlogk8saudit/contract"
)
type MyState struct {
WasDeleted bool
}
type MyManifestMapper struct {
// Embeds single pass helper.
commonlogk8saudit_contract.ManifestSinglePassMapperBase[*MyState]
}
func (m *MyManifestMapper) TaskID() taskid.TaskImplementationID[struct{}] {
return mycontract.MyManifestMapperTaskID
}
func (m *MyManifestMapper) LogIngesterTask() taskid.TaskReference[[]*log.Log] {
return commonlogk8saudit_contract.K8sAuditLogIngesterTaskID.Ref()
}
func (m *MyManifestMapper) GroupedLogTask() taskid.TaskReference[commonlogk8saudit_contract.ResourceManifestLogGroupMap] {
return commonlogk8saudit_contract.ResourceLifetimeTrackerTaskID.Ref()
}
func (m *MyManifestMapper) Dependencies() []taskid.UntypedTaskReference {
return []taskid.UntypedTaskReference{}
}
// ResolveRelatedGroupSets groups a parent resource (source) and its subresource (target) together.
func (m *MyManifestMapper) ResolveRelatedGroupSets(ctx context.Context, groupedLogs commonlogk8saudit_contract.ResourceManifestLogGroupMap) ([]commonlogk8saudit_contract.RelatedGroupSet, error) {
result := []commonlogk8saudit_contract.RelatedGroupSet{}
for _, group := range groupedLogs {
if group.Resource.Type() == commonlogk8saudit_contract.Subresource {
parentGroup := groupedLogs[group.Resource.ParentIdentity().ResourcePathString()]
result = append(result, commonlogk8saudit_contract.RelatedGroupSet{
Roles: map[string]*commonlogk8saudit_contract.ResourceManifestLogGroup{
"source": parentGroup,
"target": group,
},
})
}
}
return result, nil
}
// ProcessLog processes chronologically merged events.
func (m *MyManifestMapper) ProcessLog(ctx context.Context, event commonlogk8saudit_contract.MultiGroupLogEvent, state *MyState) (*khifilev6.TimelineChangeSet, *MyState, error) {
if state == nil {
state = &MyState{}
}
cs := khifilev6.NewTimelineChangeSet(event.Log)
// Handle parent deletion event to propagate deletion to the subresource.
if event.GroupRole == "source" && event.EventType == commonlogk8saudit_contract.ChangeEventTypeDeletion {
targetGroup := event.GroupSet.Roles["target"]
targetPath := MustResolveTimelinePath(ctx, targetGroup.Resource)
cs.AddRevision(targetPath, &khifilev6.StagingRevision{
ChangedTime: time.Now(),
StateType: commonlogk8saudit_contract.RevisionStateK8sResourceIsDeleted,
})
state.WasDeleted = true
}
return cs, state, nil
}
var _ commonlogk8saudit_contract.ManifestLogToTimelineMapper[*MyState] = (*MyManifestMapper)(nil)
registration.go
Registers the tasks with the central registry.
package customapp_impl
import (
coreinspection "github.com/GoogleCloudPlatform/khi/pkg/core/inspection"
coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task"
)
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 2k
- Forks
- 97
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
khi-parser- Source
- github.com/googlecloudplatform/khi