1
0
mirror of https://github.com/google/nomulus synced 2026-05-25 17:20:32 +00:00

Compare commits

...

23 Commits

Author SHA1 Message Date
Pavlo Tkach
6c7bf5e5dd Enable Users and Domains actions, add email notification (#2700) 2025-02-28 21:57:49 +00:00
Pavlo Tkach
ea1e8d5cc5 Add console gzip compression to js,css and html files (#2696) 2025-02-27 22:52:10 +00:00
Lai Jiang
7fb846c5b0 Add headers to record WHOIS client IPs (#2695)
The headers can be used by Cloud Armor to perform IP-based rate
limiting.
2025-02-27 22:15:13 +00:00
Lai Jiang
5180095cb6 Reduce log level to info when no email is found from the OIDC token (#2694)
This can happen on public endpoints (in pubapi) where the service is
behind IAP but all users (including not-logged-in ones) are allowed. IAP
will add an OIDC token with no email field in the request header.
2025-02-26 22:17:45 +00:00
Lai Jiang
9fe64bf9ec Make ignoreLinesStartingWith varargs (#2691)
It still is a list, because we String::startsWith does not benefit from
the target being in a set.
2025-02-26 17:12:24 +00:00
Lai Jiang
0f3b62d5ce Change the sleep time between proxy rollout (#2689) 2025-02-26 04:48:52 +00:00
Ben McIlwain
bd4701647b Refactor logic out of domain create flow tests (#2688)
This removes logic from an inner helper method so that it becomes more clear
from callsites within each test exactly which behavior is expected from those
test conditions.
2025-02-25 19:54:56 +00:00
Lai Jiang
fb816d7a2c Make it possible to ignore comment lines when comparing schemas (#2690)
We now pin to postgreSQL v17 when running tests, which means that minor
version might increase without our intervention. This causes (at least)
the comment in the golden schema to change, and failing the test as a
result.

This PR adds the ability to strip lines that we deem as comment from the
comparison, so we don't have to do trivial upgrades to the gold schema
whenever there's minor version upgrade.
2025-02-25 16:58:26 +00:00
gbrodman
8fbf363195 Remove unused dummy PGP file (#2687)
This was previously used as a dummy value for testing / compilation but
it's not used any more.
2025-02-24 21:45:26 +00:00
Lai Jiang
397f800614 Connect to GKE by default from the tool (#2686) 2025-02-24 19:01:05 +00:00
Lai Jiang
bcf42bd287 Use static IPs for EPP endpoints (#2685)
These IPs are now provisioned by Terraform. Also delete the
get-endpoints.py script as it is no longer necessary.
2025-02-24 16:38:47 +00:00
Pavlo Tkach
ed95d19b93 Provide prompt for user deletion UI (#2684) 2025-02-21 20:30:03 +00:00
Lai Jiang
97fc2c0b66 Add an annotation to the deployment (#2683)
This allows us to easily tell which tag was deployed.

Also set the gateway to use named address so they are stable, and so
that we can attach an IPv6 record to it. Auto-provisioned addresses are
IPv4 only.
2025-02-21 16:30:32 +00:00
Weimin Yu
00728c40ba Abort schema verifier when pg_dump fails (#2681)
Failed pg_dump may not leave a file, failing the subsequent diffing and
causing the verifier to return success.

The verifier should abort in this case.
2025-02-20 17:35:47 +00:00
Lai Jiang
3f2a42ab8d Expose EPP via saidcar proxy (#2680) 2025-02-19 18:57:25 +00:00
Lai Jiang
b73e342820 Update PostgreSQL version in builder image and tests (#2667) 2025-02-18 17:34:41 +00:00
Lai Jiang
df7fec7a3e Update RDAP TOS link (#2678) 2025-02-18 17:00:26 +00:00
Lai Jiang
6f7ae1eabc Redirect HTTP to HTTPS (#2679)
This opens up port 80 on the load balancer IP and upgrades all HTTP
request to HTTPS.

TESTED=tested on alpha.
2025-02-18 16:57:18 +00:00
Lai Jiang
eb978ebbd5 Let nomulus tool connect to sandbox GKE by default (#2674) 2025-02-16 18:10:03 +00:00
Pavlo Tkach
95831bc8b7 Add suspend / unsuspend to the console (#2675) 2025-02-14 20:41:19 +00:00
Lai Jiang
538260521b Update Nomulus deployment script (#2677)
We only deploy to the us-central1 cluster in order to minimize database
locality issue.
2025-02-14 17:31:18 +00:00
Pavlo Tkach
612708f0a8 Fix console user creation role param (#2676) 2025-02-14 13:51:06 +00:00
Lai Jiang
e78de98060 Read GKE logs in ICANN reports (#2673)
GKE logs are routed to a different dataset and the table is different.
The structs to look for are also different (jsonPayload vs textPayload
or protoPayload).

TESTED=Ran the resulting query in crash.
2025-02-12 20:41:44 +00:00
81 changed files with 1017 additions and 616 deletions

View File

@@ -63,6 +63,7 @@ public class TextDiffSubject extends Subject {
private final ImmutableList<String> actual;
private DiffFormat diffFormat = DiffFormat.SIDE_BY_SIDE_MARKDOWN;
private ImmutableList<String> comments = ImmutableList.of();
protected TextDiffSubject(FailureMetadata metadata, List<String> actual) {
super(metadata, actual);
@@ -83,10 +84,22 @@ public class TextDiffSubject extends Subject {
return this;
}
/** If set, ignore lines that start with the given string. */
public TextDiffSubject ignoringLinesStartingWith(String... comments) {
this.comments = ImmutableList.copyOf(comments);
return this;
}
private ImmutableList<String> filterComments(List<String> lines) {
return lines.stream()
.filter(line -> comments.stream().noneMatch(line::startsWith))
.collect(ImmutableList.toImmutableList());
}
public void hasSameContentAs(List<String> expectedContent) {
checkNotNull(expectedContent, "expectedContent");
ImmutableList<String> expected = ImmutableList.copyOf(expectedContent);
if (expected.equals(actual)) {
ImmutableList<String> expected = filterComments(expectedContent);
if (filterComments(expected).equals(filterComments(actual))) {
return;
}
String diffString = diffFormat.generateDiff(expected, actual);

View File

@@ -24,7 +24,11 @@
</div>
} @else {
<mat-menu #actions="matMenu">
<ng-template matMenuContent let-domainName="domainName">
<ng-template
matMenuContent
let-domainName="domainName"
let-domain="domain"
>
<button
mat-menu-item
(click)="openRegistryLock(domainName)"
@@ -33,6 +37,24 @@
<mat-icon>key</mat-icon>
<span>Registry Lock</span>
</button>
<button
mat-menu-item
(click)="onSuspendClick(domainName)"
[elementId]="getElementIdForSuspendUnsuspend()"
[disabled]="isDomainUnsuspendable(domain)"
>
<mat-icon>lock_clock</mat-icon>
<span>Suspend</span>
</button>
<button
mat-menu-item
(click)="onUnsuspendClick(domainName)"
[elementId]="getElementIdForSuspendUnsuspend()"
[disabled]="!isDomainUnsuspendable(domain)"
>
<mat-icon>lock_open</mat-icon>
<span>Unsuspend</span>
</button>
</ng-template>
</mat-menu>
<div
@@ -170,7 +192,10 @@
<button
mat-icon-button
[matMenuTriggerFor]="actions"
[matMenuTriggerData]="{ domainName: element.domainName }"
[matMenuTriggerData]="{
domainName: element.domainName,
domain: element
}"
aria-label="Domain actions"
>
<mat-icon>more_horiz</mat-icon>

View File

@@ -14,13 +14,17 @@
import { SelectionModel } from '@angular/cdk/collections';
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { Component, ViewChild, effect, Inject } from '@angular/core';
import { Component, effect, Inject, ViewChild } from '@angular/core';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatTableDataSource } from '@angular/material/table';
import { Subject, debounceTime, take, filter } from 'rxjs';
import { debounceTime, filter, Subject, take } from 'rxjs';
import { RegistrarService } from '../registrar/registrar.service';
import { Domain, DomainListService } from './domainList.service';
import {
BULK_ACTION_NAME,
Domain,
DomainListService,
} from './domainList.service';
import { RegistryLockComponent } from './registryLock.component';
import { RegistryLockService } from './registryLock.service';
import {
@@ -62,11 +66,17 @@ export class ResponseDialogComponent {
}
}
enum Operation {
deleting = 'deleting',
suspending = 'suspending',
unsuspending = 'unsuspending',
}
@Component({
selector: 'app-reason-dialog',
template: `
<h2 mat-dialog-title>
Please provide a reason for {{ data.operation }} the domain(s):
Please provide the (EPP) reason for {{ data.operation }} the domain(s):
</h2>
<mat-dialog-content>
<mat-form-field appearance="outline" style="width:100%">
@@ -75,8 +85,8 @@ export class ResponseDialogComponent {
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="onCancel()">Cancel</button>
<button mat-button color="warn" (click)="onDelete()" [disabled]="!reason">
Delete
<button mat-button color="warn" (click)="onSave()" [disabled]="!reason">
Save
</button>
</mat-dialog-actions>
`,
@@ -84,14 +94,13 @@ export class ResponseDialogComponent {
})
export class ReasonDialogComponent {
reason: string = '';
constructor(
public dialogRef: MatDialogRef<ReasonDialogComponent>,
@Inject(MAT_DIALOG_DATA)
public data: { operation: 'deleting' | 'suspending' }
public data: { operation: Operation }
) {}
onDelete(): void {
onSave(): void {
this.dialogRef.close(this.reason);
}
@@ -108,6 +117,13 @@ export class ReasonDialogComponent {
})
export class DomainListComponent {
public static PATH = 'domain-list';
private static SUSPENDED_STATUSES = [
'SERVER_RENEW_PROHIBITED',
'SERVER_TRANSFER_PROHIBITED',
'SERVER_UPDATE_PROHIBITED',
'SERVER_DELETE_PROHIBITED',
'SERVER_HOLD',
];
private readonly DEBOUNCE_MS = 500;
isAllSelected = false;
@@ -258,19 +274,30 @@ export class DomainListComponent {
return RESTRICTED_ELEMENTS.BULK_DELETE;
}
getElementIdForSuspendUnsuspend() {
return RESTRICTED_ELEMENTS.SUSPEND;
}
getOperationMessage(domain: string) {
if (this.operationResult && this.operationResult[domain])
return this.operationResult[domain].message;
return '';
}
isDomainUnsuspendable(domain: Domain) {
return DomainListComponent.SUSPENDED_STATUSES.every((s) =>
domain.statuses.includes(s)
);
}
sendDeleteRequest(reason: string) {
this.isLoading = true;
this.domainListService
.deleteDomains(
this.selection.selected,
.bulkDomainAction(
this.selection.selected.map((d) => d.domainName),
reason,
this.registrarService.registrarId()
this.registrarService.registrarId(),
BULK_ACTION_NAME.DELETE
)
.pipe(take(1))
.subscribe({
@@ -294,15 +321,17 @@ export class DomainListComponent {
this.operationResult = result;
this.reloadData();
},
error: (err: HttpErrorResponse) =>
this._snackBar.open(err.error || err.message),
error: (err: HttpErrorResponse) => {
this.isLoading = false;
this._snackBar.open(err.error || err.message);
},
});
}
deleteSelectedDomains() {
const dialogRef = this.dialog.open(ReasonDialogComponent, {
data: {
operation: 'deleting',
operation: Operation.deleting,
},
});
@@ -314,4 +343,77 @@ export class DomainListComponent {
)
.subscribe(this.sendDeleteRequest.bind(this));
}
sendSuspendUnsuspendRequest(
domainName: string,
reason: string,
actionName: BULK_ACTION_NAME
) {
this.isLoading = true;
this.domainListService
.bulkDomainAction(
[domainName],
reason,
this.registrarService.registrarId(),
actionName
)
.pipe(take(1))
.subscribe({
next: (result: DomainData) => {
this.isLoading = false;
if (result[domainName].responseCode.toString().startsWith('2')) {
this._snackBar.open(result[domainName].message);
} else {
this.reloadData();
}
},
error: (err: HttpErrorResponse) => {
this.isLoading = false;
this._snackBar.open(err.error || err.message);
},
});
}
onSuspendClick(domainName: string) {
const dialogRef = this.dialog.open(ReasonDialogComponent, {
data: {
operation: Operation.suspending,
},
});
dialogRef
.afterClosed()
.pipe(
take(1),
filter((reason) => !!reason)
)
.subscribe((reason) => {
this.sendSuspendUnsuspendRequest(
domainName,
reason,
BULK_ACTION_NAME.SUSPEND
);
});
}
onUnsuspendClick(domainName: string) {
const dialogRef = this.dialog.open(ReasonDialogComponent, {
data: {
operation: Operation.unsuspending,
},
});
dialogRef
.afterClosed()
.pipe(
take(1),
filter((reason) => !!reason)
)
.subscribe((reason) => {
this.sendSuspendUnsuspendRequest(
domainName,
reason,
BULK_ACTION_NAME.UNSUSPEND
);
});
}
}

View File

@@ -35,6 +35,12 @@ export interface DomainListResult {
totalResults: number;
}
export enum BULK_ACTION_NAME {
DELETE = 'DELETE',
SUSPEND = 'SUSPEND',
UNSUSPEND = 'UNSUSPEND',
}
@Injectable({
providedIn: 'root',
})
@@ -71,11 +77,16 @@ export class DomainListService {
);
}
deleteDomains(domains: Domain[], reason: string, registrarId: string) {
bulkDomainAction(
domains: string[],
reason: string,
registrarId: string,
actionName: BULK_ACTION_NAME
) {
return this.backendService.bulkDomainAction(
domains.map((d) => d.domainName),
domains,
reason,
'DELETE',
actionName,
registrarId
);
}

View File

@@ -20,17 +20,17 @@ export enum RESTRICTED_ELEMENTS {
OTE,
USERS,
BULK_DELETE,
SUSPEND,
}
export const DISABLED_ELEMENTS_PER_ROLE = {
NONE: [
RESTRICTED_ELEMENTS.REGISTRAR_ELEMENT,
RESTRICTED_ELEMENTS.OTE,
RESTRICTED_ELEMENTS.USERS,
RESTRICTED_ELEMENTS.BULK_DELETE,
RESTRICTED_ELEMENTS.SUSPEND,
],
SUPPORT_LEAD: [RESTRICTED_ELEMENTS.USERS],
SUPPORT_AGENT: [RESTRICTED_ELEMENTS.USERS],
SUPPORT_LEAD: [],
SUPPORT_AGENT: [],
};
@Directive({

View File

@@ -67,17 +67,24 @@ export class UserDetailsComponent {
}
deleteUser() {
this.isLoading = true;
this.usersService.deleteUser(this.userDetails()).subscribe({
error: (err) => {
this._snackBar.open(err.error || err.message);
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
this.goBack();
},
});
if (
confirm(
'This will permanently delete the user ' +
this.userDetails().emailAddress
)
) {
this.isLoading = true;
this.usersService.deleteUser(this.userDetails()).subscribe({
error: (err) => {
this._snackBar.open(err.error || err.message);
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
this.goBack();
},
});
}
}
goBack() {

View File

@@ -1,32 +0,0 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
lQHYBFo8aRIBBAC8MA2xQXYvEbeLV1iMo4GC3lRFYvrUCarenhwoWufCYH6dGien
/HhiB0eiDF672J4MtueHQ2M7UaGJgxAoQTG9c6O90vlmFFhPZ967U1MTdY/NLvDK
bQEGzjdaUC1T/O6kr0O4GHRAyNyHa39Q75Oaj8MNdPsTmT4tDy+aFO6kKwARAQAB
AAP9Gd59M12tUmEcGxKBwKuFVSkc6oDlvBosG/geJMoCS+0Z2pzK0MPbBJa9mSAc
MbRgXZ0TDLwNuwzIqO+UXARCQu1ln/NlCcSzQZd5S80Of6CSoFMdFEb0kcpFW3z9
rpZdIBpNNk2iyBro9+7JOLJgCUkZQX7jy2K4LM5eTJsnuMECANFBnrMUde43XBiT
gixOJ5zbekGIIGq4QeRc8fJUDUhkFMq1znNriu30bB0Ld4Btlxzyn56tx8DVgx1+
4anONuECAOY5nm2G9i46AUxQN3dB8IE0SMMHcRcz60eX68fke+1aYjdSQA/nf9hR
l2f+gX9+y3cPqo7bFZzrDNECRm3J2IsB/2444JDTnzyME99jRYeEZGM0BXMWZEoO
hLU7f2V8pdN1po6mZ5bZZv6LeTXWPCIqCuBxNHZAV/xH9oWmkpjnw8Sc77QpVGVz
dCBSZWdpc3RyeSA8dGVzdC1yZWdpc3RyeUBleGFtcGxlLmNvbT6IuAQTAQIAIgUC
WjxpEgIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQvIOfrLbgEN2NegP+
JV+i4DxPTv3jfRDcpGy8yDANiIoBFSyARpqMqg0+TfV/UypuyjFTGfnLuQv+osce
OKtPevH8gCc779+OqtqyNDcPooTG5K+eUYR77PYtfzsKk/Z/33tQtEJDb8WWn4G4
R9Nh51MOz1X17oe3ih9HNvMGIrOG9VeWPsTxjXAzBoidAdgEWjxpEgEEANiasS9p
bG53M3jeCwLX0PFgWgspMZl3QnU6bvaTsfMAHaklJ55Tj1wuaaQymHqNm6xElCN8
MK8exDQQvPZwYVQOuoP3cHriCslLGznB943URcuxXz6R7F7WixYUeVVpQ4J0+gFu
bR8PfThDCtHQyP+uYx9U+EVWIvuIZIchdjl9ABEBAAEAA/4xmt2sorthIf3g9pL1
e/jfKoZ8i1rPT1NiNvdeE217neFtEPP9i5vni76ISskGOgN2hH8bkE+y7zwWQ2YP
FyYGlvVcw2KjT7+SrAWCkgR6Y7hWib+RDcVGje+YH5MxGtBIX2W/zcOW5S9+nC3Q
Y3Tzc3YQxF8sOeaHvrEb1tJ9eQIA5ivEjt43GgZq0nxacKLhleXyA9Z/JmwDg15z
FCZCnPABmR72wpXzXe2gO18W3iiqwS/WFDbdSFwxDQ0lXSy8VQIA8Okv6Q2BNXEw
H0hufK8P7aHvuOI1ll4qTw6QkY+z5hRZAcmmID3boQJeJAmVbUissYKUNJudmiUJ
DPLQod+wiQIAtJWxlRgHvEHRjQS5tH13ERWLObBHdZcQvKcqdtTCZj1EVH7zVHpb
qBLggo7QwPJTC+UMf/f4nPd1U2O6zXv66p5liJ8EGAECAAkFAlo8aRICGwwACgkQ
vIOfrLbgEN141gP9GATYCoihm5igbZ0FL8YPPb5WvHpTEA4WgdIIUUCQ0TYJ2ZOC
dK0i3qbb1xRRBJq006qSiE4vqQ7fHO8HxmEWaPLlsPvebGm39PUuzVyWx8I2w+0/
qcxt5L2VVzbZFp6+Yoa+meRYsO77gAzUvqUG1yLWo6MD4pSUNYJA867BB/k=
=mkAP
-----END PGP PRIVATE KEY BLOCK-----

View File

@@ -70,7 +70,7 @@ import google.registry.rdap.RdapObjectClasses.RdapRegistrarEntity;
import google.registry.rdap.RdapObjectClasses.SecureDns;
import google.registry.rdap.RdapObjectClasses.Vcard;
import google.registry.rdap.RdapObjectClasses.VcardArray;
import google.registry.request.FullServletPath;
import google.registry.request.RequestServerName;
import google.registry.util.Clock;
import jakarta.persistence.Entity;
import java.net.Inet4Address;
@@ -114,7 +114,7 @@ public class RdapJsonFormatter {
@Nullable
String rdapTosStaticUrl;
@Inject @FullServletPath String fullServletPath;
@Inject @RequestServerName String serverName;
@Inject RdapAuthorization rdapAuthorization;
@Inject Clock clock;
@@ -267,7 +267,7 @@ public class RdapJsonFormatter {
.setDescription(rdapTos)
.addLink(selfLink);
if (rdapTosStaticUrl != null) {
URI htmlBaseURI = URI.create(fullServletPath);
URI htmlBaseURI = URI.create("https//:" + serverName + "/rdap/");
URI htmlUri = htmlBaseURI.resolve(rdapTosStaticUrl);
noticeBuilder.addLink(
Link.builder()
@@ -1071,7 +1071,7 @@ public class RdapJsonFormatter {
/** Create a link relative to the RDAP server endpoint. */
String makeRdapServletRelativeUrl(String part, String... moreParts) {
return makeServerRelativeUrl(fullServletPath, part, moreParts);
return makeServerRelativeUrl("https://" + serverName + "/rdap/", part, moreParts);
}
/** Create a link relative to some base server */

View File

@@ -70,10 +70,10 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
ImmutableMap.Builder<String, String> queriesBuilder = ImmutableMap.builder();
String operationalRegistrarsQuery;
operationalRegistrarsQuery =
SqlTemplate.create(getQueryFromFile("cloud_sql_registrar_operating_status.sql"))
.put("PROJECT_ID", projectId)
.build();
operationalRegistrarsQuery =
SqlTemplate.create(getQueryFromFile(REGISTRAR_OPERATING_STATUS + ".sql"))
.put("PROJECT_ID", projectId)
.build();
queriesBuilder.put(
getTableName(REGISTRAR_OPERATING_STATUS, yearMonth), operationalRegistrarsQuery);
@@ -83,9 +83,10 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
// Convert reportingMonth into YYYYMMDD format for Bigquery table partition pattern-matching.
DateTimeFormatter logTableFormatter = DateTimeFormat.forPattern("yyyyMMdd");
String monthlyLogsQuery =
SqlTemplate.create(getQueryFromFile("monthly_logs.sql"))
SqlTemplate.create(getQueryFromFile(MONTHLY_LOGS + ".sql"))
.put("PROJECT_ID", projectId)
.put("APPENGINE_LOGS_DATA_SET", "appengine_logs")
.put("GKE_LOGS_DATA_SET", "gke_logs")
.put("REQUEST_TABLE", "appengine_googleapis_com_request_log_")
.put("FIRST_DAY_OF_MONTH", logTableFormatter.print(firstDayOfMonth))
.put("LAST_DAY_OF_MONTH", logTableFormatter.print(lastDayOfMonth))
@@ -93,9 +94,10 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
queriesBuilder.put(getTableName(MONTHLY_LOGS, yearMonth), monthlyLogsQuery);
String eppQuery =
SqlTemplate.create(getQueryFromFile("epp_metrics.sql"))
SqlTemplate.create(getQueryFromFile(EPP_METRICS + ".sql"))
.put("PROJECT_ID", projectId)
.put("APPENGINE_LOGS_DATA_SET", "appengine_logs")
.put("GKE_LOGS_DATA_SET", "gke_logs")
.put("APP_LOGS_TABLE", "_var_log_app_")
.put("FIRST_DAY_OF_MONTH", logTableFormatter.print(firstDayOfMonth))
.put("LAST_DAY_OF_MONTH", logTableFormatter.print(lastDayOfMonth))
@@ -103,7 +105,7 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
queriesBuilder.put(getTableName(EPP_METRICS, yearMonth), eppQuery);
String whoisQuery =
SqlTemplate.create(getQueryFromFile("whois_counts.sql"))
SqlTemplate.create(getQueryFromFile(WHOIS_COUNTS + ".sql"))
.put("PROJECT_ID", projectId)
.put("ICANN_REPORTING_DATA_SET", icannReportingDataSet)
.put("MONTHLY_LOGS_TABLE", getTableName(MONTHLY_LOGS, yearMonth))
@@ -111,7 +113,7 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
queriesBuilder.put(getTableName(WHOIS_COUNTS, yearMonth), whoisQuery);
SqlTemplate aggregateQuery =
SqlTemplate.create(getQueryFromFile("cloud_sql_activity_report_aggregation.sql"))
SqlTemplate.create(getQueryFromFile(ACTIVITY_REPORT_AGGREGATION + ".sql"))
.put("PROJECT_ID", projectId)
.put(
"REGISTRAR_OPERATING_STATUS_TABLE",

View File

@@ -71,27 +71,27 @@ public final class TransactionsReportingQueryBuilder implements QueryBuilder {
ImmutableMap.Builder<String, String> queriesBuilder = ImmutableMap.builder();
String registrarIanaIdQuery =
SqlTemplate.create(getQueryFromFile("cloud_sql_registrar_iana_id.sql"))
SqlTemplate.create(getQueryFromFile(REGISTRAR_IANA_ID + ".sql"))
.put("PROJECT_ID", projectId)
.build();
queriesBuilder.put(getTableName(REGISTRAR_IANA_ID, yearMonth), registrarIanaIdQuery);
String totalDomainsQuery =
SqlTemplate.create(getQueryFromFile("cloud_sql_total_domains.sql"))
SqlTemplate.create(getQueryFromFile(TOTAL_DOMAINS + ".sql"))
.put("PROJECT_ID", projectId)
.build();
queriesBuilder.put(getTableName(TOTAL_DOMAINS, yearMonth), totalDomainsQuery);
DateTimeFormatter timestampFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS");
String totalNameserversQuery =
SqlTemplate.create(getQueryFromFile("cloud_sql_total_nameservers.sql"))
SqlTemplate.create(getQueryFromFile(TOTAL_NAMESERVERS + ".sql"))
.put("PROJECT_ID", projectId)
.put("LATEST_REPORT_TIME", timestampFormatter.print(latestReportTime))
.build();
queriesBuilder.put(getTableName(TOTAL_NAMESERVERS, yearMonth), totalNameserversQuery);
String transactionCountsQuery =
SqlTemplate.create(getQueryFromFile("cloud_sql_transaction_counts.sql"))
SqlTemplate.create(getQueryFromFile(TRANSACTION_COUNTS + ".sql"))
.put("PROJECT_ID", projectId)
.put("EARLIEST_REPORT_TIME", timestampFormatter.print(earliestReportTime))
.put("LATEST_REPORT_TIME", timestampFormatter.print(latestReportTime))
@@ -99,7 +99,7 @@ public final class TransactionsReportingQueryBuilder implements QueryBuilder {
queriesBuilder.put(getTableName(TRANSACTION_COUNTS, yearMonth), transactionCountsQuery);
String transactionTransferLosingQuery =
SqlTemplate.create(getQueryFromFile("cloud_sql_transaction_transfer_losing.sql"))
SqlTemplate.create(getQueryFromFile(TRANSACTION_TRANSFER_LOSING + ".sql"))
.put("PROJECT_ID", projectId)
.put("EARLIEST_REPORT_TIME", timestampFormatter.print(earliestReportTime))
.put("LATEST_REPORT_TIME", timestampFormatter.print(latestReportTime))
@@ -110,9 +110,10 @@ public final class TransactionsReportingQueryBuilder implements QueryBuilder {
// App Engine log table suffixes use YYYYMMDD format
DateTimeFormatter logTableFormatter = DateTimeFormat.forPattern("yyyyMMdd");
String attemptedAddsQuery =
SqlTemplate.create(getQueryFromFile("cloud_sql_attempted_adds.sql"))
SqlTemplate.create(getQueryFromFile(ATTEMPTED_ADDS + ".sql"))
.put("PROJECT_ID", projectId)
.put("APPENGINE_LOGS_DATA_SET", "appengine_logs")
.put("GKE_LOGS_DATA_SET", "gke_logs")
.put("APP_LOGS_TABLE", "_var_log_app_")
.put("FIRST_DAY_OF_MONTH", logTableFormatter.print(earliestReportTime))
.put("LAST_DAY_OF_MONTH", logTableFormatter.print(latestReportTime))
@@ -120,7 +121,7 @@ public final class TransactionsReportingQueryBuilder implements QueryBuilder {
queriesBuilder.put(getTableName(ATTEMPTED_ADDS, yearMonth), attemptedAddsQuery);
String aggregateQuery =
SqlTemplate.create(getQueryFromFile("cloud_sql_transactions_report_aggregation.sql"))
SqlTemplate.create(getQueryFromFile(TRANSACTIONS_REPORT_AGGREGATION + ".sql"))
.put("PROJECT_ID", projectId)
.put("ICANN_REPORTING_DATA_SET", icannReportingDataSet)
.put("REGISTRAR_IANA_ID_TABLE", getTableName(REGISTRAR_IANA_ID, yearMonth))

View File

@@ -151,17 +151,9 @@ public final class RequestModule {
* query string.
*/
@Provides
@FullServletPath
static String provideFullServletPath(HttpServletRequest req) {
// Include the port only if it differs from the default for the scheme.
if (("http".equals(req.getScheme()) && (req.getServerPort() == 80))
|| ("https".equals(req.getScheme()) && (req.getServerPort() == 443))) {
return String.format("%s://%s%s", req.getScheme(), req.getServerName(), req.getServletPath());
} else {
return String.format(
"%s://%s:%d%s",
req.getScheme(), req.getServerName(), req.getServerPort(), req.getServletPath());
}
@RequestServerName
static String provideServerName(HttpServletRequest req) {
return req.getServerName();
}
@Provides

View File

@@ -20,11 +20,11 @@ import java.lang.annotation.RetentionPolicy;
import javax.inject.Qualifier;
/**
* Dagger qualifier for the HTTP servlet path, prepended with scheme, host and port.
* Dagger qualifier for the server name of the HTTP request.
*
* <p>See {@link jakarta.servlet.http.HttpServletRequest#getServletPath}
* <p>See {@link jakarta.servlet.http.HttpServletRequest#getServerName()}
*/
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface FullServletPath {}
public @interface RequestServerName {}

View File

@@ -106,7 +106,7 @@ public abstract class OidcTokenAuthenticationMechanism implements Authentication
String email = (String) token.getPayload().get("email");
if (email == null) {
logger.atWarning().log("No email address from the OIDC token:\n%s", token.getPayload());
logger.atInfo().log("No email address from the OIDC token:\n%s", token.getPayload());
return AuthResult.NOT_AUTHENTICATED;
}
Optional<User> maybeUser =

View File

@@ -25,7 +25,6 @@ import com.beust.jcommander.Parameters;
import com.beust.jcommander.ParametersDelegate;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.persistence.transaction.TransactionManagerFactory;
@@ -42,10 +41,6 @@ import org.postgresql.util.PSQLException;
@Parameters(separators = " =", commandDescription = "Command-line interface to the registry")
final class RegistryCli implements CommandRunner {
private static final ImmutableSet<RegistryToolEnvironment> DEFAULT_GKE_ENVIRONMENTS =
ImmutableSet.of(
RegistryToolEnvironment.ALPHA, RegistryToolEnvironment.CRASH, RegistryToolEnvironment.QA);
// The environment parameter is parsed twice: once here, and once with {@link
// RegistryToolEnvironment#parseFromArgs} in the {@link RegistryTool#main} function.
//
@@ -75,9 +70,6 @@ final class RegistryCli implements CommandRunner {
+ "Beam pipelines")
private String sqlAccessInfoFile = null;
@Parameter(names = "--gke", description = "Whether to use GKE runtime, instead of GAE")
private boolean useGke = false;
@Parameter(names = "--gae", description = "Whether to use GAE runtime, instead of GKE")
private boolean useGae = false;
@@ -158,12 +150,6 @@ final class RegistryCli implements CommandRunner {
throw e;
}
checkState(!useGke || !useGae, "Cannot specify both --gke and --gae");
// Special logic to set the default based on the environment if neither --gae nor --gke is set.
if (!useGke && !useGae) {
useGke = DEFAULT_GKE_ENVIRONMENTS.contains(environment);
}
String parsedCommand = jcommander.getParsedCommand();
// Show the list of all commands either if requested or if no subcommand name was specified
// (which does not throw a ParameterException parse error above).
@@ -183,7 +169,7 @@ final class RegistryCli implements CommandRunner {
DaggerRegistryToolComponent.builder()
.credentialFilePath(credentialJson)
.sqlAccessInfoFile(sqlAccessInfoFile)
.useGke(useGke)
.useGke(!useGae)
.useCanary(useCanary)
.build();

View File

@@ -31,12 +31,14 @@ import com.google.api.services.directory.Directory;
import com.google.api.services.directory.model.UserName;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gson.annotations.Expose;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.console.ConsolePermission;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
import google.registry.model.registrar.Registrar;
import google.registry.persistence.VKey;
import google.registry.request.Action;
import google.registry.request.Action.GkeService;
@@ -44,6 +46,7 @@ import google.registry.request.HttpException.BadRequestException;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.tools.IamClient;
import google.registry.util.DiffUtils;
import google.registry.util.StringGenerator;
import java.io.IOException;
import java.util.List;
@@ -96,24 +99,14 @@ public class ConsoleUsersAction extends ConsoleApiAction {
@Override
protected void postHandler(User user) {
// Temporary flag while testing
if (user.getUserRoles().isAdmin()) {
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
tm().transact(this::runPostInTransaction);
} else {
consoleApiParams.response().setStatus(SC_FORBIDDEN);
}
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
tm().transact(this::runPostInTransaction);
}
@Override
protected void putHandler(User user) {
// Temporary flag while testing
if (user.getUserRoles().isAdmin()) {
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
tm().transact(this::runUpdateInTransaction);
} else {
consoleApiParams.response().setStatus(SC_FORBIDDEN);
}
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
tm().transact(this::runUpdateInTransaction);
}
@Override
@@ -135,13 +128,8 @@ public class ConsoleUsersAction extends ConsoleApiAction {
@Override
protected void deleteHandler(User user) {
// Temporary flag while testing
if (user.getUserRoles().isAdmin()) {
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
tm().transact(this::runDeleteInTransaction);
} else {
consoleApiParams.response().setStatus(SC_FORBIDDEN);
}
checkPermission(user, registrarId, ConsolePermission.MANAGE_USERS);
tm().transact(this::runDeleteInTransaction);
}
private void runPostInTransaction() throws IOException {
@@ -163,6 +151,8 @@ public class ConsoleUsersAction extends ConsoleApiAction {
this.userData.get().emailAddress,
registrarId,
RegistrarRole.valueOf(this.userData.get().role));
sendConfirmationEmail(registrarId, this.userData.get().emailAddress, "Added existing user");
consoleApiParams.response().setStatus(SC_OK);
}
@@ -186,6 +176,7 @@ public class ConsoleUsersAction extends ConsoleApiAction {
VKey<User> key = VKey.create(User.class, email);
tm().delete(key);
User.revokeIapPermission(email, maybeGroupEmailAddress, cloudTasksUtils, null, iamClient);
sendConfirmationEmail(registrarId, email, "Deleted user");
}
consoleApiParams.response().setStatus(SC_OK);
@@ -225,13 +216,14 @@ public class ConsoleUsersAction extends ConsoleApiAction {
UserRoles userRoles =
new UserRoles.Builder()
.setRegistrarRoles(ImmutableMap.of(registrarId, ACCOUNT_MANAGER))
.setRegistrarRoles(
ImmutableMap.of(registrarId, RegistrarRole.valueOf(userData.get().role)))
.build();
User.Builder builder = new User.Builder().setUserRoles(userRoles).setEmailAddress(newEmail);
tm().put(builder.build());
User.grantIapPermission(newEmail, maybeGroupEmailAddress, cloudTasksUtils, null, iamClient);
sendConfirmationEmail(registrarId, newEmail, "Created user");
consoleApiParams.response().setStatus(SC_CREATED);
consoleApiParams
.response()
@@ -250,6 +242,8 @@ public class ConsoleUsersAction extends ConsoleApiAction {
this.userData.get().emailAddress,
registrarId,
RegistrarRole.valueOf(this.userData.get().role));
sendConfirmationEmail(registrarId, this.userData.get().emailAddress, "Updated user");
consoleApiParams.response().setStatus(SC_OK);
}
@@ -314,6 +308,20 @@ public class ConsoleUsersAction extends ConsoleApiAction {
.collect(toImmutableList()));
}
private boolean sendConfirmationEmail(String registrarId, String emailAddress, String operation) {
Optional<Registrar> registrar = Registrar.loadByRegistrarId(registrarId);
if (registrar.isEmpty()) { // Shouldn't happen, but worth checking
setFailedResponse(
"Failed to send an email to registrar " + registrarId, SC_INTERNAL_SERVER_ERROR);
return false;
}
sendExternalUpdates(
ImmutableMap.of("Console users updated", new DiffUtils.DiffPair(operation, emailAddress)),
registrar.get(),
ImmutableSet.of());
return true;
}
public record UserData(
@Expose String emailAddress, @Expose String role, @Expose @Nullable String password) {}
}

View File

@@ -15,7 +15,6 @@
package google.registry.ui.server.console.domains;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -81,11 +80,6 @@ public class ConsoleBulkDomainAction extends ConsoleApiAction {
@Override
protected void postHandler(User user) {
// Temporary flag while testing
if (!user.getUserRoles().isAdmin()) {
consoleApiParams.response().setStatus(SC_FORBIDDEN);
return;
}
JsonElement jsonPayload =
optionalJsonPayload.orElseThrow(
() -> new IllegalArgumentException("Bulk action payload must be present"));

View File

@@ -0,0 +1,71 @@
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.ui.server.console.domains;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonElement;
import google.registry.model.console.ConsolePermission;
/** An action that will unsuspend the given domain, removing all 5 server*Prohibited statuses. */
public class ConsoleBulkDomainUnsuspendActionType implements ConsoleDomainActionType {
private static final String DOMAIN_SUSPEND_XML =
"""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp
xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>%DOMAIN_NAME%</domain:name>
<domain:add></domain:add>
<domain:rem>
<domain:status s="serverDeleteProhibited" lang="en"></domain:status>
<domain:status s="serverHold" lang="en"></domain:status>
<domain:status s="serverRenewProhibited" lang="en"></domain:status>
<domain:status s="serverTransferProhibited" lang="en"></domain:status>
<domain:status s="serverUpdateProhibited" lang="en"></domain:status>
</domain:rem>
</domain:update>
</update>
<extension>
<metadata:metadata
xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
<metadata:reason>Console unsuspension: %REASON%</metadata:reason>
<metadata:requestedByRegistrar>false</metadata:requestedByRegistrar>
</metadata:metadata>
</extension>
<clTRID>RegistryConsole</clTRID>
</command>
</epp>""";
private final String reason;
public ConsoleBulkDomainUnsuspendActionType(JsonElement jsonElement) {
this.reason = jsonElement.getAsJsonObject().get("reason").getAsString();
}
@Override
public String getXmlContentsToRun(String domainName) {
return ConsoleDomainActionType.fillSubstitutions(
DOMAIN_SUSPEND_XML, ImmutableMap.of("DOMAIN_NAME", domainName, "REASON", reason));
}
@Override
public ConsolePermission getNecessaryPermission() {
return ConsolePermission.SUSPEND_DOMAIN;
}
}

View File

@@ -31,7 +31,8 @@ public interface ConsoleDomainActionType {
enum BulkAction {
DELETE(ConsoleBulkDomainDeleteActionType.class),
SUSPEND(ConsoleBulkDomainSuspendActionType.class);
SUSPEND(ConsoleBulkDomainSuspendActionType.class),
UNSUSPEND(ConsoleBulkDomainUnsuspendActionType.class);
private final Class<? extends ConsoleDomainActionType> actionClass;

View File

@@ -19,7 +19,7 @@
-- monthly App Engine logs, searching for all create commands and associating
-- them with their corresponding registrars.
-- Example log generated by FlowReporter in App Engine logs:
-- Example log generated by FlowReporter in App Engine and GKE logs:
--google.registry.flows.FlowReporter
-- recordToLogs: FLOW-LOG-SIGNATURE-METADATA:
--{"serverTrid":"oNwL2J2eRya7bh7c9oHIzg==-2360a","clientId":"ipmirror"
@@ -38,30 +38,42 @@ FROM (
JSON_EXTRACT_SCALAR(json, '$.tld') AS tld,
JSON_EXTRACT_SCALAR(json, '$.clientId') AS clientId,
COUNT(json) AS count
FROM (
FROM ((
-- Extract JSON metadata package from monthly logs
SELECT
REGEXP_EXTRACT(textPayload, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
AS json
FROM (
REGEXP_EXTRACT(textPayload, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$') AS json
FROM
`%PROJECT_ID%.%APPENGINE_LOGS_DATA_SET%.%APP_LOGS_TABLE%*`
WHERE
_TABLE_SUFFIX BETWEEN '%FIRST_DAY_OF_MONTH%'
AND '%LAST_DAY_OF_MONTH%'
AND STARTS_WITH(textPayload, "FLOW-LOG-SIGNATURE-METADATA")
-- Look for domain creates
AND REGEXP_CONTAINS(textPayload, r'"commandType":"create","resourceType":"domain"')
-- Filter prober data
AND NOT REGEXP_CONTAINS(textPayload, r'"prober-[a-z]{2}-((any)|(canary))"'))
UNION ALL (
-- Extract JSON metadata package from monthly logs
SELECT
textPayload
REGEXP_EXTRACT(jsonPayload.message, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$') AS json
FROM
`%PROJECT_ID%.%APPENGINE_LOGS_DATA_SET%.%APP_LOGS_TABLE%*`
WHERE _TABLE_SUFFIX
BETWEEN '%FIRST_DAY_OF_MONTH%'
AND '%LAST_DAY_OF_MONTH%')
WHERE STARTS_WITH(textPayload, "FLOW-LOG-SIGNATURE-METADATA")
-- Look for domain creates
AND REGEXP_CONTAINS(
textPayload, r'"commandType":"create","resourceType":"domain"')
-- Filter prober data
AND NOT REGEXP_CONTAINS(
textPayload, r'"prober-[a-z]{2}-((any)|(canary))"') )
GROUP BY tld, clientId ) AS logs_table
`%PROJECT_ID%.%GKE_LOGS_DATA_SET%.stderr_*`
WHERE
_TABLE_SUFFIX BETWEEN '%FIRST_DAY_OF_MONTH%'
AND '%LAST_DAY_OF_MONTH%'
AND STARTS_WITH(jsonPayload.message, "FLOW-LOG-SIGNATURE-METADATA")
-- Look for domain creates
AND REGEXP_CONTAINS(jsonPayload.message, r'"commandType":"create","resourceType":"domain"')
-- Filter prober data
AND NOT REGEXP_CONTAINS(jsonPayload.message, r'"prober-[a-z]{2}-((any)|(canary))"')))
GROUP BY
tld,
clientId ) AS logs_table
JOIN
EXTERNAL_QUERY("projects/%PROJECT_ID%/locations/us/connections/%PROJECT_ID%-sql",
'''SELECT registrar_id, registrar_name FROM "Registrar";''') AS registrar_table
ON
logs_table.clientId = registrar_table.registrar_id
ORDER BY tld, registrar_name
'''SELECT registrar_id, registrar_name FROM "Registrar";''') AS registrar_table
ON
logs_table.clientId = registrar_table.registrar_id
ORDER BY
tld,
registrar_name

View File

@@ -35,14 +35,30 @@ FROM (
JSON_EXTRACT_SCALAR(json,
'$.icannActivityReportField') AS activityReportField
FROM (
-- For reasons that I don't understand, if I directly select the three columns
-- from the union, BigQuery complains about column number mismatch, so I have to
-- make a temporary union table and select on it.
SELECT
-- Extract the logged JSON payload.
REGEXP_EXTRACT(textPayload, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
AS json
FROM `%PROJECT_ID%.%APPENGINE_LOGS_DATA_SET%.%APP_LOGS_TABLE%*`
WHERE
STARTS_WITH(textPayload, "FLOW-LOG-SIGNATURE-METADATA")
AND _TABLE_SUFFIX BETWEEN '%FIRST_DAY_OF_MONTH%' AND '%LAST_DAY_OF_MONTH%')) AS regexes
*
FROM (
SELECT
-- Extract the logged JSON payload.
REGEXP_EXTRACT(textPayload, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
AS json
FROM `%PROJECT_ID%.%APPENGINE_LOGS_DATA_SET%.%APP_LOGS_TABLE%*`
WHERE
STARTS_WITH(textPayload, "FLOW-LOG-SIGNATURE-METADATA")
AND _TABLE_SUFFIX BETWEEN '%FIRST_DAY_OF_MONTH%' AND '%LAST_DAY_OF_MONTH%')
UNION ALL (
SELECT
-- Extract the logged JSON payload.
REGEXP_EXTRACT(jsonPayload.message, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
AS json
FROM `%PROJECT_ID%.%GKE_LOGS_DATA_SET%.stderr_*`
WHERE
STARTS_WITH(jsonPayload.message, "FLOW-LOG-SIGNATURE-METADATA")
AND _TABLE_SUFFIX BETWEEN '%FIRST_DAY_OF_MONTH%' AND '%LAST_DAY_OF_MONTH%')
)) AS regexes
JOIN
-- Unnest the JSON-parsed tlds.
UNNEST(regexes.tlds) AS tld

View File

@@ -13,13 +13,23 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
-- Query to fetch AppEngine request logs for the report month.
-- Query to fetch AppEngine and GKE request logs for the report month.
-- START_OF_MONTH and END_OF_MONTH should be in YYYYMM01 format.
SELECT
protoPayload.resource AS requestPath,
FROM
`%PROJECT_ID%.%APPENGINE_LOGS_DATA_SET%.%REQUEST_TABLE%*`
WHERE
_TABLE_SUFFIX BETWEEN '%FIRST_DAY_OF_MONTH%' AND '%LAST_DAY_OF_MONTH%'
*
FROM (
SELECT
jsonPayload.httrequest.requesturl AS requestPath
FROM
`%PROJECT_ID%.%GKE_LOGS_DATA_SET%.stderr_*`
WHERE
_TABLE_SUFFIX BETWEEN '%FIRST_DAY_OF_MONTH%' AND '%LAST_DAY_OF_MONTH%')
UNION ALL (
SELECT
protoPayload.resource AS requestPath
FROM
`%PROJECT_ID%.%APPENGINE_LOGS_DATA_SET%.%REQUEST_TABLE%*`
WHERE
_TABLE_SUFFIX BETWEEN '%FIRST_DAY_OF_MONTH%' AND '%LAST_DAY_OF_MONTH%')

View File

@@ -161,8 +161,6 @@ import google.registry.model.common.FeatureFlag;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.rgp.GracePeriodStatus;
@@ -287,59 +285,45 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistContactsAndHosts("net"); // domain_create.xml uses hosts on "net".
}
private void assertSuccessfulCreate(String domainTld, ImmutableSet<Flag> expectedBillingFlags)
throws Exception {
assertSuccessfulCreate(domainTld, expectedBillingFlags, null, 24, null);
}
private void assertSuccessfulCreate(
String domainTld, ImmutableSet<BillingBase.Flag> expectedBillingFlags) throws Exception {
assertSuccessfulCreate(domainTld, expectedBillingFlags, null);
String domainTld, ImmutableSet<Flag> expectedBillingFlags, double createCost)
throws Exception {
assertSuccessfulCreate(domainTld, expectedBillingFlags, null, createCost, null);
}
private void assertSuccessfulCreate(
String domainTld, ImmutableSet<Flag> expectedBillingFlags, AllocationToken token)
throws Exception {
assertSuccessfulCreate(domainTld, expectedBillingFlags, token, 24, null);
}
private void assertSuccessfulCreate(
String domainTld,
ImmutableSet<BillingBase.Flag> expectedBillingFlags,
@Nullable AllocationToken allocationToken)
ImmutableSet<Flag> expectedBillingFlags,
AllocationToken token,
double createCost)
throws Exception {
assertSuccessfulCreate(domainTld, expectedBillingFlags, token, createCost, null);
}
private void assertSuccessfulCreate(
String domainTld,
ImmutableSet<Flag> expectedBillingFlags,
@Nullable AllocationToken token,
double createCost,
@Nullable Integer specifiedRenewCost)
throws Exception {
Domain domain = reloadResourceByForeignKey();
boolean isAnchorTenant = expectedBillingFlags.contains(ANCHOR_TENANT);
// Set up the creation cost.
boolean isDomainPremium = isDomainPremium(getUniqueIdFromCommand(), clock.nowUtc());
BigDecimal createCost = isDomainPremium ? BigDecimal.valueOf(200) : BigDecimal.valueOf(24);
if (isAnchorTenant) {
createCost = BigDecimal.ZERO;
}
if (expectedBillingFlags.contains(SUNRISE)) {
createCost =
createCost.multiply(
BigDecimal.valueOf(1 - RegistryConfig.getSunriseDomainCreateDiscount()));
}
if (allocationToken != null) {
if (allocationToken
.getRegistrationBehavior()
.equals(RegistrationBehavior.NONPREMIUM_CREATE)) {
createCost =
createCost.subtract(
BigDecimal.valueOf(isDomainPremium ? 87 : 0)); // premium is 100, standard 13
}
if (allocationToken.getRenewalPriceBehavior().equals(NONPREMIUM)) {
createCost =
createCost.subtract(
BigDecimal.valueOf(isDomainPremium ? 89 : 0)); // premium is 100, standard 11
}
if (allocationToken.getRenewalPriceBehavior().equals(SPECIFIED)) {
createCost =
createCost
.subtract(BigDecimal.valueOf(isDomainPremium ? 100 : 11))
.add(allocationToken.getRenewalPrice().get().getAmount());
}
}
FeesAndCredits feesAndCredits =
new FeesAndCredits.Builder()
.setCurrency(USD)
.addFeeOrCredit(
Fee.create(
createCost,
FeeType.CREATE,
isDomainPremium(getUniqueIdFromCommand(), clock.nowUtc())))
.build();
Money eapFee =
Money.of(
Tld.get(domainTld).getCurrency(),
@@ -362,7 +346,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
RenewalPriceBehavior expectedRenewalPriceBehavior =
isAnchorTenant
? RenewalPriceBehavior.NONPREMIUM
: Optional.ofNullable(allocationToken)
: Optional.ofNullable(token)
.map(AllocationToken::getRenewalPriceBehavior)
.orElse(RenewalPriceBehavior.DEFAULT);
// There should be one bill for the create and one for the recurrence autorenew event.
@@ -371,13 +355,13 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.setReason(Reason.CREATE)
.setTargetId(getUniqueIdFromCommand())
.setRegistrarId("TheRegistrar")
.setCost(feesAndCredits.getCreateCost())
.setCost(Money.of(USD, BigDecimal.valueOf(createCost)))
.setPeriodYears(2)
.setEventTime(clock.nowUtc())
.setBillingTime(billingTime)
.setFlags(expectedBillingFlags)
.setDomainHistory(historyEntry)
.setAllocationToken(allocationToken == null ? null : allocationToken.createVKey())
.setAllocationToken(Optional.ofNullable(token).map(t -> t.createVKey()).orElse(null))
.build();
BillingRecurrence renewBillingEvent =
@@ -391,8 +375,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.setDomainHistory(historyEntry)
.setRenewalPriceBehavior(expectedRenewalPriceBehavior)
.setRenewalPrice(
Optional.ofNullable(allocationToken)
.flatMap(AllocationToken::getRenewalPrice)
Optional.ofNullable(specifiedRenewCost)
.map(r -> Money.of(USD, BigDecimal.valueOf(r)))
.orElse(null))
.build();
@@ -480,7 +464,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertMutatingFlow(true);
runFlowAssertResponse(
CommitMode.LIVE, userPrivileges, loadFile(responseXmlFile, substitutions));
assertSuccessfulCreate(domainTld, ImmutableSet.of());
assertSuccessfulCreate(domainTld, ImmutableSet.of(), 24);
assertNoLordn();
}
@@ -886,10 +870,14 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
clock.nowUtc().plusDays(1),
Money.of(USD, 0)))
.build());
doSuccessfulTest(
"example",
"domain_create_response_premium_eap.xml",
ImmutableMap.of("DOMAIN", "rich.example"));
assertMutatingFlow(true);
runFlowAssertResponse(
CommitMode.LIVE,
UserPrivileges.NORMAL,
loadFile(
"domain_create_response_premium_eap.xml", ImmutableMap.of("DOMAIN", "rich.example")));
assertSuccessfulCreate("example", ImmutableSet.of(), 200);
assertNoLordn();
}
/**
@@ -1336,7 +1324,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
setEppInput("domain_create_anchor_allocationtoken.xml");
persistContactsAndHosts();
runFlowAssertResponse(loadFile("domain_create_anchor_response.xml"));
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), allocationToken);
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), allocationToken, 0);
assertNoLordn();
assertAllocationTokenWasRedeemed("abcDEF23456");
}
@@ -1350,7 +1338,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.setTokenType(SINGLE_USE)
.setDomainName("resdom.tld")
.setRenewalPriceBehavior(SPECIFIED)
.setRenewalPrice(Money.of(USD, 0))
.setRenewalPrice(Money.of(USD, 1))
.build());
// Despite the domain being FULLY_BLOCKED, the non-superuser create succeeds the domain is also
// RESERVED_FOR_SPECIFIC_USE and the correct allocation token is passed.
@@ -1359,7 +1347,8 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistContactsAndHosts();
runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "resdom.tld")));
assertSuccessfulCreate("tld", ImmutableSet.of(RESERVED), allocationToken);
// $13 for the first year plus $1 renewal for the second year =
assertSuccessfulCreate("tld", ImmutableSet.of(RESERVED), allocationToken, 14, 1);
assertNoLordn();
assertAllocationTokenWasRedeemed("abc123");
}
@@ -1379,7 +1368,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
setEppInput("domain_create_anchor_allocationtoken.xml");
persistContactsAndHosts();
runFlowAssertResponse(loadFile("domain_create_anchor_response.xml"));
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), allocationToken);
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), allocationToken, 0);
assertNoLordn();
assertAllocationTokenWasRedeemed("abcDEF23456");
}
@@ -1402,7 +1391,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
clock.setTo(DateTime.parse("2009-08-16T09:00:00.0Z"));
persistContactsAndHosts();
runFlowAssertResponse(loadFile("domain_create_response_claims.xml"));
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), allocationToken);
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), allocationToken, 0);
assertDomainDnsRequests("example-one.tld");
assertClaimsLordn();
assertAllocationTokenWasRedeemed("abcDEF23456");
@@ -1415,7 +1404,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistContactsAndHosts();
runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "example.tld")));
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT));
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), 0);
assertNoLordn();
}
@@ -1450,7 +1439,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
SMD_VALID_TIME.toString(),
"EXPIRATION_TIME",
SMD_VALID_TIME.plusYears(2).toString())));
assertSuccessfulCreate("tld", ImmutableSet.of(SUNRISE, ANCHOR_TENANT));
assertSuccessfulCreate("tld", ImmutableSet.of(SUNRISE, ANCHOR_TENANT), 0);
}
@Test
@@ -1482,7 +1471,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
SMD_VALID_TIME.toString(),
"EXPIRATION_TIME",
SMD_VALID_TIME.plusYears(2).toString())));
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT, SUNRISE), allocationToken);
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT, SUNRISE), allocationToken, 0);
assertDomainDnsRequests("test-validate.tld");
assertSunriseLordn();
assertAllocationTokenWasRedeemed("abcDEF23456");
@@ -1505,7 +1494,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
setEppInput("domain_create_anchor_allocationtoken.xml");
persistContactsAndHosts();
runFlowAssertResponse(loadFile("domain_create_anchor_response.xml"));
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), allocationToken);
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), allocationToken, 0);
assertNoLordn();
assertAllocationTokenWasRedeemed("abcDEF23456");
}
@@ -1929,7 +1918,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
loadFile(
"domain_create_response_premium.xml",
ImmutableMap.of("EXDATE", "2001-04-03T22:00:00.0Z", "FEE", "200.00")));
assertSuccessfulCreate("example", ImmutableSet.of());
assertSuccessfulCreate("example", ImmutableSet.of(), 200);
}
private BillingEvent runTest_defaultToken(String token) throws Exception {
@@ -2148,7 +2137,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
loadFile(
"domain_create_response_premium.xml",
ImmutableMap.of("EXDATE", "2001-04-03T22:00:00.0Z", "FEE", "200.00")));
assertSuccessfulCreate("example", ImmutableSet.of());
assertSuccessfulCreate("example", ImmutableSet.of(), 200);
}
@Test
@@ -2572,7 +2561,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistContactsAndHosts();
persistBsaLabel("anchor");
runFlowAssertResponse(loadFile("domain_create_anchor_response.xml"));
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), allocationToken);
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), allocationToken, 0);
assertNoLordn();
assertAllocationTokenWasRedeemed("abcDEF23456");
}
@@ -2738,7 +2727,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
SMD_VALID_TIME.toString(),
"EXPIRATION_TIME",
SMD_VALID_TIME.plusYears(2).toString())));
assertSuccessfulCreate("tld", ImmutableSet.of(SUNRISE));
assertSuccessfulCreate("tld", ImmutableSet.of(SUNRISE), 20.40);
assertSunriseLordn();
}
@@ -2761,7 +2750,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
SMD_VALID_TIME.toString(),
"EXPIRATION_TIME",
SMD_VALID_TIME.plusYears(2).toString())));
assertSuccessfulCreate("tld", ImmutableSet.of(SUNRISE));
assertSuccessfulCreate("tld", ImmutableSet.of(SUNRISE), 20.40);
assertSunriseLordn();
}
@@ -3245,7 +3234,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
runFlow();
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), token);
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), token, 0);
}
@Test
@@ -3265,7 +3254,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"domain_create_premium_allocationtoken.xml",
ImmutableMap.of("YEARS", "2", "FEE", "111.00"));
runFlow();
assertSuccessfulCreate("example", ImmutableSet.of(), token);
assertSuccessfulCreate("example", ImmutableSet.of(), token, 111);
}
@Test
@@ -3286,7 +3275,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"domain_create_premium_allocationtoken.xml",
ImmutableMap.of("YEARS", "2", "FEE", "101.00"));
runFlow();
assertSuccessfulCreate("example", ImmutableSet.of(), token);
assertSuccessfulCreate("example", ImmutableSet.of(), token, 101, 1);
}
@Test
@@ -3438,7 +3427,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
persistContactsAndHosts();
runFlow();
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), token);
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), token, 0);
}
@Test
@@ -3492,7 +3481,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
runFlow();
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), token);
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), token, 0);
}
@Test
@@ -3510,7 +3499,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.build());
persistContactsAndHosts();
runFlow();
assertSuccessfulCreate("tld", ImmutableSet.of(SUNRISE, ANCHOR_TENANT), allocationToken);
assertSuccessfulCreate("tld", ImmutableSet.of(SUNRISE, ANCHOR_TENANT), allocationToken, 0);
}
@Test
@@ -3572,7 +3561,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
runFlow();
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), token);
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), token, 0);
}
@Test
@@ -3599,7 +3588,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
setEppInput("domain_create_allocationtoken_claims.xml");
persistContactsAndHosts();
runFlow();
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), allocationToken);
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), allocationToken, 0);
}
@Test
@@ -3649,7 +3638,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
runFlow();
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), token);
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), token, 0);
}
@Test
@@ -3663,7 +3652,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
setEppInput("domain_create_allocationtoken_claims.xml");
persistContactsAndHosts();
runFlow();
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), allocationToken);
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), allocationToken, 0);
}
@Test

View File

@@ -406,26 +406,6 @@ class RdapJsonFormatterTest {
.isEqualTo(loadJson("rdapjson_registrant_logged_out.json"));
}
@Test
void testRegistrant_baseHasNoTrailingSlash() {
// First, make sure we have a trailing slash at the end by default!
// This test tries to change the default state, if the default doesn't have a /, then this test
// doesn't help.
assertThat(rdapJsonFormatter.fullServletPath).endsWith("/");
rdapJsonFormatter.fullServletPath =
rdapJsonFormatter.fullServletPath.substring(
0, rdapJsonFormatter.fullServletPath.length() - 1);
assertAboutJson()
.that(
rdapJsonFormatter
.createRdapContactEntity(
contactRegistrant,
ImmutableSet.of(RdapEntity.Role.REGISTRANT),
OutputDataType.FULL)
.toJson())
.isEqualTo(loadJson("rdapjson_registrant.json"));
}
@Test
void testAdmin() {
assertAboutJson()

View File

@@ -150,7 +150,7 @@ class RdapTestHelper {
static RdapJsonFormatter getTestRdapJsonFormatter(Clock clock) {
RdapJsonFormatter rdapJsonFormatter = new RdapJsonFormatter();
rdapJsonFormatter.rdapAuthorization = RdapAuthorization.PUBLIC_AUTHORIZATION;
rdapJsonFormatter.fullServletPath = "https://example.tld/rdap/";
rdapJsonFormatter.serverName = "example.tld";
rdapJsonFormatter.clock = clock;
rdapJsonFormatter.rdapTos =
ImmutableList.of(

View File

@@ -30,6 +30,7 @@ import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
@@ -61,6 +62,14 @@ public class ConsoleBulkDomainActionTest {
private static final Gson GSON = GsonUtils.provideGson();
private static ImmutableSet<StatusValue> serverSuspensionStatuses =
ImmutableSet.of(
StatusValue.SERVER_RENEW_PROHIBITED,
StatusValue.SERVER_TRANSFER_PROHIBITED,
StatusValue.SERVER_UPDATE_PROHIBITED,
StatusValue.SERVER_DELETE_PROHIBITED,
StatusValue.SERVER_HOLD);
private final FakeClock clock = new FakeClock(DateTime.parse("2024-05-13T00:00:00.000Z"));
@RegisterExtension
@@ -135,12 +144,34 @@ public class ConsoleBulkDomainActionTest {
"""
{"example.tld":{"message":"Command completed successfully","responseCode":1000}}""");
assertThat(loadByEntity(domain).getStatusValues())
.containsAtLeast(
StatusValue.SERVER_RENEW_PROHIBITED,
StatusValue.SERVER_TRANSFER_PROHIBITED,
StatusValue.SERVER_UPDATE_PROHIBITED,
StatusValue.SERVER_DELETE_PROHIBITED,
StatusValue.SERVER_HOLD);
.containsAtLeastElementsIn(serverSuspensionStatuses);
}
@Test
void testSuccess_unsuspend() throws Exception {
User adminUser =
persistResource(
new User.Builder()
.setEmailAddress("email@email.com")
.setUserRoles(
new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).setIsAdmin(true).build())
.build());
persistResource(domain.asBuilder().addStatusValues(serverSuspensionStatuses).build());
ConsoleBulkDomainAction action =
createAction(
"UNSUSPEND",
GSON.toJsonTree(
ImmutableMap.of("domainList", ImmutableList.of("example.tld"), "reason", "test")),
adminUser);
assertThat(loadByEntity(domain).getStatusValues())
.containsAtLeastElementsIn(serverSuspensionStatuses);
action.run();
assertThat(fakeResponse.getStatus()).isEqualTo(SC_OK);
assertThat(fakeResponse.getPayload())
.isEqualTo(
"""
{"example.tld":{"message":"Command completed successfully","responseCode":1000}}""");
assertThat(loadByEntity(domain).getStatusValues()).containsNoneIn(serverSuspensionStatuses);
}
@Test

View File

@@ -19,7 +19,7 @@
-- monthly App Engine logs, searching for all create commands and associating
-- them with their corresponding registrars.
-- Example log generated by FlowReporter in App Engine logs:
-- Example log generated by FlowReporter in App Engine and GKE logs:
--google.registry.flows.FlowReporter
-- recordToLogs: FLOW-LOG-SIGNATURE-METADATA:
--{"serverTrid":"oNwL2J2eRya7bh7c9oHIzg==-2360a","clientId":"ipmirror"
@@ -38,30 +38,42 @@ FROM (
JSON_EXTRACT_SCALAR(json, '$.tld') AS tld,
JSON_EXTRACT_SCALAR(json, '$.clientId') AS clientId,
COUNT(json) AS count
FROM (
FROM ((
-- Extract JSON metadata package from monthly logs
SELECT
REGEXP_EXTRACT(textPayload, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
AS json
FROM (
REGEXP_EXTRACT(textPayload, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$') AS json
FROM
`domain-registry-alpha.appengine_logs._var_log_app_*`
WHERE
_TABLE_SUFFIX BETWEEN '20170901'
AND '20170930'
AND STARTS_WITH(textPayload, "FLOW-LOG-SIGNATURE-METADATA")
-- Look for domain creates
AND REGEXP_CONTAINS(textPayload, r'"commandType":"create","resourceType":"domain"')
-- Filter prober data
AND NOT REGEXP_CONTAINS(textPayload, r'"prober-[a-z]{2}-((any)|(canary))"'))
UNION ALL (
-- Extract JSON metadata package from monthly logs
SELECT
textPayload
REGEXP_EXTRACT(jsonPayload.message, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$') AS json
FROM
`domain-registry-alpha.appengine_logs._var_log_app_*`
WHERE _TABLE_SUFFIX
BETWEEN '20170901'
AND '20170930')
WHERE STARTS_WITH(textPayload, "FLOW-LOG-SIGNATURE-METADATA")
-- Look for domain creates
AND REGEXP_CONTAINS(
textPayload, r'"commandType":"create","resourceType":"domain"')
-- Filter prober data
AND NOT REGEXP_CONTAINS(
textPayload, r'"prober-[a-z]{2}-((any)|(canary))"') )
GROUP BY tld, clientId ) AS logs_table
`domain-registry-alpha.gke_logs.stderr_*`
WHERE
_TABLE_SUFFIX BETWEEN '20170901'
AND '20170930'
AND STARTS_WITH(jsonPayload.message, "FLOW-LOG-SIGNATURE-METADATA")
-- Look for domain creates
AND REGEXP_CONTAINS(jsonPayload.message, r'"commandType":"create","resourceType":"domain"')
-- Filter prober data
AND NOT REGEXP_CONTAINS(jsonPayload.message, r'"prober-[a-z]{2}-((any)|(canary))"')))
GROUP BY
tld,
clientId ) AS logs_table
JOIN
EXTERNAL_QUERY("projects/domain-registry-alpha/locations/us/connections/domain-registry-alpha-sql",
'''SELECT registrar_id, registrar_name FROM "Registrar";''') AS registrar_table
ON
logs_table.clientId = registrar_table.registrar_id
ORDER BY tld, registrar_name
'''SELECT registrar_id, registrar_name FROM "Registrar";''') AS registrar_table
ON
logs_table.clientId = registrar_table.registrar_id
ORDER BY
tld,
registrar_name

View File

@@ -35,14 +35,30 @@ FROM (
JSON_EXTRACT_SCALAR(json,
'$.icannActivityReportField') AS activityReportField
FROM (
-- For reasons that I don't understand, if I directly select the three columns
-- from the union, BigQuery complains about column number mismatch, so I have to
-- make a temporary union table and select on it.
SELECT
-- Extract the logged JSON payload.
REGEXP_EXTRACT(textPayload, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
AS json
FROM `domain-registry-alpha.appengine_logs._var_log_app_*`
WHERE
STARTS_WITH(textPayload, "FLOW-LOG-SIGNATURE-METADATA")
AND _TABLE_SUFFIX BETWEEN '20170901' AND '20170930')) AS regexes
*
FROM (
SELECT
-- Extract the logged JSON payload.
REGEXP_EXTRACT(textPayload, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
AS json
FROM `domain-registry-alpha.appengine_logs._var_log_app_*`
WHERE
STARTS_WITH(textPayload, "FLOW-LOG-SIGNATURE-METADATA")
AND _TABLE_SUFFIX BETWEEN '20170901' AND '20170930')
UNION ALL (
SELECT
-- Extract the logged JSON payload.
REGEXP_EXTRACT(jsonPayload.message, r'FLOW-LOG-SIGNATURE-METADATA: (.*)\n?$')
AS json
FROM `domain-registry-alpha.gke_logs.stderr_*`
WHERE
STARTS_WITH(jsonPayload.message, "FLOW-LOG-SIGNATURE-METADATA")
AND _TABLE_SUFFIX BETWEEN '20170901' AND '20170930')
)) AS regexes
JOIN
-- Unnest the JSON-parsed tlds.
UNNEST(regexes.tlds) AS tld

View File

@@ -13,13 +13,23 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
-- Query to fetch AppEngine request logs for the report month.
-- Query to fetch AppEngine and GKE request logs for the report month.
-- START_OF_MONTH and END_OF_MONTH should be in YYYYMM01 format.
SELECT
protoPayload.resource AS requestPath,
FROM
`domain-registry-alpha.appengine_logs.appengine_googleapis_com_request_log_*`
WHERE
_TABLE_SUFFIX BETWEEN '20170901' AND '20170930'
*
FROM (
SELECT
jsonPayload.httrequest.requesturl AS requestPath
FROM
`domain-registry-alpha.gke_logs.stderr_*`
WHERE
_TABLE_SUFFIX BETWEEN '20170901' AND '20170930')
UNION ALL (
SELECT
protoPayload.resource AS requestPath
FROM
`domain-registry-alpha.appengine_logs.appengine_googleapis_com_request_log_*`
WHERE
_TABLE_SUFFIX BETWEEN '20170901' AND '20170930')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 75 KiB

View File

@@ -23,7 +23,7 @@ public class NomulusPostgreSql {
/** The current PostgreSql version in Cloud SQL. */
// TODO(weiminyu): setup periodic checks to detect version changes in Cloud SQL.
private static final String TARGET_VERSION = "11.21-alpine";
private static final String TARGET_VERSION = "17-alpine";
/**
* Returns the docker image of the targeted Postgresql server version.

View File

@@ -257,11 +257,11 @@ td.section {
<tbody>
<tr>
<td class="property_name">generated by</td>
<td class="property_value">SchemaCrawler 16.23.2</td>
<td class="property_value">SchemaCrawler 16.25.2</td>
</tr>
<tr>
<td class="property_name">generated on</td>
<td class="property_value">2024-12-09 22:31:09</td>
<td class="property_value">2025-02-16 20:10:13</td>
</tr>
<tr>
<td class="property_name">last flyway file</td>
@@ -278,9 +278,9 @@ td.section {
</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-3577.64 4860,-3577.64 4860,4 -4,4" />
<text text-anchor="start" x="4616" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated by</text>
<text text-anchor="start" x="4699" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 16.23.2</text>
<text text-anchor="start" x="4699" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 16.25.2</text>
<text text-anchor="start" x="4615" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated on</text>
<text text-anchor="start" x="4699" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">2024-12-09 22:31:09</text>
<text text-anchor="start" x="4699" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">2025-02-16 20:10:13</text>
<polygon fill="none" stroke="#888888" points="4612,-4 4612,-44 4848,-44 4848,-4 4612,-4" /> <!-- allocationtoken_a08ccbef -->
<g id="node1" class="node">
<title>
@@ -2716,6 +2716,11 @@ td.section {
<td class="minwidth">recurrence_last_expansion</td>
<td class="minwidth">timestamptz not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default '2021-05-31 20:00:00-04'::timestamp with time zone</td>
</tr>
<tr>
<td colspan="3"></td>
</tr>
@@ -2825,6 +2830,11 @@ td.section {
<td class="minwidth"><b><i>job_id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"BsaDomainRefresh_job_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -2861,6 +2871,11 @@ td.section {
<td class="minwidth"><b><i>job_id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"BsaDownload_job_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -3069,6 +3084,11 @@ td.section {
<td class="minwidth"><b><i>revision_id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"ClaimsList_revision_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -3639,6 +3659,11 @@ td.section {
<td class="minwidth"><b><i>id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"DnsRefreshRequest_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -3785,6 +3810,11 @@ td.section {
<td class="minwidth">lordn_phase</td>
<td class="minwidth">text not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default 'NONE'::text</td>
</tr>
<tr>
<td colspan="3"></td>
</tr>
@@ -4334,6 +4364,11 @@ td.section {
<td class="minwidth"><b><i>id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"DomainTransactionRecord_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -4895,6 +4930,11 @@ td.section {
<td class="minwidth"><b><i>package_promotion_id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"Package_promotion_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -5188,6 +5228,11 @@ td.section {
<td class="minwidth"><b><i>revision_id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"PremiumList_revision_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -5873,6 +5918,11 @@ td.section {
<td class="minwidth"><b><i>revision_id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"RegistryLock_revision_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -6006,6 +6056,11 @@ td.section {
<td class="minwidth"><b><i>revision_id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"ReservedList_revision_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -6065,6 +6120,11 @@ td.section {
<td class="minwidth"><b><i>id</i></b></td>
<td class="minwidth">int8 not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default 1</td>
</tr>
<tr>
<td colspan="3"></td>
</tr>
@@ -6155,6 +6215,11 @@ td.section {
<td class="minwidth"><b><i>revision_id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"SignedMarkRevocationList_revision_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -6209,6 +6274,11 @@ td.section {
<td class="minwidth"><b><i>id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"SafeBrowsingThreat_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -6321,6 +6391,11 @@ td.section {
<td class="minwidth"><b><i>id</i></b></td>
<td class="minwidth">int8 not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default 1</td>
</tr>
<tr>
<td colspan="3"></td>
</tr>

View File

@@ -257,11 +257,11 @@ td.section {
<tbody>
<tr>
<td class="property_name">generated by</td>
<td class="property_value">SchemaCrawler 16.23.2</td>
<td class="property_value">SchemaCrawler 16.25.2</td>
</tr>
<tr>
<td class="property_name">generated on</td>
<td class="property_value">2024-12-09 22:31:07</td>
<td class="property_value">2025-02-16 20:10:10</td>
</tr>
<tr>
<td class="property_name">last flyway file</td>
@@ -278,9 +278,9 @@ td.section {
</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-7933.5 5565,-7933.5 5565,4 -4,4" />
<text text-anchor="start" x="5321" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated by</text>
<text text-anchor="start" x="5404" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 16.23.2</text>
<text text-anchor="start" x="5404" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 16.25.2</text>
<text text-anchor="start" x="5320" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated on</text>
<text text-anchor="start" x="5404" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">2024-12-09 22:31:07</text>
<text text-anchor="start" x="5404" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">2025-02-16 20:10:10</text>
<polygon fill="none" stroke="#888888" points="5317,-4 5317,-44 5553,-44 5553,-4 5317,-4" /> <!-- allocationtoken_a08ccbef -->
<g id="node1" class="node">
<title>
@@ -3986,11 +3986,21 @@ td.section {
<td class="minwidth">renewal_price_behavior</td>
<td class="minwidth">text not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default 'DEFAULT'::text</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">registration_behavior</td>
<td class="minwidth">text not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default 'DEFAULT'::text</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">allowed_epp_actions</td>
@@ -4755,6 +4765,11 @@ td.section {
<td class="minwidth">renewal_price_behavior</td>
<td class="minwidth">text not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default 'DEFAULT'::text</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">renewal_price_currency</td>
@@ -4770,6 +4785,11 @@ td.section {
<td class="minwidth">recurrence_last_expansion</td>
<td class="minwidth">timestamptz not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default '2021-05-31 20:00:00-04'::timestamp with time zone</td>
</tr>
<tr>
<td colspan="3"></td>
</tr>
@@ -4986,6 +5006,11 @@ td.section {
<td class="minwidth"><b><i>job_id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"BsaDomainRefresh_job_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -5055,6 +5080,11 @@ td.section {
<td class="minwidth"><b><i>job_id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"BsaDownload_job_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -5392,6 +5422,11 @@ td.section {
<td class="minwidth"><b><i>revision_id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"ClaimsList_revision_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -6866,6 +6901,11 @@ td.section {
<td class="minwidth"><b><i>id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"DnsRefreshRequest_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -7224,6 +7264,11 @@ td.section {
<td class="minwidth">lordn_phase</td>
<td class="minwidth">text not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default 'NONE'::text</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">last_update_time_via_epp</td>
@@ -8154,6 +8199,11 @@ td.section {
<td class="minwidth">lordn_phase</td>
<td class="minwidth">text not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default 'NONE'::text</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">last_update_time_via_epp</td>
@@ -8494,6 +8544,11 @@ td.section {
<td class="minwidth"><b><i>id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"DomainTransactionRecord_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -9622,6 +9677,11 @@ td.section {
<td class="minwidth"><b><i>package_promotion_id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"Package_promotion_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -10154,6 +10214,11 @@ td.section {
<td class="minwidth"><b><i>revision_id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"PremiumList_revision_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -11688,6 +11753,11 @@ td.section {
<td class="minwidth"><b><i>revision_id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"RegistryLock_revision_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -11958,6 +12028,11 @@ td.section {
<td class="minwidth"><b><i>revision_id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"ReservedList_revision_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -12057,6 +12132,11 @@ td.section {
<td class="minwidth"><b><i>id</i></b></td>
<td class="minwidth">int8 not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default 1</td>
</tr>
<tr>
<td colspan="3"></td>
</tr>
@@ -12193,6 +12273,11 @@ td.section {
<td class="minwidth"><b><i>revision_id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"SignedMarkRevocationList_revision_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -12270,6 +12355,11 @@ td.section {
<td class="minwidth"><b><i>id</i></b></td>
<td class="minwidth">bigserial not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default nextval('"SafeBrowsingThreat_id_seq"'::regclass)</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
@@ -12595,6 +12685,11 @@ td.section {
<td class="minwidth">breakglass_mode</td>
<td class="minwidth">bool not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default false</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">bsa_enroll_start_time</td>
@@ -12699,6 +12794,11 @@ td.section {
<td class="minwidth"><b><i>id</i></b></td>
<td class="minwidth">int8 not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth"></td>
<td class="minwidth">default 1</td>
</tr>
<tr>
<td colspan="3"></td>
</tr>

View File

@@ -2,12 +2,13 @@
-- PostgreSQL database dump
--
-- Dumped from database version 11.21
-- Dumped by pg_dump version 11.21
-- Dumped from database version 17.4
-- Dumped by pg_dump version 17.4
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET transaction_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
@@ -32,7 +33,7 @@ COMMENT ON EXTENSION hstore IS 'data type for storing sets of (key, value) pairs
SET default_tablespace = '';
SET default_with_oids = false;
SET default_table_access_method = heap;
--
-- Name: AllocationToken; Type: TABLE; Schema: public; Owner: -

View File

@@ -115,6 +115,7 @@ class SchemaTest {
Joiner.on(File.separatorChar).join(MOUNTED_RESOURCE_PATH, DUMP_OUTPUT_FILE));
assertThat(dumpedSchema)
.ignoringLinesStartingWith("--")
.hasSameContentAs(Resources.getResource("sql/schema/nomulus.golden.sql"));
}

View File

@@ -29,48 +29,48 @@ environment=${1}
base_domain=${2}
project="domain-registry-"${environment}
current_context=$(kubectl config current-context)
line=$(gcloud container clusters list --project "${project}" | grep nomulus | grep main)
parts=(${line})
echo "Updating cluster ${parts[0]} in location ${parts[1]}..."
gcloud container fleet memberships get-credentials "${parts[0]}" --project "${project}"
for service in frontend backend pubapi console
do
sed s/GCP_PROJECT/"${project}"/g "./kubernetes/nomulus-${service}.yaml" | \
sed s/ENVIRONMENT/"${environment}"/g | \
sed s/PROXY_ENV/"${environment}"/g | \
sed s/EPP/"epp"/g | \
kubectl apply -f -
kubectl rollout restart deployment/${service}
# canary
sed s/GCP_PROJECT/"${project}"/g "./kubernetes/nomulus-${service}.yaml" | \
sed s/ENVIRONMENT/"${environment}"/g | \
sed s/PROXY_ENV/"${environment}_canary"/g | \
sed s/EPP/"epp-canary"/g | \
sed s/"${service}"/"${service}-canary"/g | \
kubectl apply -f -
kubectl rollout restart deployment/${service}-canary
done
kubectl apply -f "./kubernetes/gateway/nomulus-gateway.yaml"
kubectl apply -f "./kubernetes/gateway/nomulus-iap-${environment}.yaml"
for service in frontend backend console pubapi
do
sed s/BASE_DOMAIN/"${base_domain}"/g "./kubernetes/gateway/nomulus-route-${service}.yaml" | \
kubectl apply -f -
sed s/SERVICE/"${service}"/g "./kubernetes/gateway/nomulus-backend-policy-${environment}.yaml" | \
kubectl apply -f -
sed s/SERVICE/"${service}-canary"/g "./kubernetes/gateway/nomulus-backend-policy-${environment}.yaml" | \
kubectl apply -f -
done
# Restart proxies
while read line
do
parts=(${line})
echo "Updating cluster ${parts[0]} in location ${parts[1]}..."
gcloud container fleet memberships get-credentials "${parts[0]}" --project "${project}"
for service in frontend backend pubapi console
do
sed s/GCP_PROJECT/"${project}"/g "./kubernetes/nomulus-${service}.yaml" | \
sed s/ENVIRONMENT/"${environment}"/g | \
sed s/PROXY_ENV/"${environment}"/g | \
sed s/EPP/"epp"/g | \
sed s/WHOIS/"whois"/g | \
kubectl apply -f -
# canary
sed s/GCP_PROJECT/"${project}"/g "./kubernetes/nomulus-${service}.yaml" | \
sed s/ENVIRONMENT/"${environment}"/g | \
sed s/PROXY_ENV/"${environment}_canary"/g | \
sed s/EPP/"epp-canary"/g | \
sed s/WHOIS/"whois-canary"/g | \
sed s/"${service}"/"${service}-canary"/g | \
kubectl apply -f -
done
# Kills all running pods, new pods created will be pulling the new image.
kubectl delete pods --all
# The multi-cluster gateway is only deployed to one cluster (the one in the US).
if [[ "${parts[1]}" == us-* ]]
then
kubectl apply -f "./kubernetes/gateway/nomulus-gateway.yaml"
for service in frontend backend console pubapi
do
sed s/BASE_DOMAIN/"${base_domain}"/g "./kubernetes/gateway/nomulus-route-${service}.yaml" | \
kubectl apply -f -
# Don't enable IAP on pubapi.
if [[ "${service}" == pubapi ]]
then
continue
fi
sed s/SERVICE/"${service}"/g "./kubernetes/gateway/nomulus-iap-${environment}.yaml" | \
kubectl apply -f -
sed s/SERVICE/"${service}-canary"/g "./kubernetes/gateway/nomulus-iap-${environment}.yaml" | \
kubectl apply -f -
done
fi
done < <(gcloud container clusters list --project "${project}" | grep nomulus)
gcloud container clusters get-credentials ${parts[0]} \
--project ${project} --location ${parts[1]}
kubectl rollout restart deployment/proxy-deployment
kubectl rollout restart deployment/proxy-deployment-canary
done < <(gcloud container clusters list --project ${project} | grep proxy-cluster)
kubectl config use-context "$current_context"

View File

@@ -1,158 +0,0 @@
#! /bin/env python3
# Copyright 2024 The Nomulus Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
'''
A script that outputs the IP endpoints of various load balancers, to be run
after Nomulus is deployed.
'''
import ipaddress
import json
import subprocess
import sys
from dataclasses import dataclass
from ipaddress import IPv4Address
from ipaddress import IPv6Address
from operator import attrgetter
from operator import methodcaller
class PreserveContext:
def __enter__(self):
self._context = run_command('kubectl config current-context')
def __exit__(self, type, value, traceback):
run_command('kubectl config use-context ' + self._context)
class UseCluster(PreserveContext):
def __init__(self, cluster: str, region: str, project: str):
self._cluster = cluster
self._region = region
self._project = project
def __enter__(self):
super().__enter__()
cmd = (f'gcloud container fleet memberships get-credentials'
f' {self._cluster} --project {self._project}')
run_command(cmd)
def run_command(cmd: str, print_output=False) -> str:
proc = subprocess.run(cmd, text=True, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
if print_output:
print(proc.stdout)
return proc.stdout
def get_clusters(project: str) -> dict[str, str]:
cmd = f'gcloud container clusters list --project {project} --format=json'
content = json.loads(run_command(cmd))
res = {}
for item in content:
name = item['name']
region = item['location']
if not name.startswith('nomulus-cluster'):
continue
res[name] = region
return res
def get_endpoints(resource: str, service: str, jsonpath: str) -> list[
str]:
content = run_command(
f'kubectl get {resource}/{service} -o jsonpath={jsonpath}', )
return content.split()
def get_region_symbol(region: str) -> str:
if region.startswith('us'):
return 'amer'
if region.startswith('europe'):
return 'emea'
if region.startswith('asia'):
return 'apac'
return 'other'
@dataclass
class IP:
service: str
region: str
address: IPv4Address | IPv6Address
def is_ipv6(self) -> bool:
return self.address.version == 6
def __str__(self) -> str:
return f'{self.service} {self.region}: {self.address}'
def terraform_str(item) -> str:
res = ""
if (isinstance(item, dict)):
res += '{\n'
for key, value in item.items():
res += f'{key} = {terraform_str(value)}\n'
res += '}'
elif (isinstance(item, list)):
res += '['
for i, value in enumerate(item):
if i != 0:
res += ', '
res += terraform_str(value)
res += ']'
else:
res += f'"{item}"'
return res
if __name__ == '__main__':
if len(sys.argv) != 2:
raise ValueError('Usage: get-endpoints.py <project>')
project = sys.argv[1]
print(f'Project: {project}')
clusters = get_clusters(project)
ips = []
res = {}
for cluster, region in clusters.items():
with UseCluster(cluster, region, project):
for service in ['whois', 'whois-canary', 'epp', 'epp-canary']:
map_key = service.replace('-', '_')
for ip in get_endpoints('services', service,
'{.status.loadBalancer.ingress[*].ip}'):
ip = ipaddress.ip_address(ip)
if isinstance(ip, IPv4Address):
map_key_with_iptype = map_key + '_ipv4'
else:
map_key_with_iptype = map_key + '_ipv6'
if map_key_with_iptype not in res:
res[map_key_with_iptype] = {}
res[map_key_with_iptype][get_region_symbol(region)] = [ip]
ips.append(IP(service, get_region_symbol(region), ip))
if not region.startswith('us'):
continue
ip = get_endpoints('gateways.gateway.networking.k8s.io', 'nomulus',
'{.status.addresses[*].value}')[0]
print(f'nomulus: {ip}')
res['https_ip'] = ipaddress.ip_address(ip)
ips.sort(key=attrgetter('region'))
ips.sort(key=methodcaller('is_ipv6'))
ips.sort(key=attrgetter('service'))
for ip in ips:
print(ip)
print("Terraform friendly output:")
print(terraform_str(res))

View File

@@ -4,7 +4,18 @@ metadata:
name: nomulus
spec:
gatewayClassName: gke-l7-global-external-managed-mc
addresses:
- type: NamedAddress
value: nomulus-ipv4-address
- type: NamedAddress
value: nomulus-ipv6-address
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
kinds:
- kind: HTTPRoute
- name: https
protocol: HTTPS
port: 443
@@ -15,3 +26,19 @@ spec:
allowedRoutes:
kinds:
- kind: HTTPRoute
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: redirect
spec:
parentRefs:
- kind: Gateway
name: nomulus
sectionName: http
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https

View File

@@ -6,6 +6,7 @@ spec:
parentRefs:
- kind: Gateway
name: nomulus
sectionName: https
hostnames:
- "backend.BASE_DOMAIN"
rules:

View File

@@ -6,6 +6,7 @@ spec:
parentRefs:
- kind: Gateway
name: nomulus
sectionName: https
hostnames:
- "console.BASE_DOMAIN"
rules:

View File

@@ -6,6 +6,7 @@ spec:
parentRefs:
- kind: Gateway
name: nomulus
sectionName: https
hostnames:
- "frontend.BASE_DOMAIN"
rules:

View File

@@ -6,6 +6,7 @@ spec:
parentRefs:
- kind: Gateway
name: nomulus
sectionName: https
hostnames:
- "pubapi.BASE_DOMAIN"
rules:

View File

@@ -2,6 +2,8 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
annotations:
tag: "latest"
spec:
selector:
matchLabels:
@@ -12,6 +14,8 @@ spec:
service: backend
spec:
serviceAccountName: nomulus
nodeSelector:
cloud.google.com/compute-class: "Performance"
containers:
- name: backend
image: gcr.io/GCP_PROJECT/nomulus
@@ -20,13 +24,18 @@ spec:
name: http
resources:
requests:
cpu: "500m"
cpu: "100m"
memory: "512Mi"
args: [ENVIRONMENT]
env:
- name: POD_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: JETTY_WORKER_INSTANCE
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE_ID
valueFrom:
fieldRef:
@@ -43,8 +52,8 @@ spec:
apiVersion: apps/v1
kind: Deployment
name: backend
minReplicas: 1
maxReplicas: 20
minReplicas: 2
maxReplicas: 5
metrics:
- type: Resource
resource:

View File

@@ -2,6 +2,8 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: console
annotations:
tag: "latest"
spec:
selector:
matchLabels:
@@ -12,6 +14,8 @@ spec:
service: console
spec:
serviceAccountName: nomulus
nodeSelector:
cloud.google.com/compute-class: "Performance"
containers:
- name: console
image: gcr.io/GCP_PROJECT/nomulus
@@ -20,13 +24,18 @@ spec:
name: http
resources:
requests:
cpu: "500m"
cpu: "100m"
memory: "512Mi"
args: [ENVIRONMENT]
env:
- name: POD_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: JETTY_WORKER_INSTANCE
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE_ID
valueFrom:
fieldRef:
@@ -44,7 +53,7 @@ spec:
kind: Deployment
name: console
minReplicas: 1
maxReplicas: 20
maxReplicas: 5
metrics:
- type: Resource
resource:

View File

@@ -2,6 +2,8 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
annotations:
tag: "latest"
spec:
selector:
matchLabels:
@@ -12,6 +14,8 @@ spec:
service: frontend
spec:
serviceAccountName: nomulus
nodeSelector:
cloud.google.com/compute-class: "Performance"
containers:
- name: frontend
image: gcr.io/GCP_PROJECT/nomulus
@@ -20,13 +24,18 @@ spec:
name: http
resources:
requests:
cpu: "500m"
cpu: "100m"
memory: "512Mi"
args: [ENVIRONMENT]
env:
- name: POD_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: JETTY_WORKER_INSTANCE
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE_ID
valueFrom:
fieldRef:
@@ -40,7 +49,8 @@ spec:
name: epp
resources:
requests:
cpu: "500m"
cpu: "100m"
memory: "512Mi"
args: [--env, PROXY_ENV, --log, --local]
env:
- name: POD_ID
@@ -71,8 +81,8 @@ spec:
apiVersion: apps/v1
kind: Deployment
name: frontend
minReplicas: 1
maxReplicas: 20
minReplicas: 15
maxReplicas: 15
metrics:
- type: Resource
resource:
@@ -100,6 +110,7 @@ metadata:
annotations:
cloud.google.com/l4-rbs: enabled
networking.gke.io/weighted-load-balancing: pods-per-node
networking.gke.io/load-balancer-ip-addresses: "EPP-ipv6-main,EPP-ipv4-main"
spec:
type: LoadBalancer
# Traffic is directly delivered to a node, preserving the original source IP.

View File

@@ -2,6 +2,8 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: pubapi
annotations:
tag: "latest"
spec:
selector:
matchLabels:
@@ -12,6 +14,8 @@ spec:
service: pubapi
spec:
serviceAccountName: nomulus
nodeSelector:
cloud.google.com/compute-class: "Performance"
containers:
- name: pubapi
image: gcr.io/GCP_PROJECT/nomulus
@@ -20,43 +24,24 @@ spec:
name: http
resources:
requests:
cpu: "500m"
cpu: "100m"
memory: "512Mi"
args: [ENVIRONMENT]
env:
- name: POD_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: JETTY_WORKER_INSTANCE
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE_ID
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: CONTAINER_NAME
value: pubapi
- name: WHOIS
image: gcr.io/GCP_PROJECT/proxy
ports:
- containerPort: 30001
name: whois
- containerPort: 30010
name: http-whois
- containerPort: 30011
name: https-whois
resources:
requests:
cpu: "500m"
args: [ --env, PROXY_ENV, --log, --local ]
env:
- name: POD_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE_ID
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: CONTAINER_NAME
value: WHOIS
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
@@ -67,8 +52,8 @@ spec:
apiVersion: apps/v1
kind: Deployment
name: pubapi
minReplicas: 1
maxReplicas: 20
minReplicas: 5
maxReplicas: 15
metrics:
- type: Resource
resource:
@@ -89,32 +74,6 @@ spec:
targetPort: http
name: http
---
apiVersion: v1
kind: Service
metadata:
name: WHOIS
annotations:
cloud.google.com/l4-rbs: enabled
networking.gke.io/weighted-load-balancing: pods-per-node
spec:
type: LoadBalancer
# Traffic is directly delivered to a node, preserving the original source IP.
externalTrafficPolicy: Local
ipFamilies: [IPv4, IPv6]
ipFamilyPolicy: RequireDualStack
selector:
service: pubapi
ports:
- port: 43
targetPort: whois
name: whois
- port: 80
targetPort: http-whois
name: http-whois
- port: 443
targetPort: https-whois
name: https-whois
---
apiVersion: net.gke.io/v1
kind: ServiceExport
metadata:

View File

@@ -0,0 +1,9 @@
# ---------------------------------------
# Module: gzip
# Enables gzip compression of the following mime types
# ---------------------------------------
--module=gzip
jetty.http.gzip.minGzipSize=200
jetty.http.gzip.includedMimeTypes=text/html,text/css,text/javascript,application/javascript,application/json,image/svg+xml

View File

@@ -40,7 +40,8 @@ do
kubectl apply -f -
kubectl apply -f "./kubernetes/proxy-service-canary.yaml" --force
fi
# Kills all running pods, new pods created will be pulling the new image.
kubectl delete pods --all
# Restart all running pods, new pods created will be pulling the new image.
kubectl rollout restart deployment/proxy-deployment
kubectl rollout restart deployment/proxy-deployment-canary
done < <(gcloud container clusters list --project ${project} | grep proxy-cluster)
kubectl config use-context "$current_context"

View File

@@ -15,8 +15,10 @@
package google.registry.proxy.handler;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.proxy.handler.ProxyProtocolHandler.REMOTE_ADDRESS_KEY;
import google.registry.proxy.metric.FrontendMetrics;
import google.registry.util.ProxyHttpHeaders;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
@@ -30,6 +32,8 @@ import java.util.function.Supplier;
/** Handler that processes WHOIS protocol logic. */
public final class WhoisServiceHandler extends HttpsRelayServiceHandler {
private String clientAddress;
public WhoisServiceHandler(
String relayHost,
String relayPath,
@@ -45,6 +49,12 @@ public final class WhoisServiceHandler extends HttpsRelayServiceHandler {
super.channelActive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
clientAddress = ctx.channel().attr(REMOTE_ADDRESS_KEY).get();
super.channelRead(ctx, msg);
}
@Override
protected FullHttpRequest decodeFullHttpRequest(ByteBuf byteBuf) {
FullHttpRequest request = super.decodeFullHttpRequest(byteBuf);
@@ -52,6 +62,12 @@ public final class WhoisServiceHandler extends HttpsRelayServiceHandler {
.headers()
.set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.set(HttpHeaderNames.ACCEPT, HttpHeaderValues.TEXT_PLAIN);
if (clientAddress != null) {
request
.headers()
.set(ProxyHttpHeaders.IP_ADDRESS, clientAddress)
.set(ProxyHttpHeaders.FALLBACK_IP_ADDRESS, clientAddress);
}
return request;
}

View File

@@ -28,7 +28,7 @@ COPY go.sum ./
COPY go.mod ./
RUN go build -o /deployCloudSchedulerAndQueue
FROM marketplace.gcr.io/google/ubuntu2204
FROM marketplace.gcr.io/google/ubuntu2404
ENV DEBIAN_FRONTEND=noninteractive LANG=en_US.UTF-8
# Add script for cloud scheduler and cloud tasks deployment
COPY --from=deployCloudSchedulerAndQueueBuilder /deployCloudSchedulerAndQueue /usr/local/bin/deployCloudSchedulerAndQueue

View File

@@ -43,10 +43,6 @@ apt-get install openjdk-21-jdk-headless -y
# Install Python
apt-get install python3 -y
# As of March 2021 python3 is at v3.6. Get pip then install dataclasses
# (introduced in 3.7) for nom_build
apt-get install python3-pip -y
python3 -m pip install dataclasses
# Install Node
apt-get install npm -y
@@ -57,11 +53,13 @@ npm install -g n
for i in {1..5}; do n 22.7.0 && break || sleep 15; done
# Install gp_dump
apt-get install postgresql-client-11 procps -y
apt-get install postgresql-client-17 procps -y
# Install gcloud
apt-get install google-cloud-cli -y
apt-get install google-cloud-sdk-app-engine-java -y
apt-get install kubectl -y
apt-get install google-cloud-cli-gke-gcloud-auth-plugin -y
# Install git
apt-get install git -y

View File

@@ -198,6 +198,7 @@ artifacts:
- 'release/cloudbuild-delete-*.yaml'
- 'release/cloudbuild-schema-deploy-*.yaml'
- 'release/cloudbuild-schema-verify-*.yaml'
- 'release/cloudbuild-restart-proxies-*.yaml'
- 'jetty/kubernetes/*.yaml'
- 'jetty/kubernetes/gateway/*.yaml'
# The images are already uploaded, but we still need to include them there so that

View File

@@ -88,6 +88,7 @@ steps:
sed -i s/builder:latest/builder@$builder_digest/g release/cloudbuild-schema-deploy.yaml
sed -i s/builder:latest/builder@$builder_digest/g release/cloudbuild-schema-verify.yaml
sed -i s/builder:latest/builder@$builder_digest/g release/cloudbuild-delete.yaml
sed -i s/builder:latest/builder@$builder_digest/g release/cloudbuild-restart-proxies.yaml
sed -i s/GCP_PROJECT/${PROJECT_ID}/ proxy/kubernetes/proxy-*.yaml
sed -i s/'$${TAG_NAME}'/${TAG_NAME}/g release/cloudbuild-sync-and-tag.yaml
sed -i s/'$${TAG_NAME}'/${TAG_NAME}/g release/cloudbuild-deploy.yaml
@@ -99,6 +100,11 @@ steps:
> release/cloudbuild-deploy-gke-${environment}.yaml
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-delete.yaml \
> release/cloudbuild-delete-${environment}.yaml
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-restart-proxies.yaml \
> release/cloudbuild-restart-proxies-${environment}.yaml
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-restart-proxies.yaml | \
sed s/proxy-deployment/proxy-deployment-canary/g \
> release/cloudbuild-restart-proxies-${environment}-canary.yaml
done
# Build and upload the schema_deployer image.
- name: 'gcr.io/cloud-builders/docker'
@@ -178,14 +184,14 @@ steps:
base_domain=$(grep baseDomain \
./core/src/main/java/google/registry/config/files/nomulus-config-${env}.yaml | \
awk '{print $2}')
for service in frontend backend pubapi console
for service in frontend backend pubapi console
do
# non-canary
sed s/GCP_PROJECT/${PROJECT_ID}/g ./jetty/kubernetes/nomulus-${service}.yaml | \
sed s/latest/${TAG_NAME}/g | \
sed s/ENVIRONMENT/${env}/g | \
sed s/PROXY_ENV/${env}/g | \
sed s/EPP/epp/g | \
sed s/WHOIS/whois/g > ./jetty/kubernetes/nomulus-${env}-${service}.yaml
sed s/PROXY_ENV/"${env}"/g | \
sed s/EPP/"epp"/g > ./jetty/kubernetes/nomulus-${env}-${service}.yaml
# Proxy '--log' flag does not work on production.
if [ ${env} == production ]
then
@@ -198,10 +204,10 @@ steps:
fi
# canary
sed s/GCP_PROJECT/${PROJECT_ID}/g ./jetty/kubernetes/nomulus-${service}.yaml | \
sed s/latest/${TAG_NAME}/g | \
sed s/ENVIRONMENT/${env}/g | \
sed s/PROXY_ENV/${env}_canary/g | \
sed s/EPP/epp-canary/g | \
sed s/WHOIS/whois-canary/g | \
sed s/PROXY_ENV/"${env}_canary"/g | \
sed s/EPP/"epp-canary"/g | \
sed s/${service}/${service}-canary/g \
> ./jetty/kubernetes/nomulus-${env}-${service}-canary.yaml
# Proxy '--log' flag does not work on production.
@@ -218,11 +224,11 @@ steps:
sed s/BASE_DOMAIN/${base_domain}/g \
./jetty/kubernetes/gateway/nomulus-route-${service}.yaml \
> ./jetty/kubernetes/gateway/nomulus-route-${env}-${service}.yaml
# IAP
sed s/SERVICE/${service}/g ./jetty/kubernetes/gateway/nomulus-iap-${env}.yaml \
> ./jetty/kubernetes/gateway/nomulus-iap-${env}-${service}.yaml
sed s/SERVICE/${service}-canary/g ./jetty/kubernetes/gateway/nomulus-iap-${env}.yaml \
> ./jetty/kubernetes/gateway/nomulus-iap-${env}-${service}-canary.yaml
# GCP backend policy
sed s/SERVICE/${service}/g ./jetty/kubernetes/gateway/nomulus-backend-policy-${env}.yaml \
> ./jetty/kubernetes/gateway/nomulus-backend-policy-${env}-${service}.yaml
sed s/SERVICE/${service}-canary/g ./jetty/kubernetes/gateway/nomulus-backend-policy-${env}.yaml \
> ./jetty/kubernetes/gateway/nomulus-backend-policy-${env}-${service}-canary.yaml
done
done
# Upload the Gradle binary to GCS if it does not exist and point URL in Gradle wrapper to it.
@@ -270,7 +276,7 @@ steps:
$(gcloud auth list --format='get(account)' --filter=active)
git add .
git commit -m "Release commit for tag ${TAG_NAME}"
git push -o nokeycheck origin master
git push -o nokeycheck origin master
git tag ${TAG_NAME}
git push -o nokeycheck origin ${TAG_NAME}
timeout: 3600s

View File

@@ -0,0 +1,64 @@
# This will do rolling restarts of all proxies. This forces the client to reconnect
# and resets the sessions.
#
# To manually trigger a build on GCB, run:
# gcloud builds submit --config=cloudbuild-restart-proxies.yaml \
# --substitutions=_ENV=[ENV] ..
#
# To trigger a build automatically, follow the instructions below and add a trigger:
# https://cloud.google.com/cloud-build/docs/running-builds/automate-builds
#
# Note: to work around the issue in Spinnaker's 'Deployment Manifest' stage,
# variable references must avoid the ${var} format. Valid formats include
# $var or ${"${var}"}. This file uses the former. Since TAG_NAME and _ENV are
# expanded in the copies sent to Spinnaker, we preserve the brackets around
# them for safe pattern matching during release.
# See https://github.com/spinnaker/spinnaker/issues/3028 for more information.
steps:
# Pull the credential for nomulus tool.
- name: 'gcr.io/$PROJECT_ID/builder:latest'
entrypoint: /bin/bash
args:
- -c
- |
set -e
gcloud secrets versions access latest \
--secret nomulus-tool-cloudbuild-credential > tool-credential.json
# Do rolling restarts of all proxies in all environments.
- name: 'gcr.io/$PROJECT_ID/builder:latest'
entrypoint: /bin/bash
args:
- -c
- |
if [ ${_ENV} == production ]
then
project_id="domain-registry"
else
project_id="domain-registry-${_ENV}"
fi
gcloud auth activate-service-account --key-file=tool-credential.json
first=true
t=0
while read line
do
# Sleep for t seconds for the rollout to stabilize.
if [[ -v first ]]
then
unset first
else
sleep $t
fi
name=$(echo $line | awk '{print $1}')
location=$(echo $line | awk '{print $2}')
echo $name $region
echo "Updating cluster $name in location $location..."
gcloud container clusters get-credentials $name \
--project $project_id --location $location
kubectl rollout restart deployment/proxy-deployment
done < <(gcloud container clusters list --project $project_id | grep proxy-cluster)
timeout: 7500s
options:
machineType: 'N1_HIGHCPU_8'

View File

@@ -3,3 +3,6 @@ COMMENT ON EXTENSION pgaudit IS 'provides auditing functionality';
SET default_with_oids = false;
CREATE EXTENSION IF NOT EXISTS pg_stat_statements WITH SCHEMA public;
COMMENT ON EXTENSION pg_stat_statements IS 'track execution statistics of all SQL statements executed';
SET transaction_timeout = 0;
SET default_table_access_method = heap;
SET default_with_oids = false;

View File

@@ -69,6 +69,14 @@ PGPASSWORD=${db_password} pg_dump -h localhost -U "${db_user}" \
--exclude-table flyway_schema_history \
postgres
if [ $? -ne 0 ]; then
echo "Failed to dump schema."
exit 1
else
echo "Schema dumped."
fi
raw_diff=$(diff /schema/nomulus.golden.sql /schema/nomulus.actual.sql)
# Clean up the raw_diff:
# - Remove diff locations (e.g. "5,6c5,6): grep "^[<>]"