Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,6 @@ linters:
- third_party$
- builtin$
- examples$
rules:
- linters: [gocheckcompilerdirectives]
text: 'compiler directive unrecognized: //go:fix'
55 changes: 55 additions & 0 deletions dev-tools/fixschema/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.

//go:build ignore

// fixschema converts optional time.Time fields in model.Agent to *time.Time.
// Go's encoding/json omitempty is ineffective for struct types: a zero time.Time
// serializes as "0001-01-01T00:00:00Z" instead of being omitted, causing issues
// such as Kibana treating enrolled agents as unenrolled (non-null unenrolled_at).
// The schema-generate tool does not support pointer types, so this post-processing
// step is applied after generation.
package main

import (
"os"
"strings"
)

// agentOptionalTimeFields are the time.Time fields in model.Agent that must be
// *time.Time so that omitempty correctly omits them when unset.
var agentOptionalTimeFields = []string{
"AuditUnenrolledTime",
"LastCheckin",
"LastUpdated",
"UnenrolledAt",
"UnenrollmentStartedAt",
"UpdatedAt",
"UpgradeStartedAt",
"UpgradedAt",
}

const schemaFile = "internal/pkg/model/schema.go"

func main() {
filename := schemaFile
if len(os.Args) >= 2 && os.Args[1] != "" {
filename = os.Args[1]
}
data, err := os.ReadFile(filename)
if err != nil {
panic(err)
}
content := string(data)
for _, field := range agentOptionalTimeFields {
// Replace "FieldName time.Time " with "FieldName *time.Time "
// Only match value-type declarations (not already-pointer ones).
old := "\t" + field + " time.Time "
new := "\t" + field + " *time.Time "
content = strings.ReplaceAll(content, old, new)
}
if err := os.WriteFile(filename, []byte(content), 0o644); err != nil {
panic(err)
}
}
2 changes: 1 addition & 1 deletion dev-tools/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 // indirect
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/elastic/go-json-schema-generate v0.0.0-20220323152209-ec19b88f6b5e // indirect
github.com/elastic/go-json-schema-generate v0.0.0-20220519132038-c708d18d6ca2 // indirect
github.com/elastic/go-licenser v0.4.2 // indirect
github.com/getkin/kin-openapi v0.132.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions dev-tools/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w=
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q=
github.com/elastic/go-json-schema-generate v0.0.0-20220323152209-ec19b88f6b5e h1:tD24NCs6y1KDmiAQCkCXdgtOhA+toU+wWY1wEWRDrz8=
github.com/elastic/go-json-schema-generate v0.0.0-20220323152209-ec19b88f6b5e/go.mod h1:w6t176CDaF2cZXwuQtFA5T+trYjvo5OYxLbBwAE7gxU=
github.com/elastic/go-json-schema-generate v0.0.0-20220519132038-c708d18d6ca2 h1:C8FTj5Y0BfGNSFyQI6V0HeATwKwTlLfU8qletYG7V8Y=
github.com/elastic/go-json-schema-generate v0.0.0-20220519132038-c708d18d6ca2/go.mod h1:w6t176CDaF2cZXwuQtFA5T+trYjvo5OYxLbBwAE7gxU=
github.com/elastic/go-licenser v0.4.2 h1:bPbGm8bUd8rxzSswFOqvQh1dAkKGkgAmrPxbUi+Y9+A=
github.com/elastic/go-licenser v0.4.2/go.mod h1:W8eH6FaZDR8fQGm+7FnVa7MxI1b/6dAqxz+zPB8nm5c=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
Expand Down
15 changes: 4 additions & 11 deletions internal/pkg/action/dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,19 +153,12 @@ func (d *Dispatcher) process(ctx context.Context, hits []es.HitT) {

// offsetStartTime will return a new start time between start:start+dur based on index i and the total number of agents
// As we expect i < total the latest return time will always be < start+dur
func offsetStartTime(ctx context.Context, start string, dur int64, i, total int) string {

if start == "" {
return ""
}
startTS, err := time.Parse(time.RFC3339, start)
if err != nil {
zerolog.Ctx(ctx).Error().Err(err).Msg("unable to parse start_time string")
return ""
func offsetStartTime(_ context.Context, start time.Time, dur int64, i, total int) time.Time {
if start.IsZero() {
return time.Time{}
}
d := time.Second * time.Duration(dur)
startTS = startTS.Add((d * time.Duration(i)) / time.Duration(total)) // adjust start to a position within the range
return startTS.Format(time.RFC3339)
return start.Add((d * time.Duration(i)) / time.Duration(total)) // adjust start to a position within the range
}

// getSub returns the subscription (if any) for the specified agentID.
Expand Down
62 changes: 31 additions & 31 deletions internal/pkg/action/dispatcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ import (
"golang.org/x/time/rate"
)

func mustParseTime(s string) time.Time {
t, err := time.Parse(time.RFC3339, s)
if err != nil {
panic(err)
}
return t
}

type mockMonitor struct {
mock.Mock
}
Expand Down Expand Up @@ -185,9 +193,9 @@ func Test_Dispatcher_Run(t *testing.T) {
ActionID: "test-action",
Agents: nil,
Data: json.RawMessage(`{"key":"value"}`),
Expiration: "2022-01-02T13:00:00Z",
Expiration: mustParseTime("2022-01-02T13:00:00Z"),
RolloutDurationSeconds: 600,
StartTime: "2022-01-02T12:00:00Z",
StartTime: mustParseTime("2022-01-02T12:00:00Z"),
Type: "upgrade",
}},
},
Expand All @@ -210,27 +218,27 @@ func Test_Dispatcher_Run(t *testing.T) {
ActionID: "test-action",
Agents: nil,
Data: json.RawMessage(`{"key":"value"}`),
Expiration: "2022-01-02T13:00:00Z",
Expiration: mustParseTime("2022-01-02T13:00:00Z"),
RolloutDurationSeconds: 600,
StartTime: "2022-01-02T12:00:00Z",
StartTime: mustParseTime("2022-01-02T12:00:00Z"),
Type: "upgrade",
}},
"agent2": []model.Action{model.Action{
ActionID: "test-action",
Agents: nil,
Data: json.RawMessage(`{"key":"value"}`),
Expiration: "2022-01-02T13:00:00Z",
Expiration: mustParseTime("2022-01-02T13:00:00Z"),
RolloutDurationSeconds: 600,
StartTime: "2022-01-02T12:03:20Z",
StartTime: mustParseTime("2022-01-02T12:03:20Z"),
Type: "upgrade",
}},
"agent3": []model.Action{model.Action{
ActionID: "test-action",
Agents: nil,
Data: json.RawMessage(`{"key":"value"}`),
Expiration: "2022-01-02T13:00:00Z",
Expiration: mustParseTime("2022-01-02T13:00:00Z"),
RolloutDurationSeconds: 600,
StartTime: "2022-01-02T12:06:40Z",
StartTime: mustParseTime("2022-01-02T12:06:40Z"),
Type: "upgrade",
}},
},
Expand Down Expand Up @@ -312,60 +320,52 @@ func Test_Dispatcher_Run(t *testing.T) {
func Test_offsetStartTime(t *testing.T) {
tests := []struct {
name string
start string
end string
start time.Time
dur int64
i int
total int
result string
result time.Time
}{{
name: "no start",
result: "",
name: "no start",
}, {
name: "first agent",
start: "2022-01-02T12:00:00Z",
end: "2022-01-02T13:00:00Z",
start: mustParseTime("2022-01-02T12:00:00Z"),
i: 0,
total: 10,
result: "2022-01-02T12:00:00Z",
result: mustParseTime("2022-01-02T12:00:00Z"),
}, {
name: "mid agent no dur",
start: "2022-01-02T12:00:00Z",
end: "2022-01-02T13:00:00Z",
start: mustParseTime("2022-01-02T12:00:00Z"),
i: 4,
total: 10,
result: "2022-01-02T12:00:00Z",
result: mustParseTime("2022-01-02T12:00:00Z"),
}, {
name: "last agent no dur",
start: "2022-01-02T12:00:00Z",
end: "2022-01-02T13:00:00Z",
start: mustParseTime("2022-01-02T12:00:00Z"),
i: 9,
total: 10,
result: "2022-01-02T12:00:00Z",
result: mustParseTime("2022-01-02T12:00:00Z"),
}, {
name: "first agent 10m dur",
start: "2022-01-02T12:00:00Z",
end: "2022-01-02T13:00:00Z",
start: mustParseTime("2022-01-02T12:00:00Z"),
dur: 600,
i: 0,
total: 10,
result: "2022-01-02T12:00:00Z",
result: mustParseTime("2022-01-02T12:00:00Z"),
}, {
name: "mid agent 10m dur",
start: "2022-01-02T12:00:00Z",
end: "2022-01-02T13:00:00Z",
start: mustParseTime("2022-01-02T12:00:00Z"),
dur: 600,
i: 4,
total: 10,
result: "2022-01-02T12:04:00Z",
result: mustParseTime("2022-01-02T12:04:00Z"),
}, {
name: "last agent 10m dur",
start: "2022-01-02T12:00:00Z",
end: "2022-01-02T13:00:00Z",
start: mustParseTime("2022-01-02T12:00:00Z"),
dur: 600,
i: 9,
total: 10,
result: "2022-01-02T12:09:00Z",
result: mustParseTime("2022-01-02T12:09:00Z"),
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion internal/pkg/api/apiVersion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package api

import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -59,7 +60,7 @@ func TestAPIVersion_middleware(t *testing.T) {

resp := httptest.NewRecorder()

req := httptest.NewRequest("GET", "/api/test", nil)
req := httptest.NewRequestWithContext(context.Background(), "GET", "/api/test", nil)

nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})

Expand Down
9 changes: 3 additions & 6 deletions internal/pkg/api/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -606,8 +606,7 @@ func NewHTTPErrResp(err error) HTTPErrResp {
}
}

var drErr *BadRequestErr
if errors.As(err, &drErr) {
if _, ok := errors.AsType[*BadRequestErr](err); ok {
return HTTPErrResp{
http.StatusBadRequest,
"BadRequest",
Expand All @@ -617,8 +616,7 @@ func NewHTTPErrResp(err error) HTTPErrResp {
}

// If it's a JSON marshal error
var jErr *json.MarshalerError
if errors.As(err, &jErr) {
if _, ok := errors.AsType[*json.MarshalerError](err); ok {
return HTTPErrResp{
http.StatusInternalServerError,
err.Error(),
Expand All @@ -627,8 +625,7 @@ func NewHTTPErrResp(err error) HTTPErrResp {
}
}

var esErr *es.ErrElastic
if errors.As(err, &esErr) {
if esErr, ok := errors.AsType[*es.ErrElastic](err); ok {
return HTTPErrResp{
http.StatusServiceUnavailable,
esErr.Error(),
Expand Down
19 changes: 8 additions & 11 deletions internal/pkg/api/handleAck.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ func (a *AckResponse) SetResult(pos int, status int) {
}

func (a *AckResponse) SetError(pos int, err error) {
var esErr *es.ErrElastic
if errors.As(err, &esErr) {
if esErr, ok := errors.AsType[*es.ErrElastic](err); ok {
a.setMessage(pos, esErr.Status, esErr.Reason)
} else if errors.Is(err, bulk.ErrTooManyBulkDispatches) {
a.SetResult(pos, http.StatusTooManyRequests)
Expand Down Expand Up @@ -119,8 +118,7 @@ func (ack *AckT) processRequest(zlog zerolog.Logger, w http.ResponseWriter, r *h
span, _ := apm.StartSpan(r.Context(), "response", "write")
defer span.End()
if err != nil {
var herr *HTTPError
if errors.As(err, &herr) {
if herr, ok := errors.AsType[*HTTPError](err); ok {
w.WriteHeader(herr.Status)
} else {
// Non-HTTP error will be handled at higher level
Expand Down Expand Up @@ -178,7 +176,7 @@ func eventToActionResult(agentID, aType string, namespaces []string, ev AckReque
Namespaces: namespaces,
Data: p,
Error: fromPtr(event.Error),
Timestamp: event.Timestamp.Format(time.RFC3339Nano),
Timestamp: event.Timestamp,
}
case string(INPUTACTION):
event, _ := ev.AsInputEvent()
Expand All @@ -187,12 +185,12 @@ func eventToActionResult(agentID, aType string, namespaces []string, ev AckReque
AgentID: agentID,
Namespaces: namespaces,
ActionInputType: event.ActionInputType,
StartedAt: event.StartedAt.Format(time.RFC3339Nano),
CompletedAt: event.CompletedAt.Format(time.RFC3339Nano),
StartedAt: event.StartedAt,
CompletedAt: event.CompletedAt,
ActionData: event.ActionData,
ActionResponse: event.ActionResponse,
Error: fromPtr(event.Error),
Timestamp: event.Timestamp.Format(time.RFC3339Nano),
Timestamp: event.Timestamp,
}
default: // UPGRADE action acks are also handled by handelUpgrade (deprecated func)
event, _ := ev.AsGenericEvent()
Expand All @@ -201,7 +199,7 @@ func eventToActionResult(agentID, aType string, namespaces []string, ev AckReque
Namespaces: namespaces,
AgentID: agentID,
Error: fromPtr(event.Error),
Timestamp: event.Timestamp.Format(time.RFC3339Nano),
Timestamp: event.Timestamp,
}
}
}
Expand Down Expand Up @@ -230,8 +228,7 @@ func (ack *AckT) handleAckEvents(ctx context.Context, zlog zerolog.Logger, agent
}

setError := func(pos int, err error) {
var esErr *es.ErrElastic
if errors.As(err, &esErr) {
if esErr, ok := errors.AsType[*es.ErrElastic](err); ok {
setResult(pos, esErr.Status)
} else if errors.Is(err, bulk.ErrTooManyBulkDispatches) {
setResult(pos, http.StatusTooManyRequests)
Expand Down
Loading
Loading