Repository: cloudflare/cloudflare-go
Pull request #1468 (2023-12-19 12:46:42)

Added Logpush Output Options support
<!-- 
Provide a general summary of your changes in the title above. You should
remove this overview, any sections and any section descriptions you
don't need below before submitting. There isn't a strict requirement to
use this template if you can structure your description and still cover
these points. 
-->

## Description

Adds Logpush Output options support

<!--
Describe your changes in detail through motivation and context. Why is
this change required? What problem does it solve? If it fixes an open
issue, link to the issue using GitHub's closing issues keywords[1].
-->
## Has your change been tested?

Added tests that cover the same usage as the old logpull options.

<!--
Explain how the change has been tested and what you ran to confirm your
change affects other parts of the code. Automated tests are generally
expected and changes without tests should explain why they aren't
required.
-->

## Screenshots (if appropriate):

## Types of changes

What sort of change does your code introduce/modify?

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist:

- [x] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.
- [ ] This change is using publicly documented in [cloudflare/api-schemas](https://github.com/cloudflare/api-schemas) 
      and relies on stable APIs.

[1]: https://help.github.com/articles/closing-issues-using-keywords/


github-actions[bot]: Oops! It looks like no changelog entry is attached to this PR. Please include a release note as described in https://github.com/cloudflare/cloudflare-go/blob/master/docs/changelog-process.md.

Example: 

~~~
```release-note:TYPE
Release note
```
~~~

If you do not require a release note to be included, please add the `workflow/skip-changelog-entry` label.

Patch:
diff --git a/.changelog/1464.txt b/.changelog/1464.txt
deleted file mode 100644
index d2bb00b15..000000000
--- a/.changelog/1464.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-```release-note:enhancement
-device_posture_rules: add support for Access client fields in device posture integrations
-```
\ No newline at end of file
diff --git a/.changelog/1466.txt b/.changelog/1466.txt
deleted file mode 100644
index c7e1d56aa..000000000
--- a/.changelog/1466.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-```release-note:dependency
-deps: bumps golang.org/x/crypto from 0.14.0 to 0.17.0
-```
diff --git a/.changelog/1468.txt b/.changelog/1468.txt
new file mode 100644
index 000000000..3c91f6401
--- /dev/null
+++ b/.changelog/1468.txt
@@ -0,0 +1,3 @@
+```release-note:enhancement
+logpush: Add support for Output Options
+```
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e9ecdfe2..695b8dc08 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,14 +3,12 @@
 ENHANCEMENTS:
 
 * access_group: Add support for email lists ([#1445](https://github.com/cloudflare/cloudflare-go/issues/1445))
-* device_posture_rules: add support for Access client fields in device posture integrations ([#1464](https://github.com/cloudflare/cloudflare-go/issues/1464))
 * page_shield: added support for page shield ([#1459](https://github.com/cloudflare/cloudflare-go/issues/1459))
 
 DEPENDENCIES:
 
 * deps: bumps actions/setup-go from 4 to 5 ([#1460](https://github.com/cloudflare/cloudflare-go/issues/1460))
 * deps: bumps github/codeql-action from 2 to 3 ([#1462](https://github.com/cloudflare/cloudflare-go/issues/1462))
-* deps: bumps golang.org/x/crypto from 0.14.0 to 0.17.0 ([#1466](https://github.com/cloudflare/cloudflare-go/issues/1466))
 
 ## 0.83.0 (December 6th, 2023)
 
diff --git a/device_posture_rule.go b/device_posture_rule.go
index 02c0cfe46..664d5241f 100644
--- a/device_posture_rule.go
+++ b/device_posture_rule.go
@@ -11,14 +11,12 @@ import (
 // DevicePostureIntegrationConfig contains authentication information
 // for a device posture integration.
 type DevicePostureIntegrationConfig struct {
-	ClientID           string `json:"client_id,omitempty"`
-	ClientSecret       string `json:"client_secret,omitempty"`
-	AuthUrl            string `json:"auth_url,omitempty"`
-	ApiUrl             string `json:"api_url,omitempty"`
-	ClientKey          string `json:"client_key,omitempty"`
-	CustomerID         string `json:"customer_id,omitempty"`
-	AccessClientID     string `json:"access_client_id,omitempty"`
-	AccessClientSecret string `json:"access_client_secret,omitempty"`
+	ClientID     string `json:"client_id,omitempty"`
+	ClientSecret string `json:"client_secret,omitempty"`
+	AuthUrl      string `json:"auth_url,omitempty"`
+	ApiUrl       string `json:"api_url,omitempty"`
+	ClientKey    string `json:"client_key,omitempty"`
+	CustomerID   string `json:"customer_id,omitempty"`
 }
 
 // DevicePostureIntegration represents a device posture integration.
diff --git a/internal/tools/go.mod b/internal/tools/go.mod
index 35abdb7fd..b0c02ca79 100644
--- a/internal/tools/go.mod
+++ b/internal/tools/go.mod
@@ -219,13 +219,13 @@ require (
 	go.uber.org/multierr v1.6.0 // indirect
 	go.uber.org/zap v1.17.0 // indirect
 	golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 // indirect
-	golang.org/x/crypto v0.17.0 // indirect
+	golang.org/x/crypto v0.14.0 // indirect
 	golang.org/x/exp/typeparams v0.0.0-20220722155223-a9213eeb770e // indirect
 	golang.org/x/mod v0.8.0 // indirect
 	golang.org/x/net v0.17.0 // indirect
 	golang.org/x/sync v0.1.0 // indirect
-	golang.org/x/sys v0.15.0 // indirect
-	golang.org/x/text v0.14.0 // indirect
+	golang.org/x/sys v0.13.0 // indirect
+	golang.org/x/text v0.13.0 // indirect
 	golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
 	golang.org/x/tools v0.6.0 // indirect
 	golang.org/x/vuln v0.0.0-20220725105440-4151a5aca1df // indirect
diff --git a/internal/tools/go.sum b/internal/tools/go.sum
index 8230c4ed3..766cd8d67 100644
--- a/internal/tools/go.sum
+++ b/internal/tools/go.sum
@@ -917,8 +917,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
-golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1104,10 +1104,10 @@ golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
+golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1117,8 +1117,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/logpush.go b/logpush.go
index 3fbb1af50..34e32e9cb 100644
--- a/logpush.go
+++ b/logpush.go
@@ -12,22 +12,23 @@ import (
 
 // LogpushJob describes a Logpush job.
 type LogpushJob struct {
-	ID                       int                `json:"id,omitempty"`
-	Dataset                  string             `json:"dataset"`
-	Enabled                  bool               `json:"enabled"`
-	Kind                     string             `json:"kind,omitempty"`
-	Name                     string             `json:"name"`
-	LogpullOptions           string             `json:"logpull_options"`
-	DestinationConf          string             `json:"destination_conf"`
-	OwnershipChallenge       string             `json:"ownership_challenge,omitempty"`
-	LastComplete             *time.Time         `json:"last_complete,omitempty"`
-	LastError                *time.Time         `json:"last_error,omitempty"`
-	ErrorMessage             string             `json:"error_message,omitempty"`
-	Frequency                string             `json:"frequency,omitempty"`
-	Filter                   *LogpushJobFilters `json:"filter,omitempty"`
-	MaxUploadBytes           int                `json:"max_upload_bytes,omitempty"`
-	MaxUploadRecords         int                `json:"max_upload_records,omitempty"`
-	MaxUploadIntervalSeconds int                `json:"max_upload_interval_seconds,omitempty"`
+	ID                       int                   `json:"id,omitempty"`
+	Dataset                  string                `json:"dataset"`
+	Enabled                  bool                  `json:"enabled"`
+	Kind                     string                `json:"kind,omitempty"`
+	Name                     string                `json:"name"`
+	LogpullOptions           string                `json:"logpull_options,omitempty"`
+	OutputOptions            *LogpushOutputOptions `json:"output_options,omitempty"`
+	DestinationConf          string                `json:"destination_conf"`
+	OwnershipChallenge       string                `json:"ownership_challenge,omitempty"`
+	LastComplete             *time.Time            `json:"last_complete,omitempty"`
+	LastError                *time.Time            `json:"last_error,omitempty"`
+	ErrorMessage             string                `json:"error_message,omitempty"`
+	Frequency                string                `json:"frequency,omitempty"`
+	Filter                   *LogpushJobFilters    `json:"filter,omitempty"`
+	MaxUploadBytes           int                   `json:"max_upload_bytes,omitempty"`
+	MaxUploadRecords         int                   `json:"max_upload_records,omitempty"`
+	MaxUploadIntervalSeconds int                   `json:"max_upload_interval_seconds,omitempty"`
 }
 
 type LogpushJobFilters struct {
@@ -63,6 +64,21 @@ type LogpushJobFilter struct {
 	Value    interface{} `json:"value,omitempty"`
 }
 
+type LogpushOutputOptions struct {
+	FieldNames      []string `json:"field_names"`
+	OutputType      string   `json:"output_type,omitempty"`
+	BatchPrefix     string   `json:"batch_prefix,omitempty"`
+	BatchSuffix     string   `json:"batch_suffix,omitempty"`
+	RecordPrefix    string   `json:"record_prefix,omitempty"`
+	RecordSuffix    string   `json:"record_suffix,omitempty"`
+	RecordTemplate  string   `json:"record_template,omitempty"`
+	RecordDelimiter string   `json:"record_delimiter,omitempty"`
+	FieldDelimiter  string   `json:"field_delimiter,omitempty"`
+	TimestampFormat string   `json:"timestamp_format,omitempty"`
+	SampleRate      float64  `json:"sample_rate,omitempty"`
+	CVE202144228    *bool    `json:"CVE-2021-44228,omitempty"`
+}
+
 // LogpushJobsResponse is the API response, containing an array of Logpush Jobs.
 type LogpushJobsResponse struct {
 	Response
@@ -323,19 +339,20 @@ func (filter *LogpushJobFilter) Validate() error {
 }
 
 type CreateLogpushJobParams struct {
-	Dataset                  string             `json:"dataset"`
-	Enabled                  bool               `json:"enabled"`
-	Kind                     string             `json:"kind,omitempty"`
-	Name                     string             `json:"name"`
-	LogpullOptions           string             `json:"logpull_options"`
-	DestinationConf          string             `json:"destination_conf"`
-	OwnershipChallenge       string             `json:"ownership_challenge,omitempty"`
-	ErrorMessage             string             `json:"error_message,omitempty"`
-	Frequency                string             `json:"frequency,omitempty"`
-	Filter                   *LogpushJobFilters `json:"filter,omitempty"`
-	MaxUploadBytes           int                `json:"max_upload_bytes,omitempty"`
-	MaxUploadRecords         int                `json:"max_upload_records,omitempty"`
-	MaxUploadIntervalSeconds int                `json:"max_upload_interval_seconds,omitempty"`
+	Dataset                  string                `json:"dataset"`
+	Enabled                  bool                  `json:"enabled"`
+	Kind                     string                `json:"kind,omitempty"`
+	Name                     string                `json:"name"`
+	LogpullOptions           string                `json:"logpull_options,omitempty"`
+	OutputOptions            *LogpushOutputOptions `json:"output_options,omitempty"`
+	DestinationConf          string                `json:"destination_conf"`
+	OwnershipChallenge       string                `json:"ownership_challenge,omitempty"`
+	ErrorMessage             string                `json:"error_message,omitempty"`
+	Frequency                string                `json:"frequency,omitempty"`
+	Filter                   *LogpushJobFilters    `json:"filter,omitempty"`
+	MaxUploadBytes           int                   `json:"max_upload_bytes,omitempty"`
+	MaxUploadRecords         int                   `json:"max_upload_records,omitempty"`
+	MaxUploadIntervalSeconds int                   `json:"max_upload_interval_seconds,omitempty"`
 }
 
 type ListLogpushJobsParams struct{}
@@ -349,22 +366,23 @@ type GetLogpushFieldsParams struct {
 }
 
 type UpdateLogpushJobParams struct {
-	ID                       int                `json:"-"`
-	Dataset                  string             `json:"dataset"`
-	Enabled                  bool               `json:"enabled"`
-	Kind                     string             `json:"kind,omitempty"`
-	Name                     string             `json:"name"`
-	LogpullOptions           string             `json:"logpull_options"`
-	DestinationConf          string             `json:"destination_conf"`
-	OwnershipChallenge       string             `json:"ownership_challenge,omitempty"`
-	LastComplete             *time.Time         `json:"last_complete,omitempty"`
-	LastError                *time.Time         `json:"last_error,omitempty"`
-	ErrorMessage             string             `json:"error_message,omitempty"`
-	Frequency                string             `json:"frequency,omitempty"`
-	Filter                   *LogpushJobFilters `json:"filter,omitempty"`
-	MaxUploadBytes           int                `json:"max_upload_bytes,omitempty"`
-	MaxUploadRecords         int                `json:"max_upload_records,omitempty"`
-	MaxUploadIntervalSeconds int                `json:"max_upload_interval_seconds,omitempty"`
+	ID                       int                   `json:"-"`
+	Dataset                  string                `json:"dataset"`
+	Enabled                  bool                  `json:"enabled"`
+	Kind                     string                `json:"kind,omitempty"`
+	Name                     string                `json:"name"`
+	LogpullOptions           string                `json:"logpull_options,omitempty"`
+	OutputOptions            *LogpushOutputOptions `json:"output_options,omitempty"`
+	DestinationConf          string                `json:"destination_conf"`
+	OwnershipChallenge       string                `json:"ownership_challenge,omitempty"`
+	LastComplete             *time.Time            `json:"last_complete,omitempty"`
+	LastError                *time.Time            `json:"last_error,omitempty"`
+	ErrorMessage             string                `json:"error_message,omitempty"`
+	Frequency                string                `json:"frequency,omitempty"`
+	Filter                   *LogpushJobFilters    `json:"filter,omitempty"`
+	MaxUploadBytes           int                   `json:"max_upload_bytes,omitempty"`
+	MaxUploadRecords         int                   `json:"max_upload_records,omitempty"`
+	MaxUploadIntervalSeconds int                   `json:"max_upload_interval_seconds,omitempty"`
 }
 
 type ValidateLogpushOwnershipChallengeParams struct {


Test patch:
diff --git a/device_posture_rule_test.go b/device_posture_rule_test.go
index b9b72b2b8..79ce77c51 100644
--- a/device_posture_rule_test.go
+++ b/device_posture_rule_test.go
@@ -228,55 +228,6 @@ func TestDevicePostureIntegrationCreate(t *testing.T) {
 	}
 }
 
-func TestDevicePostureIntegrationTaniumCreate(t *testing.T) {
-	setup()
-	defer teardown()
-
-	id := "480f4f69-1a28-4fdd-9240-1ed29f0ac1db"
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
-		w.Header().Set("content-type", "application/json")
-		fmt.Fprintf(w, `{
-			"success": true,
-			"errors": [],
-			"messages": [],
-			"result": {
-				"id": "%s",
-				"interval": "1h",
-				"type": "tanium_s2s",
-				"name": "My Tanium integration",
-				"config": {
-					"api_url":              "https://api_url.example.com",
-					"client_secret":        "test_client_secret",
-					"access_client_id":     "test_access_client_id",
-					"access_client_secret": "test_access_client_secret"
-				}
-			}
-		}`, id)
-	}
-
-	want := DevicePostureIntegration{
-		IntegrationID: id,
-		Name:          "My Tanium integration",
-		Type:          "tanium_s2s",
-		Interval:      "1h",
-		Config: DevicePostureIntegrationConfig{
-			ApiUrl:             "https://api_url.example.com",
-			ClientSecret:       "test_client_secret",
-			AccessClientID:     "test_access_client_id",
-			AccessClientSecret: "test_access_client_secret",
-		},
-	}
-
-	mux.HandleFunc("/accounts/"+testAccountID+"/devices/posture/integration", handler)
-
-	actual, err := client.CreateDevicePostureIntegration(context.Background(), testAccountID, want)
-
-	if assert.NoError(t, err) {
-		assert.Equal(t, want, actual)
-	}
-}
-
 func TestDevicePostureIntegrationDelete(t *testing.T) {
 	setup()
 	defer teardown()
diff --git a/logpush_test.go b/logpush_test.go
index 72d97578e..f0872c624 100644
--- a/logpush_test.go
+++ b/logpush_test.go
@@ -32,6 +32,28 @@ const (
 	"frequency": "high",
 	"max_upload_bytes": 5000000
   }
+`
+	serverLogpushJobWithOutputOptionsDescription = `{
+	"id": %d,
+	"dataset": "http_requests",
+	"kind": "",
+	"enabled": false,
+	"name": "example.com",
+	"output_options": {
+		"field_names":[
+			"RayID",
+			"ClientIP",
+			"EdgeStartTimestamp"
+		],
+		"timestamp_format": "rfc3339"
+	},
+	"destination_conf": "s3://mybucket/logs?region=us-west-2",
+	"last_complete": "%[2]s",
+	"last_error": "%[2]s",
+	"error_message": "test",
+	"frequency": "high",
+	"max_upload_bytes": 5000000
+  }
 `
 	serverEdgeLogpushJobDescription = `{
 	"id": %d,
@@ -76,6 +98,26 @@ var (
 		Frequency:       "high",
 		MaxUploadBytes:  5000000,
 	}
+	expectedLogpushJobWithOutputOptionsStruct = LogpushJob{
+		ID:      jobID,
+		Dataset: "http_requests",
+		Enabled: false,
+		Name:    "example.com",
+		OutputOptions: &LogpushOutputOptions{
+			FieldNames: []string{
+				"RayID",
+				"ClientIP",
+				"EdgeStartTimestamp",
+			},
+			TimestampFormat: "rfc3339",
+		},
+		DestinationConf: "s3://mybucket/logs?region=us-west-2",
+		LastComplete:    &testLogpushTimestamp,
+		LastError:       &testLogpushTimestamp,
+		ErrorMessage:    "test",
+		Frequency:       "high",
+		MaxUploadBytes:  5000000,
+	}
 	expectedEdgeLogpushJobStruct = LogpushJob{
 		ID:              jobID,
 		Dataset:         "http_requests",
@@ -138,6 +180,10 @@ func TestGetLogpushJob(t *testing.T) {
 			result: serverLogpushJobDescription,
 			want:   expectedLogpushJobStruct,
 		},
+		"core logpush job with output options": {
+			result: serverLogpushJobWithOutputOptionsDescription,
+			want:   expectedLogpushJobWithOutputOptionsStruct,
+		},
 		"edge logpush job": {
 			result: serverEdgeLogpushJobDescription,
 			want:   expectedEdgeLogpushJobStruct,
@@ -198,6 +244,40 @@ func TestCreateLogpushJob(t *testing.T) {
 			result: serverLogpushJobDescription,
 			want:   expectedLogpushJobStruct,
 		},
+		"core logpush job with output options": {
+			newJob: CreateLogpushJobParams{
+				Dataset: "http_requests",
+				Enabled: false,
+				Name:    "example.com",
+				OutputOptions: &LogpushOutputOptions{
+					FieldNames: []string{
+						"RayID",
+						"ClientIP",
+						"EdgeStartTimestamp",
+					},
+					TimestampFormat: "rfc3339",
+				},
+				DestinationConf:  "s3://mybucket/logs?region=us-west-2",
+				MaxUploadRecords: 1000,
+			},
+			payload: `{
+				"dataset": "http_requests",
+				"enabled":false,
+				"name":"example.com",
+				"output_options": {
+					"field_names":[
+						"RayID",
+						"ClientIP",
+						"EdgeStartTimestamp"
+					],
+					"timestamp_format": "rfc3339"
+				},
+				"destination_conf":"s3://mybucket/logs?region=us-west-2",
+				"max_upload_records": 1000
+			}`,
+			result: serverLogpushJobWithOutputOptionsDescription,
+			want:   expectedLogpushJobWithOutputOptionsStruct,
+		},
 		"edge logpush job": {
 			newJob: CreateLogpushJobParams{
 				Dataset:         "http_requests",