Skip to content

Commit 845dded

Browse files
samarsinghalsamar singhal
and
samar singhal
authored
PR: Feature Request - New Resource Notification Policy and Label selector endpoint (#308)
Allow for policy definition where any resource (supported in ResourceTypes enum) with an associated label or labels may be collected into a report and sent to recipients (i.e, discrete label values are suffixed with email-domain). Co-authored-by: samar singhal <samarsinghal@ssamar-a01.vmware.com>
1 parent af5e63c commit 845dded

24 files changed

+727
-26
lines changed

README.md

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,18 @@ As mentioned previously the policy file must adhere to a naming convention
236236
See additional property requirements in Query policies and the aforementioned sample Github repository.
237237

238238

239+
#### Resource Notification Policies
240+
241+
Resource Notification policies are useful when you want to generate a report containing resources of a particular type and send that report to email recipients identified by one or more label values ascribed to the those resources.
242+
243+
For example, you might notify primary and/or secondary owners of a collection of a resources.
244+
245+
As mentioned previously the policy file must adhere to a naming convention
246+
247+
* a filename ending with `-RNP.json` encapsulates an individual [ResourceNotificationPolicy](src/main/java/io/pivotal/cfapp/domain/ResourceNotificationPolicy.java)
248+
249+
See additional property requirements in Resource Notification policies and the aforementioned sample Github repository.
250+
239251
#### Endpoint Policies
240252

241253
Endpoint policies are useful when you want to exercise any of the available GET endpoints and have the results sent to one or more designated email recipients.
@@ -1139,7 +1151,7 @@ POST /policies
11391151
"legacy-policies": [
11401152
{
11411153
"notifyee-email-template": {
1142-
"body": "<h3>TThese applications are deployed to a legacy stack</h3><p>To avoid repeated notification:</p><ul><li>for each application please execute a cf push and update the stack to a modern alternative or cf delete</li></ul><p>depending on whether or not you want to keep the workload running.</p>",
1154+
"body": "<h3>These applications are deployed to a legacy stack</h3><p>To avoid repeated notification:</p><ul><li>for each application please execute a cf push and update the stack to a modern alternative or cf delete</li></ul><p>depending on whether or not you want to keep the workload running.</p>",
11431155
"from": "admin@pcf.demo.ironleg.me",
11441156
"subject": "Legacy Policy Sample Report"
11451157
},
@@ -1175,7 +1187,32 @@ POST /policies
11751187
"service-offerings": [
11761188
"p-config-server"
11771189
]
1178-
}
1190+
},
1191+
{
1192+
"resource-notification-policies": [
1193+
{
1194+
"resource-email-template": {
1195+
"from": "admin@pcf.demo.ironleg.me",
1196+
"subject": "Platform Updates",
1197+
"body": "Please take a moment to review the platform updates and share it with your Org users"
1198+
},
1199+
"resource-email-metadata": {
1200+
"resource": "organizations",
1201+
"labels": [
1202+
"PrimaryOwner",
1203+
"SecondaryOwner"
1204+
],
1205+
"email-domain": "pivotal.io"
1206+
}
1207+
"resource-whitelist": [
1208+
"p-config-server"
1209+
]
1210+
"resource-blacklist": [
1211+
"pivot-sample-org"
1212+
]
1213+
}
1214+
]
1215+
}
11791216
]
11801217
}
11811218
```

src/main/java/io/pivotal/cfapp/controller/DbmsOnlyPoliciesController.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ public Mono<ResponseEntity<Void>> deleteHygienePolicy(@PathVariable String id) {
4343
.map(ResponseEntity::ok);
4444
}
4545

46+
@DeleteMapping("/policies/resourcenotification/{id}")
47+
public Mono<ResponseEntity<Void>> deleteResourceNotificationPolicy(@PathVariable String id) {
48+
return policiesService.deleteResourceNotificationPolicyById(id)
49+
.map(ResponseEntity::ok);
50+
}
51+
4652
@DeleteMapping("/policies/legacy/{id}")
4753
public Mono<ResponseEntity<Void>> deleteLegacyPolicy(@PathVariable String id) {
4854
return policiesService.deleteLegacyPolicyById(id)

src/main/java/io/pivotal/cfapp/controller/OnDemandResourceMetadataController.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
import org.springframework.web.bind.annotation.PatchMapping;
88
import org.springframework.web.bind.annotation.PathVariable;
99
import org.springframework.web.bind.annotation.RequestBody;
10+
import org.springframework.web.bind.annotation.RequestParam;
1011
import org.springframework.web.bind.annotation.RestController;
1112

1213
import io.pivotal.cfapp.domain.Metadata;
1314
import io.pivotal.cfapp.domain.Resource;
15+
import io.pivotal.cfapp.domain.Resources;
1416
import io.pivotal.cfapp.service.ResourceMetadataService;
1517
import reactor.core.publisher.Mono;
1618

@@ -28,6 +30,21 @@ public OnDemandResourceMetadataController(
2830
this.service = service;
2931
}
3032

33+
@GetMapping("/metadata/{type}")
34+
public Mono<ResponseEntity<Resources>> getResourcesMetadata(
35+
@PathVariable("type") String type,
36+
@RequestParam(value = "label_selector", required = false) String labelSelector
37+
) {
38+
if (labelSelector != null){
39+
return service.getResources(type,labelSelector)
40+
.map(r -> ResponseEntity.ok(r))
41+
.defaultIfEmpty(ResponseEntity.notFound().build());
42+
}
43+
return service.getResources(type)
44+
.map(r -> ResponseEntity.ok(r))
45+
.defaultIfEmpty(ResponseEntity.notFound().build());
46+
}
47+
3148
@GetMapping("/metadata/{type}/{id}")
3249
public Mono<ResponseEntity<Resource>> getResourceMetadata(
3350
@PathVariable("type") String type,

src/main/java/io/pivotal/cfapp/domain/EmailValidator.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ public class EmailValidator {
1616

1717
public static boolean isValid(String email) {
1818

19-
if (StringUtils.isBlank(email)) {
20-
return false;
21-
}
19+
if (StringUtils.isBlank(email)) {
20+
return false;
21+
}
2222

23-
Matcher matcher = EMAIL_PATTERN.matcher(email);
24-
return matcher.matches();
25-
}
23+
Matcher matcher = EMAIL_PATTERN.matcher(email);
24+
return matcher.matches();
25+
}
2626

2727
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package io.pivotal.cfapp.domain;
2+
3+
import org.apache.commons.lang3.StringUtils;
4+
5+
import com.fasterxml.jackson.annotation.JsonCreator;
6+
import com.fasterxml.jackson.annotation.JsonIgnore;
7+
import com.fasterxml.jackson.annotation.JsonInclude;
8+
import com.fasterxml.jackson.annotation.JsonProperty;
9+
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
10+
11+
import lombok.Builder;
12+
import lombok.Getter;
13+
14+
@Builder
15+
@JsonInclude(JsonInclude.Include.NON_NULL)
16+
@JsonPropertyOrder({ "from", "subject", "body" })
17+
@Getter
18+
public class OwnerNotificationTemplate {
19+
20+
@JsonProperty("from")
21+
private String from;
22+
23+
@JsonProperty("subject")
24+
private String subject;
25+
26+
@JsonProperty("body")
27+
private String body;
28+
29+
@JsonCreator
30+
public OwnerNotificationTemplate(
31+
@JsonProperty("from") String from,
32+
@JsonProperty("subject") String subject,
33+
@JsonProperty("body") String body
34+
) {
35+
this.from = from;
36+
this.subject = subject;
37+
this.body = body;
38+
}
39+
40+
@JsonIgnore
41+
public boolean isValid() {
42+
return EmailValidator.isValid(from)
43+
&& StringUtils.isNotBlank(subject)
44+
&& StringUtils.isNotBlank(body);
45+
}
46+
}

src/main/java/io/pivotal/cfapp/domain/Policies.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
@Builder
1515
@JsonInclude(JsonInclude.Include.NON_EMPTY)
16-
@JsonPropertyOrder({ "application-policies", "service-instance-policies", "endpoint-policies", "query-policies", "hygiene-policies", "legacy-policies" })
16+
@JsonPropertyOrder({ "application-policies", "service-instance-policies", "endpoint-policies", "query-policies", "hygiene-policies", "legacy-policies","resource-notification-policies" })
1717
public class Policies {
1818

1919
@JsonProperty("application-policies")
@@ -34,20 +34,26 @@ public class Policies {
3434
@JsonProperty("legacy-policies")
3535
private List<LegacyPolicy> legacyPolicies;
3636

37+
@JsonProperty("resource-notification-policies")
38+
private List<ResourceNotificationPolicy> resourceNotificationPolicies;
39+
3740
@JsonCreator
3841
Policies(
3942
@JsonProperty("application-policies") List<ApplicationPolicy> applicationPolicies,
4043
@JsonProperty("service-instance-policies") List<ServiceInstancePolicy> serviceInstancePolicies,
4144
@JsonProperty("endpoint-policies") List<EndpointPolicy> endpointPolicies,
4245
@JsonProperty("query-policies") List<QueryPolicy> queryPolicies,
4346
@JsonProperty("hygiene-policies") List<HygienePolicy> hygienePolicies,
44-
@JsonProperty("legacy-policies") List<LegacyPolicy> legacyPolicies) {
47+
@JsonProperty("legacy-policies") List<LegacyPolicy> legacyPolicies,
48+
@JsonProperty("resource-notification-policies") List<ResourceNotificationPolicy> resourceNotificationPolicies) {
4549
this.applicationPolicies = applicationPolicies;
4650
this.serviceInstancePolicies = serviceInstancePolicies;
4751
this.endpointPolicies = endpointPolicies;
4852
this.queryPolicies = queryPolicies;
4953
this.hygienePolicies = hygienePolicies;
5054
this.legacyPolicies = legacyPolicies;
55+
this.resourceNotificationPolicies = resourceNotificationPolicies;
56+
5157
}
5258

5359
public List<ApplicationPolicy> getApplicationPolicies() {
@@ -74,14 +80,19 @@ public List<ServiceInstancePolicy> getServiceInstancePolicies() {
7480
return serviceInstancePolicies != null ? serviceInstancePolicies: Collections.emptyList();
7581
}
7682

83+
public List<ResourceNotificationPolicy> getResourceNotificationPolicies() {
84+
return resourceNotificationPolicies != null ? resourceNotificationPolicies: Collections.emptyList();
85+
}
86+
7787
@JsonIgnore
7888
public boolean isEmpty() {
7989
return getApplicationPolicies().isEmpty()
8090
&& getServiceInstancePolicies().isEmpty()
8191
&& getEndpointPolicies().isEmpty()
8292
&& getQueryPolicies().isEmpty()
8393
&& getHygienePolicies().isEmpty()
84-
&& getLegacyPolicies().isEmpty();
94+
&& getLegacyPolicies().isEmpty()
95+
&& getResourceNotificationPolicies().isEmpty();
8596
}
8697

8798
}

src/main/java/io/pivotal/cfapp/domain/PoliciesValidator.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class PoliciesValidator {
2727
private static final String QUERY_REJECTED_MESSAGE = "-- {} was rejected because either name or sql was blank or sql did not start with SELECT.";
2828
private static final String EMAIL_NOTIFICATION_TEMPLATE_REJECTED_MESSAGE = "-- {} was rejected because either the email template did not contain valid email addresses for from/to or the subject/body was blank.";
2929
private static final String LEGACY_FILTER_REJECTED_MESSAGE = "-- {} was rejected because it must have only one filter. Choose either stacks or service-offerings filter.";
30+
private static final String RESOURCE_EMAIL_METADATA_REJECTED_MESSAGE = "-- {} was rejected because either the metadata template did not contain valid resource type or the labels/domain was blank.";
3031

3132

3233
private final StacksCache stacksCache;
@@ -153,6 +154,30 @@ public boolean validate(HygienePolicy policy) {
153154
return valid;
154155
}
155156

157+
public boolean validate(ResourceNotificationPolicy policy) {
158+
boolean hasId = Optional.ofNullable(policy.getId()).isPresent();
159+
boolean hasResourceEmailTemplate = Optional.ofNullable(policy.getResourceEmailTemplate()).isPresent();
160+
boolean hasResourceEmailMetadata = Optional.ofNullable(policy.getResourceEmailMetadata()).isPresent();
161+
162+
boolean valid = !hasId && hasResourceEmailTemplate && hasResourceEmailMetadata;
163+
if (hasResourceEmailTemplate) {
164+
if (!policy.getResourceEmailTemplate().isValid()) {
165+
valid = false;
166+
log.warn(EMAIL_NOTIFICATION_TEMPLATE_REJECTED_MESSAGE, policy.toString());
167+
}
168+
}
169+
if (hasResourceEmailMetadata) {
170+
if (!policy.getResourceEmailMetadata().isValid()) {
171+
valid = false;
172+
log.warn(RESOURCE_EMAIL_METADATA_REJECTED_MESSAGE, policy.toString());
173+
}
174+
}
175+
if (valid == false) {
176+
log.warn(REQUIRED_PROPERTIES_REJECTED_MESSAGE, policy.toString());
177+
}
178+
return valid;
179+
}
180+
156181
public boolean validate(LegacyPolicy policy) {
157182
boolean hasId = Optional.ofNullable(policy.getId()).isPresent();
158183
boolean hasOperatorTemplate = Optional.ofNullable(policy.getOperatorTemplate()).isPresent();

src/main/java/io/pivotal/cfapp/domain/Resource.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@
1111

1212
@Builder
1313
@Getter
14-
@JsonPropertyOrder({ "guid", "created_at", "updated_at", "metadata" })
14+
@JsonPropertyOrder({ "guid","name","created_at", "updated_at", "metadata" })
1515
public class Resource {
1616

1717
@JsonProperty("guid")
1818
private String guid;
1919

20+
@JsonProperty("name")
21+
private String name;
22+
2023
@JsonProperty("created_at")
2124
private Instant createdAt;
2225

@@ -28,11 +31,15 @@ public class Resource {
2831

2932
@JsonCreator
3033
public Resource(
31-
@JsonProperty("guid") String guid,
32-
@JsonProperty("created_at") Instant createdAt,
33-
@JsonProperty("updated_at") Instant updatedAt,
34-
@JsonProperty("metadata") EmbeddedMetadata metadata) {
34+
35+
@JsonProperty("guid") String guid,
36+
@JsonProperty("name") String name,
37+
@JsonProperty("created_at") Instant createdAt,
38+
@JsonProperty("updated_at") Instant updatedAt,
39+
@JsonProperty("metadata") EmbeddedMetadata metadata) {
40+
3541
this.guid = guid;
42+
this.name = name;
3643
this.createdAt = createdAt;
3744
this.updatedAt = updatedAt;
3845
this.metadata = metadata;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package io.pivotal.cfapp.domain;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import org.apache.commons.lang3.StringUtils;
7+
8+
import com.fasterxml.jackson.annotation.JsonCreator;
9+
import com.fasterxml.jackson.annotation.JsonIgnore;
10+
import com.fasterxml.jackson.annotation.JsonInclude;
11+
import com.fasterxml.jackson.annotation.JsonProperty;
12+
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
13+
14+
import io.jsonwebtoken.lang.Collections;
15+
import lombok.Builder;
16+
import lombok.Builder.Default;
17+
import lombok.Getter;
18+
19+
@Builder
20+
@JsonInclude(JsonInclude.Include.NON_NULL)
21+
@JsonPropertyOrder({ "resource", "labels", "email-domain" })
22+
@Getter
23+
public class ResourceEmailMetadata {
24+
25+
@JsonProperty("resource")
26+
private String resource;
27+
28+
@Default
29+
@JsonProperty("labels")
30+
private List<String> labels = new ArrayList<>();
31+
32+
@JsonProperty("email-domain")
33+
private String emailDomain;
34+
35+
36+
@JsonCreator
37+
public ResourceEmailMetadata(
38+
@JsonProperty("resource") String resource,
39+
@JsonProperty("labels") List<String> labels,
40+
@JsonProperty("email-domain") String emailDomain
41+
) {
42+
this.resource = resource;
43+
this.labels = labels;
44+
this.emailDomain = emailDomain;
45+
}
46+
47+
@JsonIgnore
48+
public boolean isValid() {
49+
return isValidResource(resource)
50+
&& !Collections.isEmpty(labels)
51+
&& StringUtils.isNotBlank(emailDomain);
52+
}
53+
54+
private static boolean isValidResource(String resource) {
55+
ResourceType[] resourceTypes = ResourceType.values();
56+
for (ResourceType resourceType : resourceTypes)
57+
if (resourceType.getId().equals(resource))
58+
return true;
59+
return false;
60+
}
61+
62+
}

0 commit comments

Comments
 (0)