Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c138420b0 | |||
| 08570511f5 | |||
| e62d970d34 | |||
| 067927b735 | |||
| 4ec2919ce3 | |||
| 19422075fa | |||
| 40b6984ffb | |||
| 6952e0f653 | |||
| dcb55d27bb | |||
| 765bd9834a | |||
| 221088e738 | |||
| 6649e00df7 | |||
| 2ceb52a7c4 | |||
| 120bcc33be |
@@ -16,31 +16,31 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^18.0.2",
|
||||
"@angular/cdk": "^18.0.2",
|
||||
"@angular/common": "^18.0.2",
|
||||
"@angular/compiler": "^18.0.2",
|
||||
"@angular/core": "^18.0.2",
|
||||
"@angular/forms": "^18.0.2",
|
||||
"@angular/material": "^18.0.2",
|
||||
"@angular/platform-browser": "^18.0.2",
|
||||
"@angular/platform-browser-dynamic": "^18.0.2",
|
||||
"@angular/router": "^18.0.2",
|
||||
"@angular/animations": "^19.1.4",
|
||||
"@angular/cdk": "^19.1.2",
|
||||
"@angular/common": "^19.1.4",
|
||||
"@angular/compiler": "^19.1.4",
|
||||
"@angular/core": "^19.1.4",
|
||||
"@angular/forms": "^19.1.4",
|
||||
"@angular/material": "^19.1.2",
|
||||
"@angular/platform-browser": "^19.1.4",
|
||||
"@angular/platform-browser-dynamic": "^19.1.4",
|
||||
"@angular/router": "^19.1.4",
|
||||
"rxjs": "~7.5.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.2"
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^18.0.3",
|
||||
"@angular-eslint/builder": "18.0.1",
|
||||
"@angular-eslint/eslint-plugin": "18.0.1",
|
||||
"@angular-eslint/eslint-plugin-template": "18.0.1",
|
||||
"@angular-eslint/schematics": "18.0.1",
|
||||
"@angular-eslint/template-parser": "18.0.1",
|
||||
"@angular/cli": "~18.0.3",
|
||||
"@angular/compiler-cli": "^18.0.2",
|
||||
"@angular-devkit/build-angular": "^19.1.5",
|
||||
"@angular-eslint/builder": "19.0.2",
|
||||
"@angular-eslint/eslint-plugin": "19.0.2",
|
||||
"@angular-eslint/eslint-plugin-template": "19.0.2",
|
||||
"@angular-eslint/schematics": "19.0.2",
|
||||
"@angular-eslint/template-parser": "19.0.2",
|
||||
"@angular/cli": "~19.1.5",
|
||||
"@angular/compiler-cli": "^19.1.4",
|
||||
"@types/jasmine": "~4.0.0",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node": "^18.19.74",
|
||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||
"@typescript-eslint/parser": "^7.2.0",
|
||||
"concurrently": "^7.6.0",
|
||||
@@ -52,6 +52,6 @@
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.0.0",
|
||||
"prettier": "2.8.7",
|
||||
"typescript": "~5.4.5"
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<mat-sidenav
|
||||
[mode]="breakpointObserver.isMobileView() ? 'over' : 'side'"
|
||||
[opened]="!breakpointObserver.isMobileView()"
|
||||
[disableClose]="!breakpointObserver.isMobileView()"
|
||||
#sidenav
|
||||
class="console-app__sidebar"
|
||||
>
|
||||
|
||||
@@ -24,6 +24,7 @@ import { UserDataService } from './shared/services/userData.service';
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class AppComponent implements AfterViewInit {
|
||||
@ViewChild(MatSidenav)
|
||||
|
||||
@@ -59,6 +59,7 @@ import { UserDataService } from './shared/services/userData.service';
|
||||
import { SnackBarModule } from './snackbar.module';
|
||||
import { SupportComponent } from './support/support.component';
|
||||
import { TldsComponent } from './tlds/tlds.component';
|
||||
import { ForceFocusDirective } from './shared/directives/forceFocus.directive';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SelectedRegistrarWrapper],
|
||||
@@ -78,6 +79,7 @@ export class SelectedRegistrarModule {}
|
||||
HeaderComponent,
|
||||
HomeComponent,
|
||||
LocationBackDirective,
|
||||
ForceFocusDirective,
|
||||
UserLevelVisibility,
|
||||
NavigationComponent,
|
||||
NewRegistrarComponent,
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
<app-selected-registrar-wrapper>
|
||||
<h1 class="mat-headline-4">Billing Info</h1>
|
||||
<h1 class="mat-headline-4" forceFocus>Billing Info</h1>
|
||||
<div class="console-app__billing">
|
||||
<div>
|
||||
<div class="console-app__billing-subhead">
|
||||
Billing records and information
|
||||
</div>
|
||||
<a class="text-l" href="{{ driveFolderUrl() }}" target="_blank"
|
||||
<a
|
||||
class="text-l"
|
||||
href="{{ driveFolderUrl() }}"
|
||||
target="_blank"
|
||||
aria-label="View billing records on Google Drive"
|
||||
>View on Google Drive</a
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<img src="./assets/billing.png" />
|
||||
<img src="./assets/billing.png" alt="Generic billing image" />
|
||||
</div>
|
||||
</div>
|
||||
</app-selected-registrar-wrapper>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
width: 100%;
|
||||
}
|
||||
&-subhead {
|
||||
font-size: 20px;
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
selector: 'app-billingInfo',
|
||||
templateUrl: './billingInfo.component.html',
|
||||
styleUrls: ['./billingInfo.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class BillingInfoComponent {
|
||||
public static PATH = 'billingInfo';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<app-selected-registrar-wrapper>
|
||||
<div class="console-app-domains">
|
||||
<h1 class="mat-headline-4">Domains</h1>
|
||||
<h1 class="mat-headline-4" forceFocus>Domains</h1>
|
||||
|
||||
<div
|
||||
class="console-app-domains__actions-wrapper"
|
||||
@@ -25,7 +25,11 @@
|
||||
} @else {
|
||||
<mat-menu #actions="matMenu">
|
||||
<ng-template matMenuContent let-domainName="domainName">
|
||||
<button mat-menu-item (click)="openRegistryLock(domainName)">
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="openRegistryLock(domainName)"
|
||||
aria-label="Access registry lock for domain"
|
||||
>
|
||||
<mat-icon>key</mat-icon>
|
||||
<span>Registry Lock</span>
|
||||
</button>
|
||||
@@ -77,6 +81,7 @@
|
||||
<button
|
||||
mat-flat-button
|
||||
aria-label="Delete Selected Domains"
|
||||
[attr.aria-hidden]="!selection.hasValue()"
|
||||
(click)="deleteSelectedDomains()"
|
||||
>
|
||||
Delete Selected Domains
|
||||
|
||||
@@ -48,6 +48,7 @@ interface DomainData {
|
||||
<button mat-button (click)="onClose()">Close</button>
|
||||
</mat-dialog-actions>
|
||||
`,
|
||||
standalone: false,
|
||||
})
|
||||
export class ResponseDialogComponent {
|
||||
constructor(
|
||||
@@ -79,6 +80,7 @@ export class ResponseDialogComponent {
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
`,
|
||||
standalone: false,
|
||||
})
|
||||
export class ReasonDialogComponent {
|
||||
reason: string = '';
|
||||
@@ -102,6 +104,7 @@ export class ReasonDialogComponent {
|
||||
selector: 'app-domain-list',
|
||||
templateUrl: './domainList.component.html',
|
||||
styleUrls: ['./domainList.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class DomainListComponent {
|
||||
public static PATH = 'domain-list';
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
color="primary"
|
||||
type="submit"
|
||||
[disabled]="!unlockDomain.valid"
|
||||
aria-label="Submit domain unlock request"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
@@ -73,6 +74,7 @@
|
||||
color="primary"
|
||||
type="submit"
|
||||
[disabled]="!lockDomain.valid"
|
||||
aria-label="Submit domain lock request"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
|
||||
@@ -25,6 +25,7 @@ import { RegistryLockService } from './registryLock.service';
|
||||
selector: 'app-registry-lock',
|
||||
templateUrl: './registryLock.component.html',
|
||||
styleUrls: ['./registryLock.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class RegistryLockComponent {
|
||||
readonly isLocked = computed(() =>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<mat-toolbar>
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="Open menu"
|
||||
aria-label="Open navigation menu"
|
||||
(click)="toggleNavPane()"
|
||||
*ngIf="breakpointObserver.isMobileView()"
|
||||
class="console-app__menu-btn"
|
||||
|
||||
@@ -19,6 +19,7 @@ import { BreakPointObserverService } from '../shared/services/breakPoint.service
|
||||
selector: 'app-header',
|
||||
templateUrl: './header.component.html',
|
||||
styleUrls: ['./header.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class HeaderComponent {
|
||||
private isNavOpen = false;
|
||||
|
||||
@@ -25,6 +25,7 @@ import { BreakPointObserverService } from '../shared/services/breakPoint.service
|
||||
selector: 'app-home',
|
||||
templateUrl: './home.component.html',
|
||||
styleUrls: ['./home.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class HomeComponent {
|
||||
constructor(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
margin-top: 30px;
|
||||
}
|
||||
&-subhead {
|
||||
font-size: 20px;
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import { DomainListComponent } from '../domains/domainList.component';
|
||||
templateUrl: './registryLockVerify.component.html',
|
||||
styleUrls: ['./registryLockVerify.component.scss'],
|
||||
providers: [RegistryLockVerifyService],
|
||||
standalone: false,
|
||||
})
|
||||
export class RegistryLockVerifyComponent {
|
||||
public static PATH = 'registry-lock-verify';
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
<mat-nested-tree-node
|
||||
*matTreeNodeDef="let node; when: hasChild"
|
||||
(click)="onClick(node)"
|
||||
tabindex="0"
|
||||
(keyup.enter)="onClick(node)"
|
||||
>
|
||||
<div class="mat-tree-node" [class.active]="router.url.includes(node.path)">
|
||||
<button
|
||||
|
||||
@@ -33,6 +33,7 @@ interface NavMenuNode extends RouteWithIcon {
|
||||
selector: 'app-navigation',
|
||||
templateUrl: './navigation.component.html',
|
||||
styleUrls: ['./navigation.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class NavigationComponent {
|
||||
renderRouter: boolean = true;
|
||||
|
||||
@@ -30,7 +30,14 @@
|
||||
>
|
||||
</mat-form-field>
|
||||
</p>
|
||||
<button mat-flat-button color="primary" type="submit">Save</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="submit"
|
||||
aria-label="Submit new OT&E account"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,6 @@ export interface OteCreateResponse extends Map<string, string> {
|
||||
|
||||
@Component({
|
||||
selector: 'app-ote',
|
||||
standalone: true,
|
||||
imports: [MaterialModule, SnackBarModule],
|
||||
templateUrl: './newOte.component.html',
|
||||
styleUrls: ['./newOte.component.scss'],
|
||||
|
||||
@@ -31,7 +31,6 @@ export interface OteStatusResponse {
|
||||
|
||||
@Component({
|
||||
selector: 'app-ote-status',
|
||||
standalone: true,
|
||||
imports: [MaterialModule, SnackBarModule, CommonModule],
|
||||
templateUrl: './oteStatus.component.html',
|
||||
styleUrls: ['./oteStatus.component.scss'],
|
||||
|
||||
@@ -175,6 +175,7 @@ JPY=billing-id-for-yen"
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="submit"
|
||||
aria-label="Submit new registrar request"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
|
||||
@@ -33,6 +33,7 @@ interface LocalizedAddressStreet {
|
||||
templateUrl: './newRegistrar.component.html',
|
||||
styleUrls: ['./newRegistrar.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
standalone: false,
|
||||
})
|
||||
export default class NewRegistrarComponent {
|
||||
protected newRegistrar: Registrar;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*ngIf="oteButtonVisible"
|
||||
mat-stroked-button
|
||||
(click)="checkOteStatus()"
|
||||
aria-label="Check OT&E account"
|
||||
aria-label="Check OT&E account status"
|
||||
[elementId]="getElementIdForOteBlock()"
|
||||
>
|
||||
Check OT&E Status
|
||||
|
||||
@@ -27,6 +27,7 @@ import { environment } from '../../environments/environment';
|
||||
selector: 'app-registrar-details',
|
||||
templateUrl: './registrarDetails.component.html',
|
||||
styleUrls: ['./registrarDetails.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class RegistrarDetailsComponent implements OnInit {
|
||||
public static PATH = 'registrars/:id';
|
||||
|
||||
@@ -19,6 +19,7 @@ import { RegistrarService } from './registrar.service';
|
||||
selector: 'app-registrar-selector',
|
||||
templateUrl: './registrarSelector.component.html',
|
||||
styleUrls: ['./registrarSelector.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class RegistrarSelectorComponent {
|
||||
registrarInput = signal<string>(this.registrarService.registrarId());
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
} @else {
|
||||
<div class="console-app__registrars">
|
||||
<div class="console-app__registrars-header">
|
||||
<h1 class="mat-headline-4">Registrars</h1>
|
||||
<h1 class="mat-headline-4" forceFocus>Registrars</h1>
|
||||
<div class="spacer"></div>
|
||||
<button
|
||||
mat-stroked-button
|
||||
|
||||
@@ -78,6 +78,7 @@ export const columns = [
|
||||
templateUrl: './registrarsTable.component.html',
|
||||
styleUrls: ['./registrarsTable.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
standalone: false,
|
||||
})
|
||||
export class RegistrarComponent {
|
||||
public static PATH = 'registrars';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<h1 class="mat-headline-4">Resources</h1>
|
||||
<h1 class="mat-headline-4" forceFocus>Resources</h1>
|
||||
<div class="console-app__resources">
|
||||
<div>
|
||||
<div class="console-app__resources-subhead">Technical resources</div>
|
||||
@@ -11,6 +11,6 @@
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<img src="./assets/resources.png" />
|
||||
<img src="./assets/resources.png" alt="Generic resources image" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
width: 100%;
|
||||
}
|
||||
&-subhead {
|
||||
font-size: 20px;
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { UserDataService } from '../shared/services/userData.service';
|
||||
selector: 'app-resources',
|
||||
templateUrl: './resources.component.html',
|
||||
styleUrls: ['./resources.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class ResourcesComponent {
|
||||
public static PATH = 'resources';
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
templateUrl: './contact.component.html',
|
||||
styleUrls: ['./contact.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
standalone: false,
|
||||
})
|
||||
export default class ContactComponent {
|
||||
public static PATH = 'contact';
|
||||
|
||||
@@ -128,6 +128,7 @@
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="submit"
|
||||
aria-label="Save contact updates"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
selector: 'app-contact-details',
|
||||
templateUrl: './contactDetails.component.html',
|
||||
styleUrls: ['./contactDetails.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class ContactDetailsComponent {
|
||||
protected contactTypeToTextMap = contactTypeToTextMap;
|
||||
|
||||
@@ -33,6 +33,7 @@ type errorFriendlyText = { [type in errorCode]: String };
|
||||
selector: 'app-epp-password-edit',
|
||||
templateUrl: './eppPasswordEdit.component.html',
|
||||
styleUrls: ['./eppPasswordEdit.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export default class EppPasswordEditComponent {
|
||||
MIN_MAX_LENGHT = new String(
|
||||
|
||||
@@ -23,6 +23,7 @@ import { SecurityService, apiToUiConverter } from './security.service';
|
||||
selector: 'app-security',
|
||||
templateUrl: './security.component.html',
|
||||
styleUrls: ['./security.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export default class SecurityComponent {
|
||||
public static PATH = 'security';
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<button
|
||||
matSuffix
|
||||
mat-icon-button
|
||||
aria-label="Remove"
|
||||
[attr.aria-label]="'Remove IP entry ' + ip.value"
|
||||
(click)="removeIpEntry(ip)"
|
||||
[disabled]="isUpdating"
|
||||
>
|
||||
@@ -36,6 +36,7 @@
|
||||
[disabled]="isUpdating"
|
||||
color="primary"
|
||||
(click)="createIpEntry()"
|
||||
aria-label="Add new IP address"
|
||||
type="button"
|
||||
>
|
||||
+ Add IP
|
||||
|
||||
@@ -26,6 +26,7 @@ import { SecurityService, apiToUiConverter } from './security.service';
|
||||
selector: 'app-security-edit',
|
||||
templateUrl: './securityEdit.component.html',
|
||||
styleUrls: ['./securityEdit.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export default class SecurityEditComponent {
|
||||
dataSource: SecuritySettings = {};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<app-selected-registrar-wrapper>
|
||||
<div class="console-settings">
|
||||
<h1 class="mat-headline-4">Settings</h1>
|
||||
<h1 class="mat-headline-4" forceFocus>Settings</h1>
|
||||
<nav
|
||||
mat-tab-nav-bar
|
||||
mat-stretch-tabs="false"
|
||||
@@ -14,6 +14,7 @@
|
||||
queryParamsHandling="merge"
|
||||
#rla="routerLinkActive"
|
||||
[active]="rla.isActive"
|
||||
aria-label="Access contacts settings"
|
||||
>Contacts</a
|
||||
>
|
||||
<a
|
||||
@@ -23,6 +24,7 @@
|
||||
queryParamsHandling="merge"
|
||||
#rla2="routerLinkActive"
|
||||
[active]="rla2.isActive"
|
||||
aria-label="Access whois settings"
|
||||
>WHOIS Info</a
|
||||
>
|
||||
<a
|
||||
@@ -32,6 +34,7 @@
|
||||
queryParamsHandling="merge"
|
||||
#rla3="routerLinkActive"
|
||||
[active]="rla3.isActive"
|
||||
aria-label="Access security settings"
|
||||
>Security</a
|
||||
>
|
||||
</nav>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { Component, ViewEncapsulation } from '@angular/core';
|
||||
templateUrl: './settings.component.html',
|
||||
styleUrls: ['./settings.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
standalone: false,
|
||||
})
|
||||
export class SettingsComponent {
|
||||
public static PATH = 'settings';
|
||||
|
||||
@@ -21,6 +21,7 @@ import { WhoisService } from './whois.service';
|
||||
selector: 'app-whois',
|
||||
templateUrl: './whois.component.html',
|
||||
styleUrls: ['./whois.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export default class WhoisComponent {
|
||||
public static PATH = 'whois';
|
||||
|
||||
@@ -149,7 +149,14 @@
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
<button mat-flat-button color="primary" type="submit">Save</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="submit"
|
||||
aria-label="Save WHOIS settings"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,6 +26,7 @@ import { WhoisService } from './whois.service';
|
||||
selector: 'app-whois-edit',
|
||||
templateUrl: './whoisEdit.component.html',
|
||||
styleUrls: ['./whoisEdit.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export default class WhoisEditComponent {
|
||||
registrarInEdit: Registrar | undefined;
|
||||
|
||||
@@ -24,6 +24,7 @@ interface Notification {
|
||||
selector: 'app-notifications',
|
||||
templateUrl: './notifications.component.html',
|
||||
styleUrls: ['./notifications.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class NotificationsComponent {
|
||||
protected mockNotifications: Notification[] = [
|
||||
|
||||
@@ -31,6 +31,7 @@ import { RegistrarService } from 'src/app/registrar/registrar.service';
|
||||
</div>
|
||||
}
|
||||
`,
|
||||
standalone: false,
|
||||
})
|
||||
export class SelectedRegistrarWrapper {
|
||||
constructor(protected registrarService: RegistrarService) {}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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.
|
||||
|
||||
import { Directive, ElementRef, effect } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[forceFocus]',
|
||||
standalone: false,
|
||||
})
|
||||
export class ForceFocusDirective {
|
||||
constructor(private el: ElementRef) {
|
||||
effect(this.processElement.bind(this));
|
||||
}
|
||||
|
||||
processElement() {
|
||||
this.el.nativeElement.tabIndex = '1';
|
||||
this.el.nativeElement.focus();
|
||||
this.el.nativeElement.tabIndex = '-1';
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { Directive, HostListener } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[backButton]',
|
||||
standalone: false,
|
||||
})
|
||||
export class LocationBackDirective {
|
||||
constructor(private location: Location) {}
|
||||
|
||||
@@ -35,6 +35,7 @@ export const DISABLED_ELEMENTS_PER_ROLE = {
|
||||
|
||||
@Directive({
|
||||
selector: '[elementId]',
|
||||
standalone: false,
|
||||
})
|
||||
export class UserLevelVisibility {
|
||||
@Input() elementId!: RESTRICTED_ELEMENTS | null;
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
For help with OT&E sandbox and certification, or new technical requirements
|
||||
for any of our new TLD launches.
|
||||
</p>
|
||||
<a class="text-l" href="mailto:registry-integration@google.com"
|
||||
<a
|
||||
class="text-l"
|
||||
href="mailto:registry-integration@google.com"
|
||||
aria-label="Email us with OT&E sandbox/certification or new TLD technical requirements questions."
|
||||
>registry-integration@google.com</a
|
||||
>
|
||||
<p class="text-l">
|
||||
@@ -19,6 +22,7 @@
|
||||
</p>
|
||||
<a
|
||||
class="text-l"
|
||||
aria-label="Email support with general purpose questions."
|
||||
href="mailto:{{ userDataService.userData()?.supportEmail }}"
|
||||
>{{ userDataService.userData()?.supportEmail }}</a
|
||||
>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { UserDataService } from '../shared/services/userData.service';
|
||||
selector: 'app-support',
|
||||
templateUrl: './support.component.html',
|
||||
styleUrls: ['./support.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class SupportComponent {
|
||||
public static PATH = 'support';
|
||||
|
||||
@@ -18,5 +18,6 @@ import { Component } from '@angular/core';
|
||||
selector: 'app-tlds',
|
||||
templateUrl: './tlds.component.html',
|
||||
styleUrls: ['./tlds.component.scss'],
|
||||
standalone: false,
|
||||
})
|
||||
export class TldsComponent {}
|
||||
|
||||
@@ -90,11 +90,12 @@
|
||||
<input
|
||||
[type]="isPasswordVisible ? 'text' : 'password'"
|
||||
[value]="userDetails().password"
|
||||
disabled
|
||||
aria-label="Password field"
|
||||
readonly
|
||||
/>
|
||||
<button
|
||||
mat-button
|
||||
aria-label="Show password"
|
||||
aria-hidden="true"
|
||||
(click)="isPasswordVisible = !isPasswordVisible"
|
||||
>
|
||||
{{ isPasswordVisible ? "Hide" : "View" }} password
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
.console-app {
|
||||
&__user-details {
|
||||
max-width: 616px;
|
||||
&-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -34,6 +35,5 @@
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 10px;
|
||||
}
|
||||
max-width: 616px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import { UserEditFormComponent } from './userEditForm.component';
|
||||
selector: 'app-user-edit',
|
||||
templateUrl: './userDetails.component.html',
|
||||
styleUrls: ['./userDetails.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
FormsModule,
|
||||
MaterialModule,
|
||||
|
||||
@@ -33,7 +33,13 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</p>
|
||||
<button mat-flat-button color="primary" aria-label="Save user" type="submit">
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
aria-label="Save user"
|
||||
type="submit"
|
||||
aria-label="Save changes to the user"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -29,19 +29,17 @@ import { User } from './users.service';
|
||||
selector: 'app-user-edit-form',
|
||||
templateUrl: './userEditForm.component.html',
|
||||
styleUrls: ['./userEditForm.component.scss'],
|
||||
standalone: true,
|
||||
imports: [FormsModule, MaterialModule, CommonModule],
|
||||
providers: [],
|
||||
})
|
||||
export class UserEditFormComponent {
|
||||
@ViewChild('form') form!: ElementRef;
|
||||
isNew = input<boolean>(false);
|
||||
user = input<User>(
|
||||
user = input<User, User>(
|
||||
{
|
||||
emailAddress: '',
|
||||
role: 'ACCOUNT_MANAGER',
|
||||
},
|
||||
// @ts-ignore - legit option, typescript fails to match it to a proper type
|
||||
{ transform: (user: User) => structuredClone(user) }
|
||||
);
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<app-selected-registrar-wrapper cdkTrapFocus [cdkTrapFocusAutoCapture]="true">
|
||||
<app-selected-registrar-wrapper>
|
||||
@if(isLoading) {
|
||||
<div class="console-app__users-spinner">
|
||||
<mat-spinner />
|
||||
</div>
|
||||
} @else if(selectingExistingUser) {
|
||||
<div class="console-app__users">
|
||||
<div class="console-app__users" cdkTrapFocus [cdkTrapFocusAutoCapture]="true">
|
||||
<h1 class="mat-headline-4">Add existing user</h1>
|
||||
<p>
|
||||
<button
|
||||
@@ -58,24 +58,32 @@
|
||||
}
|
||||
</div>
|
||||
} @else if(usersService.currentlyOpenUserEmail()) {
|
||||
<app-user-edit></app-user-edit>
|
||||
<div cdkTrapFocus [cdkTrapFocusAutoCapture]="true">
|
||||
<app-user-edit></app-user-edit>
|
||||
</div>
|
||||
|
||||
} @else if(isNew) {
|
||||
<h1 class="mat-headline-4">New User Form</h1>
|
||||
<div class="spacer"></div>
|
||||
<p>
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="Back to users list"
|
||||
(click)="isNew = false"
|
||||
>
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
</p>
|
||||
<app-user-edit-form [isNew]="true" (onEditComplete)="createNewUser($event)" />
|
||||
<div cdkTrapFocus [cdkTrapFocusAutoCapture]="true">
|
||||
<h1 class="mat-headline-4">New User Form</h1>
|
||||
<div class="spacer"></div>
|
||||
<p>
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="Back to users list"
|
||||
(click)="isNew = false"
|
||||
>
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
</p>
|
||||
<app-user-edit-form
|
||||
[isNew]="true"
|
||||
(onEditComplete)="createNewUser($event)"
|
||||
/>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="console-app__users">
|
||||
<div class="console-app__users-header">
|
||||
<h1 class="mat-headline-4">Users</h1>
|
||||
<h1 class="mat-headline-4" forceFocus>Users</h1>
|
||||
<div class="spacer"></div>
|
||||
<div class="console-app__users-header-buttons">
|
||||
<button
|
||||
@@ -88,12 +96,7 @@
|
||||
<mat-icon>add</mat-icon>
|
||||
Add existing user
|
||||
</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
(click)="isNew = true"
|
||||
aria-label="Create new user"
|
||||
color="primary"
|
||||
>
|
||||
<button mat-flat-button (click)="isNew = true" color="primary">
|
||||
Create New User
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -32,7 +32,6 @@ import { UserEditFormComponent } from './userEditForm.component';
|
||||
selector: 'app-users',
|
||||
templateUrl: './users.component.html',
|
||||
styleUrls: ['./users.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
FormsModule,
|
||||
MaterialModule,
|
||||
|
||||
@@ -43,7 +43,6 @@ export const columns = [
|
||||
selector: 'app-users-list',
|
||||
templateUrl: './usersList.component.html',
|
||||
styleUrls: ['./usersList.component.scss'],
|
||||
standalone: true,
|
||||
imports: [MaterialModule, CommonModule],
|
||||
providers: [],
|
||||
})
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
@use "@angular/material" as mat;
|
||||
@import "app/registrar/registrarSelector.component.scss";
|
||||
@use "app/registrar/registrarSelector.component.scss";
|
||||
|
||||
html,
|
||||
body {
|
||||
@@ -55,13 +55,13 @@ body {
|
||||
font-weight: bold;
|
||||
width: var(--list-item-title-width);
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-size: 0.85rem;
|
||||
white-space: pre-line;
|
||||
color: #202124;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.console-app__list-value {
|
||||
font-size: 14px;
|
||||
font-size: 0.85rem;
|
||||
white-space: pre-line;
|
||||
word-break: break-word;
|
||||
color: var(--text);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
@use "sass:map";
|
||||
@use "sass:math";
|
||||
@use "@angular/material" as mat;
|
||||
@use "@material/textfield";
|
||||
|
||||
$secondary-color: #80868b;
|
||||
$border-color: #dadce0;
|
||||
@@ -13,7 +12,8 @@ $border-color: #dadce0;
|
||||
// Include the common styles for Angular Material. We include this here so that you only
|
||||
// have to load a single css file for Angular Material in your app.
|
||||
// Be sure that you only ever include this mixin once!
|
||||
@include mat.core();
|
||||
@include mat.elevation-classes();
|
||||
@include mat.app-background();
|
||||
|
||||
$typographyConfig: mat.m2-define-typography-config(
|
||||
$headline-1:
|
||||
@@ -83,10 +83,10 @@ $typographyConfig: mat.m2-define-typography-config(
|
||||
);
|
||||
|
||||
.text-xl {
|
||||
font-size: 18px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.text-l {
|
||||
font-size: 16px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
mat-row:nth-child(odd) {
|
||||
|
||||
@@ -147,6 +147,13 @@ public class FlowModule {
|
||||
.map(IsolationLevel::value);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FlowScope
|
||||
@LogSqlStatements
|
||||
boolean provideShouldLogSqlStatements(Class<? extends Flow> flowClass) {
|
||||
return SqlStatementLoggingFlow.class.isAssignableFrom(flowClass);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FlowScope
|
||||
@Superuser
|
||||
@@ -370,4 +377,9 @@ public class FlowModule {
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface Transactional {}
|
||||
|
||||
/** Dagger qualifier for if we should log all SQL statements in a flow. */
|
||||
@Qualifier
|
||||
@Documented
|
||||
public @interface LogSqlStatements {}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import static google.registry.xml.XmlTransformer.prettyPrint;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.flows.FlowModule.DryRun;
|
||||
import google.registry.flows.FlowModule.InputXml;
|
||||
import google.registry.flows.FlowModule.LogSqlStatements;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.Transactional;
|
||||
@@ -49,6 +50,7 @@ public class FlowRunner {
|
||||
@Inject @DryRun boolean isDryRun;
|
||||
@Inject @Superuser boolean isSuperuser;
|
||||
@Inject @Transactional boolean isTransactional;
|
||||
@Inject @LogSqlStatements boolean logSqlStatements;
|
||||
@Inject SessionMetadata sessionMetadata;
|
||||
@Inject Trid trid;
|
||||
@Inject FlowReporter flowReporter;
|
||||
@@ -97,7 +99,8 @@ public class FlowRunner {
|
||||
} catch (EppException e) {
|
||||
throw new EppRuntimeException(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
logSqlStatements);
|
||||
} catch (DryRunException e) {
|
||||
return e.output;
|
||||
} catch (EppRuntimeException e) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// 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.flows;
|
||||
|
||||
/**
|
||||
* Interface for a {@link Flow} that logs its SQL statements when running transactionally.
|
||||
*
|
||||
* <p>We don't wish to log all SQL statements ever executed (that'll create too much log bloat) but
|
||||
* for some flows and some occasions we may wish to know precisely what SQL statements are being
|
||||
* run.
|
||||
*/
|
||||
public interface SqlStatementLoggingFlow extends TransactionalFlow {}
|
||||
@@ -55,6 +55,7 @@ import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.MutatingFlow;
|
||||
import google.registry.flows.SessionMetadata;
|
||||
import google.registry.flows.SqlStatementLoggingFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.custom.DomainDeleteFlowCustomLogic;
|
||||
import google.registry.flows.custom.DomainDeleteFlowCustomLogic.AfterValidationParameters;
|
||||
@@ -117,7 +118,7 @@ import org.joda.time.Duration;
|
||||
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_DELETE)
|
||||
public final class DomainDeleteFlow implements MutatingFlow {
|
||||
public final class DomainDeleteFlow implements MutatingFlow, SqlStatementLoggingFlow {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
|
||||
@@ -287,6 +287,9 @@ public final class DomainPricingLogic {
|
||||
|| token.getRenewalPriceBehavior().equals(RenewalPriceBehavior.NONPREMIUM)) {
|
||||
return tld.getStandardRenewCost(dateTime).multipliedBy(years);
|
||||
}
|
||||
if (token.getRenewalPriceBehavior().equals(RenewalPriceBehavior.SPECIFIED)) {
|
||||
return token.getRenewalPrice().get();
|
||||
}
|
||||
}
|
||||
return getDomainCostWithDiscount(
|
||||
domainPrices.isPremium(),
|
||||
|
||||
@@ -21,6 +21,7 @@ import jakarta.persistence.Query;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.metamodel.Metamodel;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/** Sub-interface of {@link TransactionManager} which defines JPA related methods. */
|
||||
public interface JpaTransactionManager extends TransactionManager {
|
||||
@@ -92,4 +93,15 @@ public interface JpaTransactionManager extends TransactionManager {
|
||||
|
||||
/** Return the {@link TransactionIsolationLevel} used in the current transaction. */
|
||||
TransactionIsolationLevel getCurrentTransactionIsolationLevel();
|
||||
|
||||
/** Executes the work with the given isolation level, possibly logging all SQL statements used. */
|
||||
<T> T transact(
|
||||
TransactionIsolationLevel isolationLevel, Callable<T> work, boolean logSqlStatements);
|
||||
|
||||
/**
|
||||
* Executes the work with the given isolation level without retry, possibly logging all SQL
|
||||
* statements used.
|
||||
*/
|
||||
<T> T transactNoRetry(
|
||||
TransactionIsolationLevel isolationLevel, Callable<T> work, boolean logSqlStatements);
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
import javax.annotation.Nullable;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
@@ -89,6 +90,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
private static final String NESTED_TRANSACTION_MESSAGE =
|
||||
"Nested transaction detected. Try refactoring to avoid nested transactions. If unachievable,"
|
||||
+ " use reTransact() in nested transactions";
|
||||
private static final String SQL_STATEMENT_LOG_SENTINEL_FORMAT = "SQL_STATEMENT_LOG: %s";
|
||||
|
||||
// EntityManagerFactory is thread safe.
|
||||
private final EntityManagerFactory emf;
|
||||
@@ -174,6 +176,12 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
|
||||
@Override
|
||||
public <T> T transact(TransactionIsolationLevel isolationLevel, Callable<T> work) {
|
||||
return transact(isolationLevel, work, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T transact(
|
||||
TransactionIsolationLevel isolationLevel, Callable<T> work, boolean logSqlStatements) {
|
||||
if (inTransaction()) {
|
||||
if (!getHibernateAllowNestedTransactions()) {
|
||||
throw new IllegalStateException(NESTED_TRANSACTION_MESSAGE);
|
||||
@@ -186,7 +194,8 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
return transactNoRetry(isolationLevel, work);
|
||||
}
|
||||
return retrier.callWithRetry(
|
||||
() -> transactNoRetry(isolationLevel, work), JpaRetries::isFailedTxnRetriable);
|
||||
() -> transactNoRetry(isolationLevel, work, logSqlStatements),
|
||||
JpaRetries::isFailedTxnRetriable);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -202,6 +211,14 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
@Override
|
||||
public <T> T transactNoRetry(
|
||||
@Nullable TransactionIsolationLevel isolationLevel, Callable<T> work) {
|
||||
return transactNoRetry(isolationLevel, work, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T transactNoRetry(
|
||||
@Nullable TransactionIsolationLevel isolationLevel,
|
||||
Callable<T> work,
|
||||
boolean logSqlStatements) {
|
||||
if (inTransaction()) {
|
||||
// This check will no longer be necessary when the transact() method always throws
|
||||
// inside a nested transaction, as the only way to pass a non-null isolation level
|
||||
@@ -224,7 +241,18 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
}
|
||||
}
|
||||
TransactionInfo txnInfo = transactionInfo.get();
|
||||
txnInfo.entityManager = emf.createEntityManager();
|
||||
|
||||
txnInfo.entityManager =
|
||||
logSqlStatements
|
||||
? emf.unwrap(SessionFactory.class)
|
||||
.withOptions()
|
||||
.statementInspector(
|
||||
s -> {
|
||||
logger.atInfo().log(SQL_STATEMENT_LOG_SENTINEL_FORMAT, s);
|
||||
return s;
|
||||
})
|
||||
.openSession()
|
||||
: emf.createEntityManager();
|
||||
if (readOnly) {
|
||||
// Disable Hibernate's dirty object check on flushing, it has become more aggressive in v6.
|
||||
txnInfo.entityManager.unwrap(Session.class).setDefaultReadOnly(true);
|
||||
|
||||
@@ -138,7 +138,7 @@ public final class DomainLockUtils {
|
||||
createLockBuilder(domainName, registrarId, registrarPocId, isAdmin)
|
||||
.setLockCompletionTime(now)
|
||||
.build());
|
||||
tm().transact(() -> applyLockStatuses(newLock, now, isAdmin));
|
||||
applyLockStatuses(newLock, now, isAdmin);
|
||||
setAsRelock(newLock);
|
||||
return newLock;
|
||||
});
|
||||
@@ -160,7 +160,7 @@ public final class DomainLockUtils {
|
||||
createUnlockBuilder(domainName, registrarId, isAdmin, relockDuration)
|
||||
.setUnlockCompletionTime(now)
|
||||
.build());
|
||||
tm().transact(() -> removeLockStatuses(result, isAdmin, now));
|
||||
removeLockStatuses(result, isAdmin, now);
|
||||
return result;
|
||||
});
|
||||
// Submit relock outside the transaction to make sure that it fully succeeded
|
||||
|
||||
@@ -16,11 +16,9 @@ package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.Iterables.partition;
|
||||
import static google.registry.model.eppcommon.StatusValue.SERVER_DELETE_PROHIBITED;
|
||||
import static google.registry.model.eppcommon.StatusValue.SERVER_TRANSFER_PROHIBITED;
|
||||
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.findDuplicates;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
@@ -38,8 +36,6 @@ public abstract class LockOrUnlockDomainCommand extends ConfirmingCommand {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private static final int BATCH_SIZE = 10;
|
||||
|
||||
public static final ImmutableSet<StatusValue> REGISTRY_LOCK_STATUSES =
|
||||
ImmutableSet.of(
|
||||
SERVER_DELETE_PROHIBITED, SERVER_TRANSFER_PROHIBITED, SERVER_UPDATE_PROHIBITED);
|
||||
@@ -79,26 +75,16 @@ public abstract class LockOrUnlockDomainCommand extends ConfirmingCommand {
|
||||
protected String execute() {
|
||||
ImmutableSet.Builder<String> successfulDomainsBuilder = new ImmutableSet.Builder<>();
|
||||
ImmutableMap.Builder<String, String> failedDomainsToReasons = new ImmutableMap.Builder<>();
|
||||
partition(getDomains(), BATCH_SIZE)
|
||||
.forEach(
|
||||
batch ->
|
||||
// we require that the jpaTm is the outer transaction in DomainLockUtils
|
||||
tm().transact(
|
||||
() ->
|
||||
tm().transact(
|
||||
() -> {
|
||||
for (String domain : batch) {
|
||||
try {
|
||||
createAndApplyRequest(domain);
|
||||
} catch (Throwable t) {
|
||||
logger.atSevere().withCause(t).log(
|
||||
"Error when (un)locking domain %s.", domain);
|
||||
failedDomainsToReasons.put(domain, t.getMessage());
|
||||
continue;
|
||||
}
|
||||
successfulDomainsBuilder.add(domain);
|
||||
}
|
||||
})));
|
||||
for (String domain : getDomains()) {
|
||||
try {
|
||||
createAndApplyRequest(domain);
|
||||
} catch (Throwable t) {
|
||||
logger.atSevere().withCause(t).log("Error when (un)locking domain %s.", domain);
|
||||
failedDomainsToReasons.put(domain, t.getMessage());
|
||||
continue;
|
||||
}
|
||||
successfulDomainsBuilder.add(domain);
|
||||
}
|
||||
ImmutableSet<String> successfulDomains = successfulDomainsBuilder.build();
|
||||
ImmutableSet<String> failedDomains =
|
||||
failedDomainsToReasons.build().entrySet().stream()
|
||||
|
||||
@@ -56,7 +56,7 @@ import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
public abstract class ResourceFlowTestCase<F extends Flow, R extends EppResource>
|
||||
extends FlowTestCase<F> {
|
||||
|
||||
private final TestLogHandler logHandler = new TestLogHandler();
|
||||
protected final TestLogHandler logHandler = new TestLogHandler();
|
||||
|
||||
@RegisterExtension
|
||||
public final TestCacheExtension testCacheExtension =
|
||||
|
||||
@@ -102,8 +102,10 @@ import google.registry.model.transfer.TransferResponse;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.testing.CloudTasksHelper.TaskMatcher;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.LogsSubject;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
@@ -1258,4 +1260,17 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_pending.xml"));
|
||||
assertPollMessages();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_logsSqlStatements() throws Exception {
|
||||
setUpSuccessfulTest();
|
||||
runFlowAssertResponse(loadFile("domain_delete_response_pending.xml"));
|
||||
LogsSubject.assertAboutLogs()
|
||||
.that(logHandler)
|
||||
.hasLogAtLevelWithMessage(
|
||||
Level.INFO,
|
||||
"SQL_STATEMENT_LOG: insert into \"DomainHistory\" (history_by_superuser,"
|
||||
+ "history_registrar_id,history_modification_time,history_other_registrar_id,"
|
||||
+ "history_period_unit,history_period_value,history_reason,history");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1242,4 +1242,23 @@ public class DomainPricingLogicTest {
|
||||
.addFeeOrCredit(Fee.create(new BigDecimal("120.00"), CREATE, true))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDomainRenewPrice_specifiedToken() throws Exception {
|
||||
AllocationToken allocationToken =
|
||||
persistResource(
|
||||
new AllocationToken.Builder()
|
||||
.setToken("abc123")
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setDomainName("premium.example")
|
||||
.setRenewalPriceBehavior(SPECIFIED)
|
||||
.setRenewalPrice(Money.of(USD, 5))
|
||||
.build());
|
||||
assertThat(
|
||||
domainPricingLogic
|
||||
.getRenewPrice(
|
||||
tld, "premium.example", clock.nowUtc(), 1, null, Optional.of(allocationToken))
|
||||
.getRenewCost())
|
||||
.isEqualTo(Money.of(USD, 5));
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 74 KiB |
@@ -1,349 +1,607 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "Registry Console API",
|
||||
"version": "1.0.0"
|
||||
"version": "1.0.0",
|
||||
"title": "Console API",
|
||||
"description": "API for managing registrars, user data, registry locks, domain lists, users, and contact settings."
|
||||
},
|
||||
"basePath": "/console-api",
|
||||
"schemes": [
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/console-api/user": {
|
||||
"/registrars": {
|
||||
"get": {
|
||||
"summary": "Provides user type to UI",
|
||||
"responses": {
|
||||
"200": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum":[
|
||||
"ADMIN",
|
||||
"USER"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/console-api/poll": {
|
||||
"post": {
|
||||
"summary": "Every T minutes (1?) request/response to keep front-end in sync with the back-end",
|
||||
"parameters": [{
|
||||
"name": "version",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}, {
|
||||
"name": "registrarId",
|
||||
"in": "body",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}, {
|
||||
"name": "notificationId",
|
||||
"in": "body",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}],
|
||||
"summary": "Retrieve a list of registrars",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful retrieval of registrars.",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"notificationText": {
|
||||
"type": "string"
|
||||
},
|
||||
"notificationId": {
|
||||
"type": "string"
|
||||
},
|
||||
"dismissable": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
"$ref": "#/definitions/Registrar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/console-api/registrars": {
|
||||
"/userdata": {
|
||||
"get": {
|
||||
"summary": "Provides list of available registrars to UI",
|
||||
"summary": "Retrieve user data",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful retrieval of user data.",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"registrarId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
"$ref": "#/definitions/UserData"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/console-api/domains": {
|
||||
"/registry-lock": {
|
||||
"get": {
|
||||
"summary": "GET list of domains per registrar with pagination and filters",
|
||||
"parameters": [{
|
||||
"name": "registrarId",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}, {
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "number",
|
||||
"default": 10
|
||||
}, {
|
||||
"name": "marker",
|
||||
"description": "Name of the last domain retrieved by the user",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}, {
|
||||
"name": "filterBy",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"content": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"tld": {
|
||||
"type":"array",
|
||||
"items": {
|
||||
"type":"string"
|
||||
}
|
||||
},
|
||||
"status":{
|
||||
"type":"array",
|
||||
"items": {
|
||||
"type":"string"
|
||||
}
|
||||
},
|
||||
"isLocked": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Provides list of domains per registrar",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"tld": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum":[
|
||||
"CLIENT_DELETE_PROHIBITED",
|
||||
"CLIENT_HOLD",
|
||||
"CLIENT_RENEW_PROHIBITED",
|
||||
"CLIENT_TRANSFER_PROHIBITED",
|
||||
"CLIENT_UPDATE_PROHIBITED",
|
||||
"INACTIVE",
|
||||
"LINKED",
|
||||
"OK",
|
||||
"PENDING_CREATE",
|
||||
"PENDING_DELETE",
|
||||
"PENDING_TRANSFER",
|
||||
"PENDING_UPDATE",
|
||||
"SERVER_DELETE_PROHIBITED",
|
||||
"SERVER_HOLD",
|
||||
"SERVER_RENEW_PROHIBITED",
|
||||
"SERVER_TRANSFER_PROHIBITED",
|
||||
"SERVER_UPDATE_PROHIBITED"
|
||||
]
|
||||
}
|
||||
},
|
||||
"isLocked": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"creationTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"expirationTime": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/console-api/domain": {
|
||||
"post": {
|
||||
"summary": "Apply action on a domain",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Action has been succesfully applied"
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "domain",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
},
|
||||
{
|
||||
"name": "registrarId",
|
||||
"in": "body",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
},
|
||||
{
|
||||
"name": "action",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/console-api/settings/contacts/get": {
|
||||
"get": {
|
||||
"summary": "GET settings contact page",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Provides list of contacts per registrar",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/settingsContacts"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "Retrieve registry lock information",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "registrarId",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
"description": "Unique identifier for the registrar.",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/console-api/settings/contacts/upd": {
|
||||
"post": {
|
||||
"summary": "Creates, updates or delete registrar contacts",
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful retrieval of registry lock information.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/settingsContacts"
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/RegistryLock"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"/domain-list": {
|
||||
"get": {
|
||||
"summary": "Retrieve a list of domains",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "registrarId",
|
||||
"in": "body",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "string"
|
||||
"in": "query",
|
||||
"description": "Unique identifier for the registrar.",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "contacts",
|
||||
"in": "body",
|
||||
"name": "resultsPerPage",
|
||||
"in": "query",
|
||||
"description": "Number of results per page.",
|
||||
"required": true,
|
||||
"type": "array",
|
||||
"type": "integer"
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful retrieval of domain list.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/settingsContacts"
|
||||
"$ref": "#/definitions/DomainList"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users": {
|
||||
"get": {
|
||||
"summary": "Retrieve a list of users",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "registrarId",
|
||||
"in": "query",
|
||||
"description": "Unique identifier for the registrar.",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful retrieval of users.",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/User"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/contacts": {
|
||||
"get": {
|
||||
"summary": "Retrieve contact settings",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "registrarId",
|
||||
"in": "query",
|
||||
"description": "Unique identifier for the registrar.",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful retrieval of contact settings.",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ContactSetting"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"summary": "Update contact settings",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"description": "Contact settings to update.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ContactSetting"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Contact settings updated successfully."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/whois-fields": {
|
||||
"post": {
|
||||
"summary": "Update WHOIS fields",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"description": "WHOIS fields to update.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Registrar"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "WHOIS fields updated successfully."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"definitions": {
|
||||
"settingsContacts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"Registrar": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"registrarId": {
|
||||
"type": "string",
|
||||
"description": "Unique identifier for the registrar."
|
||||
},
|
||||
"registrarName": {
|
||||
"type": "string",
|
||||
"description": "Name of the registrar."
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Type of registrar (e.g., OTE, REAL)."
|
||||
},
|
||||
"allowedTlds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailAddress": {
|
||||
"description": "List of allowed Top-Level Domains."
|
||||
},
|
||||
"ipAddressAllowList": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"registrarId": {
|
||||
"description": "List of allowed IP addresses."
|
||||
},
|
||||
"localizedAddress": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Street address."
|
||||
},
|
||||
"city": {
|
||||
"type": "string",
|
||||
"description": "City."
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": "State."
|
||||
},
|
||||
"zip": {
|
||||
"type": "string",
|
||||
"description": "Zip code."
|
||||
},
|
||||
"countryCode": {
|
||||
"type": "string",
|
||||
"description": "Country code."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"street",
|
||||
"city",
|
||||
"state",
|
||||
"zip",
|
||||
"countryCode"
|
||||
]
|
||||
},
|
||||
"phoneNumber": {
|
||||
"type": "string",
|
||||
"description": "Phone number."
|
||||
},
|
||||
"faxNumber": {
|
||||
"type": "string",
|
||||
"description": "Fax number."
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string",
|
||||
"description": "Email address."
|
||||
},
|
||||
"icannReferralEmail": {
|
||||
"type": "string",
|
||||
"description": "ICANN referral email address."
|
||||
},
|
||||
"registryLockAllowed": {
|
||||
"type": "boolean",
|
||||
"description": "Whether registry lock is allowed."
|
||||
},
|
||||
"clientCertificate": {
|
||||
"type": "string",
|
||||
"description": "Client certificate (if applicable)."
|
||||
},
|
||||
"failoverClientCertificate": {
|
||||
"type": "string",
|
||||
"description": "Failover client certificate (if applicable)."
|
||||
},
|
||||
"whoisServer": {
|
||||
"type": "string",
|
||||
"description": "WHOIS server (if applicable)."
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "URL (if applicable)."
|
||||
},
|
||||
"ianaIdentifier": {
|
||||
"type": "integer",
|
||||
"description": "IANA identifier (if applicable)."
|
||||
},
|
||||
"billingAccountMap": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"registryLockEmailAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"phoneNumber": {
|
||||
"type": "string"
|
||||
},
|
||||
"faxNumber": {
|
||||
"type": "string"
|
||||
},
|
||||
"types": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ABUSE",
|
||||
"ADMIN",
|
||||
"BILLING",
|
||||
"LEGAL",
|
||||
"MARKETING",
|
||||
"TECH",
|
||||
"WHOIS"
|
||||
]
|
||||
},
|
||||
"visibleInWhoisAsAdmin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"visibleInWhoisAsTech": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"visibleInDomainWhoisAsAbuse": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"allowedToSetRegistryLockPassword": {
|
||||
"type": "boolean"
|
||||
"description": "Mapping of billing accounts."
|
||||
},
|
||||
"driveFolderId": {
|
||||
"type": "string",
|
||||
"description": "Google Drive folder ID (if applicable)."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"registrarId",
|
||||
"registrarName",
|
||||
"type",
|
||||
"allowedTlds",
|
||||
"localizedAddress",
|
||||
"emailAddress",
|
||||
"icannReferralEmail",
|
||||
"registryLockAllowed"
|
||||
]
|
||||
},
|
||||
"UserData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"userRoles": {
|
||||
"type": "object",
|
||||
"description": "User roles (currently empty)."
|
||||
},
|
||||
"supportEmail": {
|
||||
"type": "string",
|
||||
"description": "Support email address."
|
||||
},
|
||||
"supportPhoneNumber": {
|
||||
"type": "string",
|
||||
"description": "Support phone number."
|
||||
},
|
||||
"globalRole": {
|
||||
"type": "string",
|
||||
"description": "Global role of the user."
|
||||
},
|
||||
"isAdmin": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the user is an admin."
|
||||
},
|
||||
"productName": {
|
||||
"type": "string",
|
||||
"description": "Name of the product."
|
||||
},
|
||||
"technicalDocsUrl": {
|
||||
"type": "string",
|
||||
"description": "URL for technical documentation."
|
||||
}
|
||||
}
|
||||
},
|
||||
"RegistryLock": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domainName": {
|
||||
"type": "string",
|
||||
"description": "Name of the domain."
|
||||
},
|
||||
"registrarPocId": {
|
||||
"type": "string",
|
||||
"description": "Registrar point of contact ID."
|
||||
},
|
||||
"lockRequestTime": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"creationTime": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Time of lock request."
|
||||
}
|
||||
}
|
||||
},
|
||||
"unlockRequestTime": {
|
||||
"type": "null",
|
||||
"description": "Time of unlock request (null if not requested)."
|
||||
},
|
||||
"lockCompletionTime": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Time of lock completion."
|
||||
},
|
||||
"unlockCompletionTime": {
|
||||
"type": "null",
|
||||
"description": "Time of unlock completion (null if not completed)."
|
||||
},
|
||||
"isSuperuser": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the user is a superuser."
|
||||
}
|
||||
}
|
||||
},
|
||||
"DomainList": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domains": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Domain"
|
||||
},
|
||||
"description": "List of domains."
|
||||
},
|
||||
"checkpointTime": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Checkpoint time for the domain list."
|
||||
},
|
||||
"totalResults": {
|
||||
"type": "integer",
|
||||
"description": "Total number of results."
|
||||
}
|
||||
}
|
||||
},
|
||||
"Domain": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domainName": {
|
||||
"type": "string",
|
||||
"description": "Name of the domain."
|
||||
},
|
||||
"adminContact": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "Key for the admin contact."
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"description": "Kind of admin contact."
|
||||
}
|
||||
}
|
||||
},
|
||||
"techContact": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "Key for the tech contact."
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"description": "Kind of tech contact."
|
||||
}
|
||||
}
|
||||
},
|
||||
"registrantContact": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "Key for the registrant contact."
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"description": "Kind of registrant contact."
|
||||
}
|
||||
}
|
||||
},
|
||||
"registrationExpirationTime": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Expiration time for domain registration."
|
||||
},
|
||||
"lastTransferTime": {
|
||||
"type": "null",
|
||||
"description": "Time of last transfer (null if not transferred)."
|
||||
},
|
||||
"repoId": {
|
||||
"type": "string",
|
||||
"description": "Repository ID."
|
||||
},
|
||||
"currentSponsorRegistrarId": {
|
||||
"type": "string",
|
||||
"description": "Current sponsor registrar ID."
|
||||
},
|
||||
"creationRegistrarId": {
|
||||
"type": "string",
|
||||
"description": "Creation registrar ID."
|
||||
},
|
||||
"lastEppUpdateRegistrarId": {
|
||||
"type": "string",
|
||||
"description": "Last EPP update registrar ID."
|
||||
},
|
||||
"creationTime": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"creationTime": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Creation time."
|
||||
}
|
||||
}
|
||||
},
|
||||
"lastEppUpdateTime": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Last EPP update time."
|
||||
},
|
||||
"statuses": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Domain statuses."
|
||||
},
|
||||
"nsHosts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "Key for the name server host."
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"description": "Kind of name server host."
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Name server hosts."
|
||||
}
|
||||
}
|
||||
},
|
||||
"User": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"emailAddress": {
|
||||
"type": "string",
|
||||
"description": "Email address of the user."
|
||||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
"description": "Role of the user."
|
||||
}
|
||||
}
|
||||
},
|
||||
"ContactSetting": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the contact."
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string",
|
||||
"description": "Email address of the contact."
|
||||
},
|
||||
"registrarId": {
|
||||
"type": "string",
|
||||
"description": "Registrar ID."
|
||||
},
|
||||
"phoneNumber": {
|
||||
"type": "string",
|
||||
"description": "Phone number of the contact."
|
||||
},
|
||||
"types": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Contact types."
|
||||
},
|
||||
"visibleInWhoisAsAdmin": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the contact is visible in WHOIS as admin."
|
||||
},
|
||||
"visibleInWhoisAsTech": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the contact is visible in WHOIS as tech."
|
||||
},
|
||||
"visibleInDomainWhoisAsAbuse": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the contact is visible in domain WHOIS as abuse."
|
||||
},
|
||||
"userFriendlyTypes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "User-friendly contact types."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ steps:
|
||||
|
||||
gcloud auth activate-service-account --key-file=tool-credential.json
|
||||
|
||||
for service in default pubapi backend bsa tools
|
||||
for service in default pubapi backend bsa tools console
|
||||
do
|
||||
for version in $(gcloud app versions list \
|
||||
--filter="SERVICE:$service AND SERVING_STATUS:STOPPED" \
|
||||
|
||||
@@ -10,21 +10,21 @@
|
||||
# https://cloud.google.com/cloud-build/docs/running-builds/automate-builds
|
||||
steps:
|
||||
# Compile javadoc
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder'
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:live'
|
||||
entrypoint: /bin/bash
|
||||
args: ['./gradlew', ':javadoc']
|
||||
# Upload the files to GCS
|
||||
# We don't use GCB's built-in artifacts uploader because we want to delete
|
||||
# the existing files in the bucket first, and we want to parallelize the
|
||||
# uploading process.
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder'
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:live'
|
||||
entrypoint: /bin/bash
|
||||
args: ['gsutil', '-m', 'rsync', '-d', '-r', 'build/docs/javadoc', 'gs://${PROJECT_ID}-javadoc']
|
||||
# Upload the files to GCS
|
||||
# We don't use GCB's built-in artifacts uploader because we want to delete
|
||||
# the existing files in the bucket first, and we want to parallelize the
|
||||
# uploading process.
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder'
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:live'
|
||||
entrypoint: /bin/bash
|
||||
args: ['gsutil', '-m', 'rsync', '-d', '-r', 'db/src/main/resources/sql/er_diagram',
|
||||
'gs://${PROJECT_ID}-er-diagram']
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# https://cloud.google.com/cloud-build/docs/running-builds/automate-builds
|
||||
steps:
|
||||
# Download Kythe
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:live'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
- -c
|
||||
@@ -20,7 +20,7 @@ steps:
|
||||
rm kythe-${_KYTHE_VERSION}.tar.gz
|
||||
mv kythe-${_KYTHE_VERSION} kythe
|
||||
# Build Nomulus with the Kythe wrapper
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:live'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
- -c
|
||||
@@ -44,7 +44,7 @@ steps:
|
||||
./gradlew clean testClasses \
|
||||
-Dno_werror=true -PenableCrossReferencing=true
|
||||
# Merge kzip files
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:live'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
- -c
|
||||
@@ -53,13 +53,11 @@ steps:
|
||||
./kythe/tools/kzip merge \
|
||||
--output $${KYTHE_OUTPUT_DIRECTORY}/merged/${COMMIT_SHA}.kzip \
|
||||
$${KYTHE_OUTPUT_DIRECTORY}/*.kzip
|
||||
|
||||
artifacts:
|
||||
objects:
|
||||
location: 'gs://${PROJECT_ID}-codesearch'
|
||||
paths:
|
||||
- 'kythe_output/merged/${COMMIT_SHA}.kzip'
|
||||
|
||||
timeout: 3600s
|
||||
options:
|
||||
machineType: 'E2_HIGHCPU_32'
|
||||
|
||||
@@ -36,18 +36,53 @@ steps:
|
||||
# informational. It makes it easier to tell the tag of the current 'live' release.
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
|
||||
entrypoint: /bin/bash
|
||||
args: [ '-c', 'echo ${TAG_NAME} > output/tag_name']
|
||||
# Tag the tool image and push it to the registry.
|
||||
args: [ '-c', 'echo ${TAG_NAME} > output/tag_name' ]
|
||||
# Build Nomulus, tool and proxy image, them upload them to GCR.
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
|
||||
# Set home for Gradle caches. Must be consistent with last step below
|
||||
# and ./build_nomulus_for_env.sh
|
||||
env: [ 'GRADLE_USER_HOME=/workspace/cloudbuild-caches' ]
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
./gradlew :jetty:buildNomulusImage :proxy:buildProxyImage :core:buildToolImage\
|
||||
-PmavenUrl=gcs://domain-registry-maven-repository/maven \
|
||||
-PpluginsUrl=gcs://domain-registry-maven-repository/plugins
|
||||
docker tag nomulus gcr.io/${PROJECT_ID}/nomulus:${TAG_NAME}
|
||||
docker tag nomulus gcr.io/${PROJECT_ID}/nomulus:latest
|
||||
docker push gcr.io/${PROJECT_ID}/nomulus:${TAG_NAME}
|
||||
docker push gcr.io/${PROJECT_ID}/nomulus:latest
|
||||
docker tag proxy gcr.io/${PROJECT_ID}/proxy:${TAG_NAME}
|
||||
docker tag proxy gcr.io/${PROJECT_ID}/proxy:latest
|
||||
docker push gcr.io/${PROJECT_ID}/proxy:${TAG_NAME}
|
||||
docker push gcr.io/${PROJECT_ID}/proxy:latest
|
||||
docker tag nomulus-tool gcr.io/${PROJECT_ID}/nomulus-tool:${TAG_NAME}
|
||||
docker tag nomulus-tool gcr.io/${PROJECT_ID}/nomulus-tool:latest
|
||||
docker push gcr.io/${PROJECT_ID}/nomulus-tool:${TAG_NAME}
|
||||
docker push gcr.io/${PROJECT_ID}/nomulus-tool:latest
|
||||
# Sign nomulus and proxy images.
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
docker tag nomulus-tool gcr.io/${PROJECT_ID}/nomulus-tool:${TAG_NAME}
|
||||
docker tag nomulus-tool gcr.io/${PROJECT_ID}/nomulus-tool:latest
|
||||
docker push gcr.io/${PROJECT_ID}/nomulus-tool:${TAG_NAME}
|
||||
docker push gcr.io/${PROJECT_ID}/nomulus-tool:latest
|
||||
nomulus_digest=$(gcloud container images list-tags gcr.io/${PROJECT_ID}/nomulus \
|
||||
--format="get(digest)" --filter="tags = ${TAG_NAME}")
|
||||
proxy_digest=$(gcloud container images list-tags gcr.io/${PROJECT_ID}/proxy \
|
||||
--format="get(digest)" --filter="tags = ${TAG_NAME}")
|
||||
gcloud --project=${PROJECT_ID} beta container binauthz attestations \
|
||||
sign-and-create --artifact-url=gcr.io/${PROJECT_ID}/nomulus@$nomulus_digest \
|
||||
--attestor=build-attestor --attestor-project=${PROJECT_ID} \
|
||||
--keyversion-project=${PROJECT_ID} --keyversion-location=global \
|
||||
--keyversion-keyring=attestor-keys --keyversion-key=signing \
|
||||
--keyversion=1
|
||||
gcloud --project=${PROJECT_ID} beta container binauthz attestations \
|
||||
sign-and-create --artifact-url=gcr.io/${PROJECT_ID}/proxy@$proxy_digest \
|
||||
--attestor=build-attestor --attestor-project=${PROJECT_ID} \
|
||||
--keyversion-project=${PROJECT_ID} --keyversion-location=global \
|
||||
--keyversion-keyring=attestor-keys --keyversion-key=signing \
|
||||
--keyversion=1
|
||||
# Get the tool image digest and substitute in the digest in other GCB files.
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
|
||||
entrypoint: /bin/bash
|
||||
@@ -57,14 +92,11 @@ steps:
|
||||
set -e
|
||||
digest=$(gcloud container images list-tags gcr.io/${PROJECT_ID}/nomulus-tool \
|
||||
--format="get(digest)" --filter="tags = ${TAG_NAME}")
|
||||
sed -i s/'$${_IMAGE}'/nomulus-tool/g release/cloudbuild-tag.yaml
|
||||
sed -i s/':$${TAG_NAME}'/@$digest/g release/cloudbuild-tag.yaml
|
||||
sed -i s/'nomulus-tool:latest'/nomulus-tool@$digest/g release/cloudbuild-deploy-*.yaml
|
||||
# schema-deploy and schema-verify scripts
|
||||
sed -i s/'nomulus-tool:latest'/nomulus-tool@$digest/g release/cloudbuild-schema-*.yaml
|
||||
# Build the prober_cert_updater image and upload it to GCR. This image extends
|
||||
# from the `builder` and the nomulus.jar built earlier.
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
sed -i s/nomulus-tool:latest/nomulus-tool@$digest/g release/cloudbuild-schema-*.yaml
|
||||
# Build and upload the prober_cert_updater image. This image extends from the `builder` and the
|
||||
# nomulus.jar built earlier.
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
- -c
|
||||
@@ -76,24 +108,12 @@ steps:
|
||||
--build-arg TAG_NAME=${TAG_NAME} --build-arg PROJECT_ID=${PROJECT_ID} .
|
||||
docker tag gcr.io/${PROJECT_ID}/prober_cert_updater:${TAG_NAME} \
|
||||
gcr.io/${PROJECT_ID}/prober_cert_updater:latest
|
||||
docker push gcr.io/${PROJECT_ID}/prober_cert_updater:latest
|
||||
docker push gcr.io/${PROJECT_ID}/prober_cert_updater:${TAG_NAME}
|
||||
docker push gcr.io/${PROJECT_ID}/prober_cert_updater:latest
|
||||
dir: 'release/prober-cert-updater/'
|
||||
# Update the prober_updater image digest in relevant GCB files.
|
||||
# Build and upload the db_object_updater image. This image extends from the `builder` and the
|
||||
# nomulus.jar built earlier.
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
digest=$(gcloud container images list-tags \
|
||||
gcr.io/${PROJECT_ID}/prober_cert_updater \
|
||||
--format="get(digest)" --filter="tags = ${TAG_NAME}")
|
||||
sed -i s/'prober_cert_updater:latest'/prober_cert_updater@$digest/g \
|
||||
release/cloudbuild-renew-prober-certs-*.yaml
|
||||
# Build the db_object_updater image and upload it to GCR. This image extends
|
||||
# from the `builder` and the nomulus.jar built earlier.
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
- -c
|
||||
@@ -105,25 +125,13 @@ steps:
|
||||
--build-arg TAG_NAME=${TAG_NAME} --build-arg PROJECT_ID=${PROJECT_ID} .
|
||||
docker tag gcr.io/${PROJECT_ID}/db_object_updater:${TAG_NAME} \
|
||||
gcr.io/${PROJECT_ID}/db_object_updater:latest
|
||||
docker push gcr.io/${PROJECT_ID}/db_object_updater:latest
|
||||
docker push gcr.io/${PROJECT_ID}/db_object_updater:${TAG_NAME}
|
||||
docker push gcr.io/${PROJECT_ID}/db_object_updater:latest
|
||||
dir: 'release/db-object-updater/'
|
||||
# Update the db_object_updater image digest in relevant GCB files.
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
digest=$(gcloud container images list-tags \
|
||||
gcr.io/${PROJECT_ID}/db_object_updater \
|
||||
--format="get(digest)" --filter="tags = ${TAG_NAME}")
|
||||
sed -i s/'db_object_updater:latest'/db_object_updater@$digest/g \
|
||||
release/cloudbuild-sync-db-objects-*.yaml
|
||||
# Build and stage Dataflow Flex templates.
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
|
||||
entrypoint: /bin/bash
|
||||
# Set home for Gradle caches. Must be consistent with previous steps above
|
||||
# Set home for Gradle caches. Must be consistent with the previous steps above
|
||||
# and ./build_nomulus_for_env.sh
|
||||
env: [ 'GRADLE_USER_HOME=/workspace/cloudbuild-caches' ]
|
||||
args:
|
||||
@@ -146,9 +154,8 @@ steps:
|
||||
google/registry/beam/resave_all_epp_resources_pipeline_metadata.json \
|
||||
google.registry.beam.wipeout.WipeOutContactHistoryPiiPipeline \
|
||||
google/registry/beam/wipe_out_contact_history_pii_pipeline_metadata.json
|
||||
# Tentatively build and publish Cloud SQL schema jar here, before schema release
|
||||
# process is finalized. Also publish nomulus:core jars that are needed for
|
||||
# server/schema compatibility tests.
|
||||
# Tentatively build and publish Cloud SQL schema jar here, before the schema release process is
|
||||
# finalized. Also publish nomulus:core jars that are needed for server/schema compatibility tests.
|
||||
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
|
||||
entrypoint: /bin/bash
|
||||
# Set home for Gradle caches. Must be consistent with previous steps above
|
||||
@@ -183,15 +190,18 @@ artifacts:
|
||||
- 'output/tag_name'
|
||||
- 'output/nomulus.jar'
|
||||
- 'output/schema.jar'
|
||||
- 'release/cloudbuild-tag.yaml'
|
||||
- 'release/cloudbuild-sync.yaml'
|
||||
- 'release/cloudbuild-sync-and-tag.yaml'
|
||||
- 'release/cloudbuild-deploy-*.yaml'
|
||||
- 'release/cloudbuild-delete-*.yaml'
|
||||
- 'release/cloudbuild-renew-prober-certs-*.yaml'
|
||||
- 'release/cloudbuild-schema-deploy-*.yaml'
|
||||
- 'release/cloudbuild-schema-verify-*.yaml'
|
||||
- 'release/cloudbuild-sync-db-objects-*.yaml'
|
||||
|
||||
- 'jetty/kubernetes/*.yaml'
|
||||
- 'jetty/kubernetes/gateway/*.yaml'
|
||||
# The images are already uploaded, but we still need to include them there so that
|
||||
# the GCB pubsub message contains them (for Spinnaker to consume).
|
||||
images:
|
||||
- 'gcr.io/${PROJECT_ID}/nomulus:${TAG_NAME}'
|
||||
- 'gcr.io/${PROJECT_ID}/proxy:${TAG_NAME}'
|
||||
timeout: 7200s
|
||||
options:
|
||||
machineType: 'E2_HIGHCPU_32'
|
||||
machineType: 'E2_HIGHCPU_32'
|
||||
@@ -43,8 +43,6 @@ steps:
|
||||
--keyversion-project=${PROJECT_ID} --keyversion-location=global \
|
||||
--keyversion-keyring=attestor-keys --keyversion-key=signing \
|
||||
--keyversion=1
|
||||
sed -i s/'$${_IMAGE}'/proxy/g release/cloudbuild-tag.yaml
|
||||
sed -i s/':$${TAG_NAME}'/@$digest/g release/cloudbuild-tag.yaml
|
||||
# Images to upload to GCR. Even though the image has already been uploaded, we still include it
|
||||
# here so that the GCB pubsub message contains it (for Spinnaker to consume).
|
||||
images: ['gcr.io/${PROJECT_ID}/proxy:${TAG_NAME}']
|
||||
@@ -55,7 +53,6 @@ artifacts:
|
||||
# This cannot be regexs because of how Spinnaker constructs artifact paths.
|
||||
paths:
|
||||
- 'proxy/kubernetes/proxy-*.yaml'
|
||||
- 'release/cloudbuild-tag.yaml'
|
||||
timeout: 3600s
|
||||
options:
|
||||
machineType: 'E2_HIGHCPU_32'
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#
|
||||
# This pipeline prepares a release. The pipeline should be run against the Nomulus public repo on
|
||||
# GitHub. It builds the builder and base images, and hard codes the sha256 hashes of the resulting
|
||||
# images in the merged code base (internal + public) , which is tagged and pushed into the release
|
||||
# images in the merged code base (internal + public), which is tagged and pushed into the release
|
||||
# repo. Actual release artifacts are built from the release repo, ensuring reproducibility.
|
||||
steps:
|
||||
# Check the out internal repo.
|
||||
@@ -42,7 +42,7 @@ steps:
|
||||
rm -rf .git && rm -rf nomulus-internal/.git
|
||||
cp -rf nomulus-internal/* .
|
||||
rm -rf nomulus-internal
|
||||
# Build the builder image and tag the proxy base image, then upload them to GCR.
|
||||
# Build the builder image and pull the base images, them upload them to GCR.
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
@@ -51,18 +51,18 @@ steps:
|
||||
set -e
|
||||
docker build -t gcr.io/${PROJECT_ID}/builder:${TAG_NAME} .
|
||||
docker tag gcr.io/${PROJECT_ID}/builder:${TAG_NAME} gcr.io/${PROJECT_ID}/builder:latest
|
||||
docker pull gcr.io/distroless/java
|
||||
docker tag gcr.io/distroless/java gcr.io/${PROJECT_ID}/base:${TAG_NAME}
|
||||
docker tag gcr.io/distroless/java gcr.io/${PROJECT_ID}/base:latest
|
||||
docker pull gcr.io/distroless/java:debug
|
||||
docker tag gcr.io/distroless/java:debug gcr.io/${PROJECT_ID}/base-debug:${TAG_NAME}
|
||||
docker tag gcr.io/distroless/java:debug gcr.io/${PROJECT_ID}/base-debug:latest
|
||||
docker push gcr.io/${PROJECT_ID}/builder:latest
|
||||
docker push gcr.io/${PROJECT_ID}/builder:${TAG_NAME}
|
||||
docker push gcr.io/${PROJECT_ID}/base:latest
|
||||
docker push gcr.io/${PROJECT_ID}/base:${TAG_NAME}
|
||||
docker push gcr.io/${PROJECT_ID}/base-debug:latest
|
||||
docker push gcr.io/${PROJECT_ID}/base-debug:${TAG_NAME}
|
||||
docker push gcr.io/${PROJECT_ID}/builder:latest
|
||||
docker pull jetty:12-jdk21
|
||||
docker tag jetty:12-jdk21 gcr.io/${PROJECT_ID}/jetty:${TAG_NAME}
|
||||
docker tag jetty:12-jdk21 gcr.io/${PROJECT_ID}/jetty:latest
|
||||
docker push gcr.io/${PROJECT_ID}/jetty:${TAG_NAME}
|
||||
docker push gcr.io/${PROJECT_ID}/jetty:latest
|
||||
docker pull eclipse-temurin:21
|
||||
docker tag eclipse-temurin:21 gcr.io/${PROJECT_ID}/temurin:${TAG_NAME}
|
||||
docker tag eclipse-temurin:21 gcr.io/${PROJECT_ID}/temurin:latest
|
||||
docker push gcr.io/${PROJECT_ID}/temurin:${TAG_NAME}
|
||||
docker push gcr.io/${PROJECT_ID}/temurin:latest
|
||||
dir: 'release/builder/'
|
||||
# Do text replacement in the merged repo, hardcoding image digests.
|
||||
- name: 'gcr.io/cloud-builders/gcloud'
|
||||
@@ -73,27 +73,30 @@ steps:
|
||||
set -e
|
||||
builder_digest=$(gcloud container images list-tags gcr.io/${PROJECT_ID}/builder \
|
||||
--format='get(digest)' --filter='tags = ${TAG_NAME}')
|
||||
base_digest=$(gcloud container images list-tags gcr.io/${PROJECT_ID}/base \
|
||||
jetty_digest=$(gcloud container images list-tags gcr.io/${PROJECT_ID}/jetty \
|
||||
--format='get(digest)' --filter='tags = ${TAG_NAME}')
|
||||
debug_digest=$(gcloud container images list-tags gcr.io/${PROJECT_ID}/base-debug \
|
||||
temurin_digest=$(gcloud container images list-tags gcr.io/${PROJECT_ID}/temurin \
|
||||
--format='get(digest)' --filter='tags = ${TAG_NAME}')
|
||||
sed -i s%distroless/java%${PROJECT_ID}/base@$base_digest% proxy/Dockerfile
|
||||
sed -i s%distroless/java:debug%${PROJECT_ID}/base-debug@$debug_digest% core/Dockerfile
|
||||
sed -i s%eclipse-temurin:21%gcr.io/${PROJECT_ID}/temurin@$temurin_digest%g proxy/Dockerfile
|
||||
sed -i s%eclipse-temurin:21%gcr.io/${PROJECT_ID}/temurin@$temurin_digest%g core/Dockerfile
|
||||
sed -i s%jetty:12-jdk21%gcr.io/${PROJECT_ID}/jetty@$jetty_digest%g jetty/Dockerfile
|
||||
sed -i s/builder:latest/builder@$builder_digest/g release/cloudbuild-proxy.yaml
|
||||
sed -i s/builder:latest/builder@$builder_digest/g release/cloudbuild-nomulus.yaml
|
||||
sed -i s/builder:latest/builder@$builder_digest/g release/cloudbuild-deploy.yaml
|
||||
sed -i s/builder:latest/builder@$builder_digest/g release/cloudbuild-sync.yaml
|
||||
sed -i s/builder:latest/builder@$builder_digest/g release/cloudbuild-tag.yaml
|
||||
sed -i s/builder:latest/builder@$builder_digest/g release/cloudbuild-sync-and-tag.yaml
|
||||
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/GCP_PROJECT/${PROJECT_ID}/ proxy/kubernetes/proxy-*.yaml
|
||||
sed -i s/'$${TAG_NAME}'/${TAG_NAME}/g release/cloudbuild-sync.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
|
||||
for environment in alpha crash sandbox production; do
|
||||
for environment in alpha crash qa sandbox production; do
|
||||
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-deploy.yaml \
|
||||
> release/cloudbuild-deploy-${environment}.yaml
|
||||
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-delete.yaml \
|
||||
> release/cloudbuild-delete-${environment}.yaml
|
||||
done
|
||||
# Build the schema_deployer image and upload it to GCR.
|
||||
# Build and upload the schema_deployer image.
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
@@ -103,10 +106,10 @@ steps:
|
||||
docker build -t gcr.io/${PROJECT_ID}/schema_deployer:${TAG_NAME} --build-arg TAG_NAME=${TAG_NAME} --build-arg PROJECT_ID=${PROJECT_ID} .
|
||||
docker tag gcr.io/${PROJECT_ID}/schema_deployer:${TAG_NAME} \
|
||||
gcr.io/${PROJECT_ID}/schema_deployer:latest
|
||||
docker push gcr.io/${PROJECT_ID}/schema_deployer:latest
|
||||
docker push gcr.io/${PROJECT_ID}/schema_deployer:${TAG_NAME}
|
||||
docker push gcr.io/${PROJECT_ID}/schema_deployer:latest
|
||||
dir: 'release/schema-deployer/'
|
||||
# Build the schema_verifier image and upload it to GCR.
|
||||
# Build and upload the schema_verifier image.
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
@@ -116,33 +119,21 @@ steps:
|
||||
docker build -t gcr.io/${PROJECT_ID}/schema_verifier:${TAG_NAME} --build-arg TAG_NAME=${TAG_NAME} --build-arg PROJECT_ID=${PROJECT_ID} .
|
||||
docker tag gcr.io/${PROJECT_ID}/schema_verifier:${TAG_NAME} \
|
||||
gcr.io/${PROJECT_ID}/schema_verifier:latest
|
||||
docker push gcr.io/${PROJECT_ID}/schema_verifier:latest
|
||||
docker push gcr.io/${PROJECT_ID}/schema_verifier:${TAG_NAME}
|
||||
docker push gcr.io/${PROJECT_ID}/schema_verifier:latest
|
||||
dir: 'release/schema-verifier/'
|
||||
# Do text replacement in the schema-deploy, schema-verify and
|
||||
# prober_cert_updater configs.
|
||||
# Do text replacement in the cloud build YAML files.
|
||||
- name: 'gcr.io/cloud-builders/gcloud'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
builder_digest=$( \
|
||||
gcloud container images list-tags gcr.io/${PROJECT_ID}/builder \
|
||||
builder_digest=$(gcloud container images list-tags gcr.io/${PROJECT_ID}/builder \
|
||||
--format='get(digest)' --filter='tags = ${TAG_NAME}')
|
||||
schema_deployer_digest=$( \
|
||||
gcloud container images list-tags gcr.io/${PROJECT_ID}/schema_deployer \
|
||||
schema_deployer_digest=$(gcloud container images list-tags gcr.io/${PROJECT_ID}/schema_deployer \
|
||||
--format='get(digest)' --filter='tags = ${TAG_NAME}')
|
||||
schema_verifier_digest=$( \
|
||||
gcloud container images list-tags gcr.io/${PROJECT_ID}/schema_verifier \
|
||||
--format='get(digest)' --filter='tags = ${TAG_NAME}')
|
||||
prober_cert_updater_digest=$( \
|
||||
gcloud container images list-tags \
|
||||
gcr.io/${PROJECT_ID}/prober_cert_updater \
|
||||
--format='get(digest)' --filter='tags = ${TAG_NAME}')
|
||||
db_object_updater_digest=$( \
|
||||
gcloud container images list-tags \
|
||||
gcr.io/${PROJECT_ID}/db_object_updater \
|
||||
schema_verifier_digest=$(gcloud container images list-tags gcr.io/${PROJECT_ID}/schema_verifier \
|
||||
--format='get(digest)' --filter='tags = ${TAG_NAME}')
|
||||
sed -i s/builder:latest/builder@$builder_digest/g \
|
||||
release/cloudbuild-schema-deploy.yaml
|
||||
@@ -157,17 +148,70 @@ steps:
|
||||
sed -i s/schema_verifier:latest/schema_verifier@$schema_verifier_digest/g \
|
||||
release/cloudbuild-schema-verify.yaml
|
||||
sed -i s/'$${TAG_NAME}'/${TAG_NAME}/g release/cloudbuild-schema-deploy.yaml
|
||||
for environment in alpha crash sandbox production; do
|
||||
for environment in alpha crash qa sandbox production; do
|
||||
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-schema-deploy.yaml \
|
||||
> release/cloudbuild-schema-deploy-${environment}.yaml
|
||||
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-schema-verify.yaml \
|
||||
> release/cloudbuild-schema-verify-${environment}.yaml
|
||||
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-renew-prober-certs.yaml \
|
||||
> release/cloudbuild-renew-prober-certs-${environment}.yaml
|
||||
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-sync-db-objects.yaml \
|
||||
> release/cloudbuild-sync-db-objects-${environment}.yaml
|
||||
done
|
||||
# Upload the gradle binary to GCS if it does not exist and point URL in gradle wrapper to it.
|
||||
# Do text replacement in the k8s manifests.
|
||||
- name: 'gcr.io/cloud-builders/gcloud'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
for env in alpha crash qa sandbox production
|
||||
do
|
||||
# This is the project where Nomulus runs, and we need it to correctly bind the k8s service
|
||||
# account to the GCP service account.
|
||||
if [ ${env} == production ]
|
||||
then
|
||||
project="domain-registry"
|
||||
else
|
||||
project="domain-registry-${env}"
|
||||
fi
|
||||
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
|
||||
do
|
||||
# non-canary
|
||||
sed s/GCP_PROJECT/${PROJECT_ID}/g ./jetty/kubernetes/nomulus-${service}.yaml | \
|
||||
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
|
||||
if [ ${service} == frontend ]
|
||||
then
|
||||
sed -i s/${PROJECT_ID}.iam.gserviceaccount.com/${project}.iam.gserviceaccount.com/g \
|
||||
./jetty/kubernetes/nomulus-${env}-${service}.yaml
|
||||
fi
|
||||
# canary
|
||||
sed s/GCP_PROJECT/${PROJECT_ID}/g ./jetty/kubernetes/nomulus-${service}.yaml | \
|
||||
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/${service}/${service}-canary/g \
|
||||
> ./jetty/kubernetes/nomulus-${env}-${service}-canary.yaml
|
||||
if [ ${service} == frontend ]
|
||||
then
|
||||
sed -i s/${PROJECT_ID}.iam.gserviceaccount.com/${project}.iam.gserviceaccount.com/g \
|
||||
./jetty/kubernetes/nomulus-${env}-${service}-canary.yaml
|
||||
fi
|
||||
# gateway
|
||||
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
|
||||
done
|
||||
done
|
||||
# Upload the Gradle binary to GCS if it does not exist and point URL in Gradle wrapper to it.
|
||||
- name: 'gcr.io/cloud-builders/gsutil'
|
||||
entrypoint: /bin/bash
|
||||
args:
|
||||
@@ -204,6 +248,7 @@ steps:
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
rm -rf gcompute-tools
|
||||
cp -rf nomulus-release/.git .
|
||||
rm -rf nomulus-release
|
||||
git config --global user.name "Cloud Build"
|
||||
|
||||