-
Notifications
You must be signed in to change notification settings - Fork 80
Add logged-on User details to the Monitor and Destination #255
Changes from 3 commits
cffa376
1ceb3a9
ff14293
39411a0
58baa44
c37ed20
c8c476c
8e1092f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,6 +52,7 @@ data class Monitor( | |
override val schedule: Schedule, | ||
override val lastUpdateTime: Instant, | ||
override val enabledTime: Instant?, | ||
val user: User?, | ||
val schemaVersion: Int = NO_SCHEMA_VERSION, | ||
val inputs: List<Input>, | ||
val triggers: List<Trigger>, | ||
|
@@ -81,9 +82,12 @@ data class Monitor( | |
sin.readLong(), // version | ||
sin.readString(), // name | ||
sin.readBoolean(), // enabled | ||
Schedule.readFrom(sin), | ||
Schedule.readFrom(sin), // schedule | ||
sin.readInstant(), // lastUpdateTime | ||
sin.readOptionalInstant(), // enabledTime | ||
if (sin.readBoolean()) { | ||
User.readFrom(sin) // user | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No particular reason. const is more clean, agree. |
||
} else null, | ||
sin.readInt(), // schemaVersion | ||
sin.readList(::SearchInput), // inputs | ||
sin.readList(::Trigger), // triggers | ||
|
@@ -104,6 +108,7 @@ data class Monitor( | |
builder.field(TYPE_FIELD, type) | ||
.field(SCHEMA_VERSION_FIELD, schemaVersion) | ||
.field(NAME_FIELD, name) | ||
.optionalUserField(user) | ||
.field(ENABLED_FIELD, enabled) | ||
.optionalTimeField(ENABLED_TIME_FIELD, enabledTime) | ||
.field(SCHEDULE_FIELD, schedule) | ||
|
@@ -117,8 +122,15 @@ data class Monitor( | |
|
||
override fun fromDocument(id: String, version: Long): Monitor = copy(id = id, version = version) | ||
|
||
private fun XContentBuilder.optionalUserField(user: User?): XContentBuilder { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Move to ElasticExtensions There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||
if (user == null) { | ||
return nullField(USER_FIELD) | ||
} | ||
return this.field(USER_FIELD, user) | ||
} | ||
|
||
@Throws(IOException::class) | ||
fun writeTo(out: StreamOutput) { | ||
override fun writeTo(out: StreamOutput) { | ||
out.writeString(id) | ||
out.writeLong(version) | ||
out.writeString(name) | ||
|
@@ -131,6 +143,12 @@ data class Monitor( | |
schedule.writeTo(out) | ||
out.writeInstant(lastUpdateTime) | ||
out.writeOptionalInstant(enabledTime) | ||
if (user != null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Same as above out.writeBoolean(user != null)
user?.writeTo(out) |
||
out.writeBoolean(true) | ||
user.writeTo(out) | ||
} else { | ||
out.writeBoolean(false) | ||
} | ||
out.writeInt(schemaVersion) | ||
out.writeCollection(inputs) | ||
out.writeCollection(triggers) | ||
|
@@ -142,6 +160,7 @@ data class Monitor( | |
const val TYPE_FIELD = "type" | ||
const val SCHEMA_VERSION_FIELD = "schema_version" | ||
const val NAME_FIELD = "name" | ||
const val USER_FIELD = "user" | ||
const val ENABLED_FIELD = "enabled" | ||
const val SCHEDULE_FIELD = "schedule" | ||
const val TRIGGERS_FIELD = "triggers" | ||
|
@@ -163,6 +182,7 @@ data class Monitor( | |
@Throws(IOException::class) | ||
fun parse(xcp: XContentParser, id: String = NO_ID, version: Long = NO_VERSION): Monitor { | ||
lateinit var name: String | ||
var user: User? = null | ||
lateinit var schedule: Schedule | ||
var lastUpdateTime: Instant? = null | ||
var enabledTime: Instant? = null | ||
|
@@ -180,6 +200,7 @@ data class Monitor( | |
when (fieldName) { | ||
SCHEMA_VERSION_FIELD -> schemaVersion = xcp.intValue() | ||
NAME_FIELD -> name = xcp.text() | ||
USER_FIELD -> user = User.parse(xcp) | ||
ENABLED_FIELD -> enabled = xcp.booleanValue() | ||
SCHEDULE_FIELD -> schedule = Schedule.parse(xcp) | ||
INPUTS_FIELD -> { | ||
|
@@ -215,6 +236,7 @@ data class Monitor( | |
requireNotNull(schedule) { "Monitor schedule is null" }, | ||
lastUpdateTime ?: Instant.now(), | ||
enabledTime, | ||
user, | ||
schemaVersion, | ||
inputs.toList(), | ||
triggers.toList(), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package com.amazon.opendistroforelasticsearch.alerting.model | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing License |
||
|
||
import org.elasticsearch.common.io.stream.StreamInput | ||
import org.elasticsearch.common.io.stream.StreamOutput | ||
import org.elasticsearch.common.io.stream.Writeable | ||
import org.elasticsearch.common.xcontent.ToXContent | ||
import org.elasticsearch.common.xcontent.ToXContentObject | ||
import org.elasticsearch.common.xcontent.XContentBuilder | ||
import org.elasticsearch.common.xcontent.XContentParser | ||
import org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken | ||
import java.io.IOException | ||
|
||
data class User( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was under the impression we wanted to decouple the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having name, roles, backend_roles as user (sub) object is much clean. Checks are simpler. |
||
val name: String, | ||
val backendRoles: List<String>, | ||
val roles: List<String>, | ||
val customAttNames: List<String> | ||
) : Writeable, ToXContentObject { | ||
|
||
@Throws(IOException::class) | ||
constructor(sin: StreamInput): this( | ||
sin.readString(), // name | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I prefer using the named arguments instead of comments @Throws(IOException::class)
constructor(sin: StreamInput): this(
name = sin.readString(),
backendRoles = sin.readStringList(),
roles = sin.readStringList(),
customAttNames = sin.readStringList()
) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1. Still kotlin newbie |
||
sin.readStringList(), // backendRoles | ||
sin.readStringList(), // roles | ||
sin.readStringList() // customAttNames | ||
) | ||
|
||
override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { | ||
builder.startObject() | ||
.field(NAME_FIELD, name) | ||
.field(BACKEND_ROLES_FIELD, backendRoles) | ||
.field(ROLES_FIELD, roles) | ||
.field(CUSTOM_ATTRIBUTE_NAMES_FIELD, customAttNames) | ||
return builder.endObject() | ||
} | ||
@Throws(IOException::class) | ||
override fun writeTo(out: StreamOutput) { | ||
out.writeString(name) | ||
out.writeStringCollection(backendRoles) | ||
out.writeStringCollection(roles) | ||
out.writeStringCollection(customAttNames) | ||
} | ||
|
||
companion object { | ||
const val NAME_FIELD = "name" | ||
const val BACKEND_ROLES_FIELD = "backend_roles" | ||
const val ROLES_FIELD = "roles" | ||
const val CUSTOM_ATTRIBUTE_NAMES_FIELD = "custom_attribute_names" | ||
|
||
@JvmStatic | ||
@Throws(IOException::class) | ||
fun parse(xcp: XContentParser): User { | ||
var name = "" | ||
val backendRoles: MutableList<String> = mutableListOf() | ||
val roles: MutableList<String> = mutableListOf() | ||
val customAttNames: MutableList<String> = mutableListOf() | ||
|
||
while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { | ||
val fieldName = xcp.currentName() | ||
xcp.nextToken() | ||
when (fieldName) { | ||
NAME_FIELD -> name = xcp.text() | ||
ROLES_FIELD -> { | ||
ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp::getTokenLocation) | ||
while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { | ||
roles.add(xcp.text()) | ||
} | ||
} | ||
BACKEND_ROLES_FIELD -> { | ||
ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp::getTokenLocation) | ||
while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { | ||
backendRoles.add(xcp.text()) | ||
} | ||
} | ||
CUSTOM_ATTRIBUTE_NAMES_FIELD -> { | ||
ensureExpectedToken(XContentParser.Token.START_ARRAY, xcp.currentToken(), xcp::getTokenLocation) | ||
while (xcp.nextToken() != XContentParser.Token.END_ARRAY) { | ||
customAttNames.add(xcp.text()) | ||
} | ||
} | ||
} | ||
} | ||
return User(name, backendRoles, roles, customAttNames) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want any validation of the values here? Or is a |
||
} | ||
|
||
@JvmStatic | ||
@Throws(IOException::class) | ||
fun readFrom(sin: StreamInput): User { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Function seems redundant if you can just call User(sin) instead. |
||
return User(sin) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,7 @@ import com.amazon.opendistroforelasticsearch.alerting.destination.response.Desti | |
import com.amazon.opendistroforelasticsearch.alerting.elasticapi.convertToMap | ||
import com.amazon.opendistroforelasticsearch.alerting.elasticapi.instant | ||
import com.amazon.opendistroforelasticsearch.alerting.elasticapi.optionalTimeField | ||
import com.amazon.opendistroforelasticsearch.alerting.model.User | ||
import com.amazon.opendistroforelasticsearch.alerting.util.DestinationType | ||
import com.amazon.opendistroforelasticsearch.alerting.util.IndexUtils.Companion.NO_SCHEMA_VERSION | ||
import org.apache.logging.log4j.LogManager | ||
|
@@ -46,6 +47,7 @@ data class Destination( | |
val schemaVersion: Int = NO_SCHEMA_VERSION, | ||
val type: DestinationType, | ||
val name: String, | ||
val user: User?, | ||
val lastUpdateTime: Instant, | ||
val chime: Chime?, | ||
val slack: Slack?, | ||
|
@@ -57,6 +59,7 @@ data class Destination( | |
if (params.paramAsBoolean("with_type", false)) builder.startObject(DESTINATION) | ||
builder.field(TYPE_FIELD, type.value) | ||
.field(NAME_FIELD, name) | ||
.optionalUserField(user) | ||
.field(SCHEMA_VERSION, schemaVersion) | ||
.optionalTimeField(LAST_UPDATE_TIME_FIELD, lastUpdateTime) | ||
.field(type.value, constructResponseForDestinationType(type)) | ||
|
@@ -68,13 +71,26 @@ data class Destination( | |
return toXContent(builder, ToXContent.EMPTY_PARAMS) | ||
} | ||
|
||
private fun XContentBuilder.optionalUserField(user: User?): XContentBuilder { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Duplication of one in Monitor, just move to ElasticExtensions and use in both |
||
if (user == null) { | ||
return nullField(USER_FIELD) | ||
} | ||
return this.field(USER_FIELD, user) | ||
} | ||
|
||
@Throws(IOException::class) | ||
fun writeTo(out: StreamOutput) { | ||
out.writeString(id) | ||
out.writeLong(version) | ||
out.writeInt(schemaVersion) | ||
out.writeEnum(type) | ||
out.writeString(name) | ||
if (user != null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as other comments |
||
out.writeBoolean(true) | ||
user.writeTo(out) | ||
} else { | ||
out.writeBoolean(false) | ||
} | ||
out.writeInstant(lastUpdateTime) | ||
if (chime != null) { | ||
out.writeBoolean(true) | ||
|
@@ -100,6 +116,7 @@ data class Destination( | |
const val DESTINATION = "destination" | ||
const val TYPE_FIELD = "type" | ||
const val NAME_FIELD = "name" | ||
const val USER_FIELD = "user" | ||
const val NO_ID = "" | ||
const val NO_VERSION = 1L | ||
const val SCHEMA_VERSION = "schema_version" | ||
|
@@ -117,6 +134,7 @@ data class Destination( | |
@Throws(IOException::class) | ||
fun parse(xcp: XContentParser, id: String = NO_ID, version: Long = NO_VERSION): Destination { | ||
lateinit var name: String | ||
var user: User? = null | ||
lateinit var type: String | ||
var slack: Slack? = null | ||
var chime: Chime? = null | ||
|
@@ -131,6 +149,7 @@ data class Destination( | |
|
||
when (fieldName) { | ||
NAME_FIELD -> name = xcp.text() | ||
USER_FIELD -> user = User.parse(xcp) | ||
TYPE_FIELD -> { | ||
type = xcp.text() | ||
val allowedTypes = DestinationType.values().map { it.value } | ||
|
@@ -164,6 +183,7 @@ data class Destination( | |
schemaVersion, | ||
DestinationType.valueOf(type.toUpperCase(Locale.ROOT)), | ||
requireNotNull(name) { "Destination name is null" }, | ||
user, | ||
lastUpdateTime ?: Instant.now(), | ||
chime, | ||
slack, | ||
|
@@ -179,6 +199,9 @@ data class Destination( | |
sin.readInt(), // schemaVersion | ||
sin.readEnum(DestinationType::class.java), // type | ||
sin.readString(), // name | ||
if (sin.readBoolean()) { | ||
User.readFrom(sin) // user | ||
} else null, | ||
sin.readInstant(), // lastUpdateTime | ||
Chime.readFrom(sin), // chime | ||
Slack.readFrom(sin), // slack | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,7 @@ import com.amazon.opendistroforelasticsearch.alerting.model.ActionRunResult | |
import com.amazon.opendistroforelasticsearch.alerting.model.InputRunResults | ||
import com.amazon.opendistroforelasticsearch.alerting.model.MonitorRunResult | ||
import com.amazon.opendistroforelasticsearch.alerting.model.TriggerRunResult | ||
import com.amazon.opendistroforelasticsearch.alerting.model.User | ||
import com.amazon.opendistroforelasticsearch.alerting.model.action.Throttle | ||
import org.apache.http.Header | ||
import org.apache.http.HttpEntity | ||
|
@@ -57,6 +58,7 @@ import java.time.temporal.ChronoUnit | |
|
||
fun randomMonitor( | ||
name: String = ESRestTestCase.randomAlphaOfLength(10), | ||
user: User = randomUser(), | ||
inputs: List<Input> = listOf(SearchInput(emptyList(), SearchSourceBuilder().query(QueryBuilders.matchAllQuery()))), | ||
schedule: Schedule = IntervalSchedule(interval = 5, unit = ChronoUnit.MINUTES), | ||
enabled: Boolean = ESTestCase.randomBoolean(), | ||
|
@@ -67,7 +69,23 @@ fun randomMonitor( | |
): Monitor { | ||
return Monitor(name = name, enabled = enabled, inputs = inputs, schedule = schedule, triggers = triggers, | ||
enabledTime = enabledTime, lastUpdateTime = lastUpdateTime, | ||
uiMetadata = if (withMetadata) mapOf("foo" to "bar") else mapOf()) | ||
user = user, uiMetadata = if (withMetadata) mapOf("foo" to "bar") else mapOf()) | ||
} | ||
|
||
// Monitor of older versions without security. | ||
fun randomMonitorWithoutUser( | ||
name: String = ESRestTestCase.randomAlphaOfLength(10), | ||
inputs: List<Input> = listOf(SearchInput(emptyList(), SearchSourceBuilder().query(QueryBuilders.matchAllQuery()))), | ||
schedule: Schedule = IntervalSchedule(interval = 5, unit = ChronoUnit.MINUTES), | ||
enabled: Boolean = ESTestCase.randomBoolean(), | ||
triggers: List<Trigger> = (1..randomInt(10)).map { randomTrigger() }, | ||
enabledTime: Instant? = if (enabled) Instant.now().truncatedTo(ChronoUnit.MILLIS) else null, | ||
lastUpdateTime: Instant = Instant.now().truncatedTo(ChronoUnit.MILLIS), | ||
withMetadata: Boolean = false | ||
): Monitor { | ||
return Monitor(name = name, enabled = enabled, inputs = inputs, schedule = schedule, triggers = triggers, | ||
enabledTime = enabledTime, lastUpdateTime = lastUpdateTime, | ||
user = null, uiMetadata = if (withMetadata) mapOf("foo" to "bar") else mapOf()) | ||
} | ||
|
||
fun randomTrigger( | ||
|
@@ -164,6 +182,14 @@ fun Monitor.toJsonString(): String { | |
return this.toXContent(builder).string() | ||
} | ||
|
||
fun randomUser(): User { | ||
return User("joe", listOf("ops", "backup"), listOf("all_access"), listOf("test_attr=test")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These don't really seem random 😛 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. randomized |
||
} | ||
|
||
fun randomUserEmpty(): User { | ||
return User("", listOf(), listOf(), listOf()) | ||
} | ||
|
||
/** | ||
* Wrapper for [RestClient.performRequest] which was deprecated in ES 6.5 and is used in tests. This provides | ||
* a single place to suppress deprecation warnings. This will probably need further work when the API is removed entirely | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Maybe cleaner?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
like it 👍