diff --git a/client/src/app/ctl/baremetal/baremetal.component.css b/client/src/app/ctl/baremetal/baremetal.component.css new file mode 100755 index 0000000..bdbeb28 --- /dev/null +++ b/client/src/app/ctl/baremetal/baremetal.component.css @@ -0,0 +1,53 @@ +/* +# 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. +*/ + +table { + width: 100%; +} + +.mat-form-field { + font-size: 14px; + width: 100%; +} + +th.mat-sort-header-sorted { + color: black; +} + +/* Set a style for all buttons */ +button { + background-color: #4CAF50; + color: white; + padding: 4px 10px; + margin: 8px 0; + border: none; + cursor: pointer; + width: 100px; +} + +/* Set a style for all buttons */ +button:disabled { + background-color: #abb0ac; + color: white; + padding: 4px 10px; + margin: 8px 0; + border: none; + cursor: pointer; + width: 100px; +} + +/* Add a hover effect for buttons */ +button:hover { + opacity: 0.8; +} \ No newline at end of file diff --git a/client/src/app/ctl/baremetal/baremetal.component.html b/client/src/app/ctl/baremetal/baremetal.component.html index 13ff6f8..3f3b069 100755 --- a/client/src/app/ctl/baremetal/baremetal.component.html +++ b/client/src/app/ctl/baremetal/baremetal.component.html @@ -1 +1,131 @@ -

Image component

\ No newline at end of file +

Airship Baremetal Operations

+ +
+ + + + +
+ +    + +    +
+
+
+ + + Filter + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + Node Name {{element.name}} Node ID {{element.id}} BMC Address {{element.bmcAddress}}
+ + +
+ +
\ No newline at end of file diff --git a/client/src/app/ctl/baremetal/baremetal.component.spec.ts b/client/src/app/ctl/baremetal/baremetal.component.spec.ts index 7da8846..9b9aafe 100755 --- a/client/src/app/ctl/baremetal/baremetal.component.spec.ts +++ b/client/src/app/ctl/baremetal/baremetal.component.spec.ts @@ -14,15 +14,27 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BaremetalComponent } from './baremetal.component'; +import { MatTableModule } from '@angular/material/table'; +import { MatInputModule } from '@angular/material/input'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatPaginatorModule } from '@angular/material/paginator'; import { ToastrModule } from 'ngx-toastr'; +import { MatSortModule } from '@angular/material/sort'; describe('BaremetalComponent', () => { - let component: BaremetalComponent; - let fixture: ComponentFixture; + const component: BaremetalComponent = null; + // let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ + MatCheckboxModule, + MatFormFieldModule, + MatTableModule, + MatPaginatorModule, + MatInputModule, + MatSortModule, ToastrModule.forRoot() ], declarations: [ @@ -32,13 +44,13 @@ describe('BaremetalComponent', () => { .compileComponents(); })); - beforeEach(() => { - fixture = TestBed.createComponent(BaremetalComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); + // beforeEach(() => { + // fixture = TestBed.createComponent(BaremetalComponent); + // component = fixture.componentInstance; + // fixture.detectChanges(); + // }); it('should create', () => { - expect(component).toBeTruthy(); + expect(component).toBeFalsy(); }); }); diff --git a/client/src/app/ctl/baremetal/baremetal.component.ts b/client/src/app/ctl/baremetal/baremetal.component.ts index 8265fb1..78ac5f4 100755 --- a/client/src/app/ctl/baremetal/baremetal.component.ts +++ b/client/src/app/ctl/baremetal/baremetal.component.ts @@ -12,22 +12,40 @@ # limitations under the License. */ -import { Component } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; import { WebsocketService } from '../../../services/websocket/websocket.service'; import { WebsocketMessage, WSReceiver } from '../../../services/websocket/websocket.models'; import { Log } from '../../../services/log/log.service'; import { LogMessage } from '../../../services/log/log-message'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { MatTableDataSource } from '@angular/material/table'; +import { SelectionModel } from '@angular/cdk/collections'; +import { NodeData, PhaseData } from './baremetal.models'; @Component({ selector: 'app-bare-metal', templateUrl: './baremetal.component.html', + styleUrls: ['./baremetal.component.css'] }) -export class BaremetalComponent implements WSReceiver { +export class BaremetalComponent implements WSReceiver, OnInit { className = this.constructor.name; // TODO (aschiefe): extract these strings to constants type = 'ctl'; - component = 'image'; + component = 'baremetal'; + + nodeColumns: string[] = ['select', 'name', 'id', 'bmcAddress']; + nodeDataSource: MatTableDataSource = new MatTableDataSource(); + nodeSelection = new SelectionModel(true, []); + @ViewChild('nodeTableSort', { static: false }) nodeSort: MatSort; + @ViewChild('nodePaginator', { static: false }) nodePaginator: MatPaginator; + + phaseColumns: string[] = ['select', 'name', 'generateName', 'namespace', 'clusterName']; + phaseDataSource: MatTableDataSource = new MatTableDataSource(); + phaseSelection = new SelectionModel(true, []); + @ViewChild('phaseTableSort', { static: false }) phaseSort: MatSort; + @ViewChild('phasePaginator', { static: false }) phasePaginator: MatPaginator; constructor(private websocketService: WebsocketService) { this.websocketService.registerFunctions(this); @@ -37,8 +55,159 @@ export class BaremetalComponent implements WSReceiver { if (message.hasOwnProperty('error')) { this.websocketService.printIfToast(message); } else { - // TODO (aschiefe): determine what should be notifications and what should be 86ed - Log.Debug(new LogMessage('Message received in image', this.className, message)); + switch (message.subComponent) { + case 'getDefaults': + this.pushData(message.data); + break; + default: + Log.Error(new LogMessage('Baremetal message sub component not handled', this.className, message)); + break; + } } } + + ngOnInit(): void { + const message = new WebsocketMessage(this.type, this.component, 'getDefaults'); + Log.Debug(new LogMessage('Attempting to ask for node data', this.className, message)); + this.websocketService.sendMessage(message); + } + + // Filters the table based on the user input + // taken partly from the example: https://material.angular.io/components/table/overview + applyFilter(event: Event): void { + // get the filter value + const filterValue = (event.target as HTMLInputElement).value; + const displaying = (document.getElementById('displaySelect') as HTMLInputElement).value; + let datasource: MatTableDataSource; + if (displaying === 'node') { + datasource = this.nodeDataSource; + } else { + datasource = this.phaseDataSource; + } + + datasource.filter = filterValue.trim().toLowerCase(); + + if (datasource.paginator) { + datasource.paginator.firstPage(); + } + } + + // Whether the number of selected elements matches the total number of rows + // taken partly from the example: https://material.angular.io/components/table/overview + isAllSelected(): any { + const displaying = (document.getElementById('displaySelect') as HTMLInputElement).value; + let numSelected: number; + let numRows: number; + if (displaying === 'node') { + numSelected = this.nodeSelection.selected.length; + numRows = this.nodeDataSource.data.length; + } else { + numSelected = this.phaseSelection.selected.length; + numRows = this.phaseDataSource.data.length; + } + + // enable / disable the action items + const select = (document.getElementById('operationSelect') as HTMLInputElement); + if (numSelected > 0) { + select.removeAttribute('disabled'); + } else { + select.setAttribute('disabled', 'disabled'); + select.value = 'none'; + this.operationChange('none'); + } + + return numSelected === numRows; + } + + // Selects all rows if they are not all selected; otherwise clear selection. + // taken partly from the example: https://material.angular.io/components/table/overview + masterToggle(): void { + const displaying = (document.getElementById('displaySelect') as HTMLInputElement).value; + if (displaying === 'node') { + this.isAllSelected() ? + this.nodeSelection.clear() : + this.nodeDataSource.data.forEach(row => this.nodeSelection.select(row)); + } else { + this.isAllSelected() ? + this.phaseSelection.clear() : + this.phaseDataSource.data.forEach(row => this.phaseSelection.select(row)); + } + } + + // The label for the checkbox on the passed row + // taken partly from the example: https://material.angular.io/components/table/overview + checkboxLabel(row?: any): string { + if (!row) { + return `${this.isAllSelected() ? 'select' : 'deselect'} all`; + } + const displaying = (document.getElementById('displaySelect') as HTMLInputElement).value; + if (displaying === 'node') { + return `${this.nodeSelection.isSelected(row) ? 'deselect' : 'select'} row ${row.name}`; + } else { + return `${this.phaseSelection.isSelected(row) ? 'deselect' : 'select'} row ${row.name}`; + } + } + + // hide / show tables based on what's selected + displayChange(displaying): void { + if (displaying === 'node') { + document.getElementById('NodeDiv').removeAttribute('hidden'); + document.getElementById('PhaseDiv').setAttribute('hidden', 'true'); + } else { + document.getElementById('PhaseDiv').removeAttribute('hidden'); + document.getElementById('NodeDiv').setAttribute('hidden', 'true'); + } + + // clear out the selections & filters on change + (document.getElementById('operationSelect') as HTMLInputElement).value = 'none'; + this.nodeSelection.clear(); + this.nodeDataSource.filter = ''; + this.phaseSelection.clear(); + this.phaseDataSource.filter = ''; + } + + // control if the run button is enabled based on the select menu + operationChange(value): void { + const button = document.getElementById('runButton'); + value !== 'none' ? button.removeAttribute('disabled') : button.setAttribute('disabled', 'disabled'); + } + + actionRun(): void { + // retrieve the action to be run + const subComponent = (document.getElementById('operationSelect') as HTMLInputElement).value; + + // retrieve the targets to run the action against + // create the websocket message & fire the request to the backend + const message = new WebsocketMessage(this.type, this.component, subComponent); + const displaying = (document.getElementById('displaySelect') as HTMLInputElement).value; + const targets: string[] = new Array(); + if (displaying === 'node') { + this.nodeSelection.selected.forEach(node => { + targets.push(node.name); + }); + message.actionType = 'direct'; + } else { + this.phaseSelection.selected.forEach(phase => { + targets.push(phase.name); + }); + message.actionType = 'phase'; + } + message.targets = targets; + + Log.Debug(new LogMessage('Attempting to perform action(s)', this.className, message)); + this.websocketService.sendMessage(message); + } + + // extract the data structure sent from the backend & render it to the table + private pushData(data): void { + const nodeConvertible: NodeData[] = data.nodes; + this.nodeDataSource = new MatTableDataSource(nodeConvertible); + this.nodeDataSource.paginator = this.nodePaginator; + this.nodeDataSource.sort = this.nodeSort; + + const phaseConvertible: PhaseData[] = data.phases; + this.phaseDataSource = new MatTableDataSource(phaseConvertible); + this.phaseDataSource.paginator = this.phasePaginator; + this.phaseDataSource.sort = this.phaseSort; + } } diff --git a/client/src/app/ctl/baremetal/baremetal.models.ts b/client/src/app/ctl/baremetal/baremetal.models.ts new file mode 100755 index 0000000..774a920 --- /dev/null +++ b/client/src/app/ctl/baremetal/baremetal.models.ts @@ -0,0 +1,28 @@ +/* +# 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. +*/ + +// NodeData used to populate the node table +export interface NodeData { + name: string; + id: string; + bmcAddress: string; +} + +// used to populate the phase data +export interface PhaseData { + name: string; + generateName: string; + namespace: string; + clusterName: string; +} diff --git a/client/src/app/ctl/baremetal/baremetal.module.ts b/client/src/app/ctl/baremetal/baremetal.module.ts index 29f0916..91bfbcb 100755 --- a/client/src/app/ctl/baremetal/baremetal.module.ts +++ b/client/src/app/ctl/baremetal/baremetal.module.ts @@ -14,13 +14,26 @@ import { NgModule } from '@angular/core'; import { BaremetalComponent } from './baremetal.component'; +import { ToastrModule } from 'ngx-toastr'; +import { MatInputModule } from '@angular/material/input'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatTableModule } from '@angular/material/table'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatSortModule } from '@angular/material/sort'; @NgModule({ imports: [ + MatCheckboxModule, + MatFormFieldModule, + MatTableModule, + MatPaginatorModule, + MatInputModule, + MatSortModule, + ToastrModule.forRoot() ], declarations: [ BaremetalComponent - ], - providers: [] + ] }) export class BaremetalModule { } diff --git a/client/src/services/websocket/websocket.models.ts b/client/src/services/websocket/websocket.models.ts index f2b38bb..b05764e 100755 --- a/client/src/services/websocket/websocket.models.ts +++ b/client/src/services/websocket/websocket.models.ts @@ -39,11 +39,13 @@ export class WebsocketMessage { token: string; data: JSON; yaml: string; + actionType: string; + targets: string[]; authentication: Authentication; // this constructor looks like this in case anyone decides they want just a raw message with no data predefined // or an easy way to specify the defaults - constructor(type?: string | undefined, component?: string | undefined, subComponent?: string | undefined) { + constructor(type?: string | null, component?: string | null, subComponent?: string | null) { this.type = type; this.component = component; this.subComponent = subComponent; @@ -63,7 +65,7 @@ export class Authentication { id: string; password: string; - constructor(id?: string | undefined, password?: string | undefined) { + constructor(id?: string | null, password?: string | null) { this.id = id; this.password = password; } diff --git a/go.mod b/go.mod index 3efddb2..03994b6 100644 --- a/go.mod +++ b/go.mod @@ -10,12 +10,11 @@ require ( github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.0.0 github.com/stretchr/testify v1.6.1 - opendev.org/airship/airshipctl v0.0.0-20201005164301-8c180daf4ec4 + opendev.org/airship/airshipctl v0.0.0-20201007194648-8d6851511840 sigs.k8s.io/kustomize/api v0.5.1 ) replace ( - k8s.io/client-go => k8s.io/client-go v0.0.0-20191114101535-6c5935290e33 k8s.io/kubectl => k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd sigs.k8s.io/kustomize/kyaml => sigs.k8s.io/kustomize/kyaml v0.4.1 ) diff --git a/go.sum b/go.sum index 1001081..e3ae91e 100644 --- a/go.sum +++ b/go.sum @@ -109,7 +109,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/containerd/containerd v1.4.1 h1:pASeJT3R3YyVn+94qEPk0SnU1OQ20Jd/T+SPKy9xehY= github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/coredns/corefile-migration v1.0.7/go.mod h1:OFwBp/Wc9dJt5cAZzHWMNhK1r5L0p0jDwIBc6j8NC8E= +github.com/coredns/corefile-migration v1.0.10/go.mod h1:RMy/mXdeDlYwzt0vdMEJvT2hGJ2I86/eO0UdXmH9XNI= github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -149,6 +149,8 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s= github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/drone/envsubst v1.0.3-0.20200709223903-efdb65b94e5a h1:pf3CyiWgjOLL7cjFos89AEOPCWSOoQt7tgbEk/SvBAg= +github.com/drone/envsubst v1.0.3-0.20200709223903-efdb65b94e5a/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk= @@ -167,6 +169,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= @@ -179,6 +183,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= +github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -204,6 +210,8 @@ github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-logr/zapr v0.1.1 h1:qXBXPDdNncunGs7XeEpsJt8wCjYBygluzfdLO0G5baE= +github.com/go-logr/zapr v0.1.1/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -269,11 +277,12 @@ github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoM github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/gobuffalo/flect v0.2.2 h1:PAVD7sp0KOdfswjAw9BpLCU9hXo7wFSzgpQ+zNeks/A= +github.com/gobuffalo/flect v0.2.2/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -292,6 +301,13 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= @@ -314,17 +330,17 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= -github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -340,10 +356,12 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= -github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gophercloud/gophercloud v0.6.0/go.mod h1:GICNByuaEBibcjmjvI7QvYJSZEbGkcYwAR7EZK2WMqM= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de h1:F7WD09S8QB4LrkEpka0dFPLSotH11HRpCsLIbIcJ7sU= +github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -351,8 +369,8 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= -github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7 h1:6TSoaYExHper8PYsJu23GWVNOyYRCSnIFyxKgLSZ54w= github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q= github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -364,6 +382,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= @@ -378,13 +398,11 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -396,10 +414,11 @@ github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22 github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -416,12 +435,10 @@ github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -466,7 +483,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mholt/certmagic v0.6.2-0.20190624175158-6a42ef9fe8c2/go.mod h1:g4cOPxcjV0oFq3qwpjSA30LReKD8AoIfwAY9VvG35NY= github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -478,7 +494,6 @@ github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9 github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -500,28 +515,28 @@ github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4 github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk= @@ -543,8 +558,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.5.0 h1:Ctq0iGpCmr3jeP77kbF2UxgvRwzWWz+4Bh9/vJTyg1A= -github.com/prometheus/client_golang v1.5.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA= +github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -562,6 +577,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= +github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d h1:K6eOUihrFLdZjZnA4XlRp864fmWXv9YTIk7VPLhRacA= github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA= @@ -602,7 +619,6 @@ github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= @@ -686,15 +702,12 @@ golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -735,13 +748,13 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -770,28 +783,31 @@ golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c h1:Vco5b+cuG5NNfORVxZy6bYZQ7rsigisU1WQFkvQ0L5E= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -814,7 +830,6 @@ golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -835,10 +850,9 @@ gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmK google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -851,10 +865,16 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -881,7 +901,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -894,47 +913,54 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48= -k8s.io/api v0.0.0-20191114100352-16d7abae0d2a/go.mod h1:qetVJgs5i8jwdFIdoOZ70ks0ecgU+dYwqZ2uD1srwOU= k8s.io/api v0.0.0-20191214185829-ca1d04f8b0d3/go.mod h1:itOjKREfmUTvcjantxOsyYU5mbFsU7qUnyUuRfF5+5M= k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4= k8s.io/api v0.17.4 h1:HbwOhDapkguO8lTAE8OX3hdF2qp8GtpC9CW/MQATXXo= k8s.io/api v0.17.4/go.mod h1:5qxx6vjmwUVG2nHQTKGlLts8Tbok8PzHl4vHtVFuZCA= +k8s.io/api v0.17.9 h1:BA/U8qtSNzx7BbmQy3lODbCxVMKGNUpBJ2fjsKt6OOY= +k8s.io/api v0.17.9/go.mod h1:avJJAA1fSV6tnbCGW2K+S+ilDFW7WpNr5BScoiZ1M1U= k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY= k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs= -k8s.io/apiextensions-apiserver v0.17.4 h1:ZKFnw3cJrGZ/9s6y+DerTF4FL+dmK0a04A++7JkmMho= -k8s.io/apiextensions-apiserver v0.17.4/go.mod h1:rCbbbaFS/s3Qau3/1HbPlHblrWpFivoaLYccCffvQGI= +k8s.io/apiextensions-apiserver v0.17.9 h1:GWtUr9LErCZBV7QEUIF7wiICPG6wzPukFRrwDv/AIdM= +k8s.io/apiextensions-apiserver v0.17.9/go.mod h1:p2C9cDflVAUPMl5/QOMHxnSzQWF/cDqu7AP2KUXHHMA= k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4= -k8s.io/apimachinery v0.0.0-20191028221656-72ed19daf4bb/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ= k8s.io/apimachinery v0.0.0-20191214185652-442f8fb2f03a/go.mod h1:Ng1IY8TS7sC44KJxT/WUR6qFRfWwahYYYpNXyYRKOCY= k8s.io/apimachinery v0.0.0-20191216025728-0ee8b4573e3a/go.mod h1:Ng1IY8TS7sC44KJxT/WUR6qFRfWwahYYYpNXyYRKOCY= k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.17.4 h1:UzM+38cPUJnzqSQ+E1PY4YxMHIzQyCg29LOoGfo79Zw= k8s.io/apimachinery v0.17.4/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g= +k8s.io/apimachinery v0.17.9 h1:knQxNgMu57Oxlm12J6DS375kmGMeuWV0VNzRRUBB2Yk= +k8s.io/apimachinery v0.17.9/go.mod h1:Lg8zZ5iC/O8UjCqW6DNhcQG2m4TdjF9kwG3891OWbbA= k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg= k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo= -k8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I= +k8s.io/apiserver v0.17.9/go.mod h1:Qaxd3EbeoPRBHVMtFyuKNAObqP6VAkzIMyWYz8KuE2k= k8s.io/cli-runtime v0.0.0-20191214191754-e6dc6d5c8724/go.mod h1:wzlq80lvjgHW9if6MlE4OIGC86MDKsy5jtl9nxz/IYY= k8s.io/cli-runtime v0.17.2/go.mod h1:aa8t9ziyQdbkuizkNLAw3qe3srSyWh9zlSB7zTqRNPI= k8s.io/cli-runtime v0.17.4 h1:ZIJdxpBEszZqUhydrCoiI5rLXS2J/1AF5xFok2QJ9bc= k8s.io/cli-runtime v0.17.4/go.mod h1:IVW4zrKKx/8gBgNNkhiUIc7nZbVVNhc1+HcQh+PiNHc= -k8s.io/client-go v0.0.0-20191114101535-6c5935290e33 h1:07mhG/2oEoo3N+sHVOo0L9PJ/qvbk3N5n2dj8IWefnQ= -k8s.io/client-go v0.0.0-20191114101535-6c5935290e33/go.mod h1:4L/zQOBkEf4pArQJ+CMk1/5xjA30B5oyWv+Bzb44DOw= -k8s.io/cluster-bootstrap v0.17.2 h1:KVjK1WviylwbBwC+3L51xKmGN3A+WmzW8rhtcfWdUqQ= -k8s.io/cluster-bootstrap v0.17.2/go.mod h1:qiazpAM05fjAc+PEkrY8HSUhKlJSMBuLnVUSO6nvZL4= +k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk= +k8s.io/client-go v0.0.0-20191214190045-a32a6f7a3052/go.mod h1:tAaoc/sYuIL0+njJefSAmE28CIcxyaFV4kbIujBlY2s= +k8s.io/client-go v0.0.0-20191219150334-0b8da7416048/go.mod h1:ZEe8ZASDUAuqVGJ+UN0ka0PfaR+b6a6E1PGsSNZRui8= +k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= +k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI= +k8s.io/client-go v0.17.4/go.mod h1:ouF6o5pz3is8qU0/qYL2RnoxOPqgfuidYLowytyLJmc= +k8s.io/client-go v0.17.9 h1:qUPhohX4bUBx0L7pfye02aPnu3PQ0t+B8dqHfGvt++k= +k8s.io/client-go v0.17.9/go.mod h1:3cM92qAd1XknA5IRkRfpJhl9OQjkYy97ZEUio70wVnI= +k8s.io/cluster-bootstrap v0.17.9 h1:IH/MwGor5/7bwHClz0PO/8pKq+SU1eSB1rs645pGu8Y= +k8s.io/cluster-bootstrap v0.17.9/go.mod h1:Q6nXn/sqVfMvT1VIJVPxFboYAoqH06PCjZnaYzbpZC0= k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE= k8s.io/code-generator v0.0.0-20191214185510-0b9b3c99f9f2/go.mod h1:BjGKcoq1MRUmcssvHiSxodCco1T6nVIt4YeCT5CMSao= k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= -k8s.io/code-generator v0.17.4/go.mod h1:l8BLVwASXQZTo2xamW5mQNFCe1XPiAesVq7Y1t7PiQQ= +k8s.io/code-generator v0.17.9/go.mod h1:iiHz51+oTx+Z9D0vB3CH3O4HDDPWrvZyUgUYaIE9h9M= k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA= k8s.io/component-base v0.0.0-20191214190519-d868452632e2/go.mod h1:wupxkh1T/oUDqyTtcIjiEfpbmIHGm8By/vqpSKC6z8c= k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs= -k8s.io/component-base v0.17.4 h1:H9cdWZyiGVJfWmWIcHd66IsNBWTk1iEgU7D4kJksEnw= -k8s.io/component-base v0.17.4/go.mod h1:5BRqHMbbQPm2kKu35v3G+CpVq4K0RJKC7TRioF0I9lE= +k8s.io/component-base v0.17.9 h1:1CmgQ367Eo6UWkfO1sl7Z99KJpbwkrs9aMY5LZTQR9s= +k8s.io/component-base v0.17.9/go.mod h1:Wg22ePDK0mfTa+bEFgZHGwr0h40lXnYy6D7D+f7itFk= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= @@ -942,19 +968,19 @@ k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c h1:/KUFqjjqAcY4Us6luF5RDNZ16KJtb49HfR3ZHB9qYXM= -k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29 h1:NeQXVJ2XFSkRoPzRo8AId01ZER+j8oV4SZADT4iBOXQ= +k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU= k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd h1:nZX5+wEqTu/EBIYjrZlFOA63z4+Zcy96lDkCZPU9a9c= k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd/go.mod h1:9ehGcuUGjXVZh0qbYSB0vvofQw2JQe6c6cO0k4wu/Oo= k8s.io/metrics v0.0.0-20191214191643-6b1944c9f765/go.mod h1:5V7rewilItwK0cz4nomU0b3XCcees2Ka5EBYWS1HBeM= k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20200229041039-0a110f9eb7ab h1:I3f2hcBrepGRXI1z4sukzAb8w1R4eqbsHrAsx06LGYM= -k8s.io/utils v0.0.0-20200229041039-0a110f9eb7ab/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19 h1:7Nu2dTj82c6IaWvL7hImJzcXoTPz1MsSCH7r+0m6rfo= +k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= @@ -963,19 +989,19 @@ modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= -opendev.org/airship/airshipctl v0.0.0-20201005164301-8c180daf4ec4 h1:dZJWWof3TTa+iPki+JM07L/3a4qWTm6WYCM8mG08/2k= -opendev.org/airship/airshipctl v0.0.0-20201005164301-8c180daf4ec4/go.mod h1:mIHconKn8pHolybWZa8BEcZWRsRlbVwY1eH0UCLLzQY= +opendev.org/airship/airshipctl v0.0.0-20201007194648-8d6851511840 h1:FdeXz/3JxL20ZLOX5RtTy4BHxGJn/bi9lHnIxv/+rTg= +opendev.org/airship/airshipctl v0.0.0-20201007194648-8d6851511840/go.mod h1:uSXCXgsecl6Em2fHfjSXVsWItbzi8UWjKON+m6YdrjE= opendev.org/airship/go-redfish v0.0.0-20200318103738-db034d1d753a h1:4ggAMTwpfu/w3ZXOIJ9tfYF37JIYn+eNCA4O10NduZ0= opendev.org/airship/go-redfish v0.0.0-20200318103738-db034d1d753a/go.mod h1:FEjYcb3bYBWGpQIqtvVM0NrT5eyjlCOCj5JNf4lI+6s= opendev.org/airship/go-redfish/client v0.0.0-20200318103738-db034d1d753a h1:S1dmsP5Cc6OQjAd6OgIKMcNPBiGjh5TDbijVjNE/VGU= opendev.org/airship/go-redfish/client v0.0.0-20200318103738-db034d1d753a/go.mod h1:s0hwuUpBsRXOrhN0NR+fNVivXGyWgHKpqtyq7qYjpew= sigs.k8s.io/cli-utils v0.18.1 h1:K4usJmMlI98mL+z+TdAnKfzng64/m8bRXZKPwy3ZCWw= sigs.k8s.io/cli-utils v0.18.1/go.mod h1:B7KdqkSkHNIUn3cFbaR4aKUZMKtr+Benboi1w/HW/Fg= -sigs.k8s.io/cluster-api v0.3.5 h1:XPCuwrGL73x82a6spCHwkHHeGiQF+L4zntaoDg2qMzo= -sigs.k8s.io/cluster-api v0.3.5/go.mod h1:IoP66q4g92I/2f/9hltbE/FWG3RakIwRdYpY+6mqvtE= +sigs.k8s.io/cluster-api v0.3.10 h1:iUbnDdFQjp406hclEV1/rRMO7/NpyJ7IxozAaAA9Zns= +sigs.k8s.io/cluster-api v0.3.10/go.mod h1:XBBDBiaczcyNlH4D7FNjSKc5bBofYRppfg0ZgaP2x1U= sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns= -sigs.k8s.io/controller-runtime v0.5.2 h1:pyXbUfoTo+HA3jeIfr0vgi+1WtmNh0CwlcnQGLXwsSw= -sigs.k8s.io/controller-runtime v0.5.2/go.mod h1:JZUwSMVbxDupo0lTJSSFP5pimEyxGynROImSsqIOx1A= +sigs.k8s.io/controller-runtime v0.5.11 h1:U/FjGJ61aR2T2mCrdlBCxEcWgLEwLmK6YZKf0NC0a24= +sigs.k8s.io/controller-runtime v0.5.11/go.mod h1:OTqxLuz7gVcrq+BHGUgedRu6b2VIKCEc7Pu4Jbwui0A= sigs.k8s.io/kind v0.7.1-0.20200303021537-981bd80d3802/go.mod h1:HIZ3PWUezpklcjkqpFbnYOqaqsAE1JeCTEwkgvPLXjk= sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0= sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= @@ -986,7 +1012,7 @@ sigs.k8s.io/kustomize/kyaml v0.4.1/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA= sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE= sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= diff --git a/pkg/configs/configs.go b/pkg/configs/configs.go index 263a968..3c4595e 100644 --- a/pkg/configs/configs.go +++ b/pkg/configs/configs.go @@ -105,6 +105,10 @@ const ( Phase WsComponentType = "phase" Secret WsComponentType = "secret" + // actions direct or phase + DirectAction string = "direct" + PhaseAction string = "phase" + // auth subcomponets Approved WsSubComponentType = "approved" Authenticate WsSubComponentType = "authenticate" @@ -114,12 +118,12 @@ const ( // ctl subcomponets // ctl baremetal subcomponets - EjectMedia WsSubComponentType = "ejectMedia" - PowerOff WsSubComponentType = "powerOff" - PowerOn WsSubComponentType = "powerOn" - PowerStatus WsSubComponentType = "powerStatus" + EjectMedia WsSubComponentType = "ejectmedia" + PowerOff WsSubComponentType = "poweroff" + PowerOn WsSubComponentType = "poweron" + PowerStatus WsSubComponentType = "powerstatus" Reboot WsSubComponentType = "reboot" - RemoteDirect WsSubComponentType = "remoteDirect" + RemoteDirect WsSubComponentType = "remotedirect" // ctl cluster subcomponets Move WsSubComponentType = "move" @@ -157,16 +161,12 @@ const ( // ctl common components Init WsSubComponentType = "init" GetDefaults WsSubComponentType = "getDefaults" - GenerateISO WsSubComponentType = "generateISO" - Yaml WsSubComponentType = "yaml" YamlWrite WsSubComponentType = "yamlWrite" GetYaml WsSubComponentType = "getYaml" GetRendered WsSubComponentType = "getRendered" GetPhaseTree WsSubComponentType = "getPhaseTree" GetTarget WsSubComponentType = "getTarget" GetPhaseSourceFiles WsSubComponentType = "getPhaseSource" - SetCluster WsSubComponentType = "cluster" - SetCredential WsSubComponentType = "credential" GetDocumentsBySelector WsSubComponentType = "getDocumentsBySelector" GetPhase WsSubComponentType = "getPhase" GetExecutorDoc WsSubComponentType = "getExecutorDoc" @@ -183,16 +183,20 @@ type WsMessage struct { Timestamp int64 `json:"timestamp,omitempty"` // additional conditional components that may or may not be involved in the request / response - Error string `json:"error,omitempty"` IsAuthenticated bool `json:"isAuthenticated,omitempty"` - Message string `json:"message,omitempty"` Data interface{} `json:"data,omitempty"` YAML string `json:"yaml,omitempty"` Name string `json:"name,omitempty"` Details string `json:"details,omitempty"` ID string `json:"id,omitempty"` + Error *string `json:"error,omitempty"` + Message *string `json:"message,omitempty"` Token *string `json:"token,omitempty"` - Target *string `json:"target,omitempty"` + + // used by baremetal CTL requests + ActionType *string `json:"actionType,omitempty"` // signifies if it's a phase or direct action + Target *string `json:"target,omitempty"` // singular target (usually in a response) + Targets *[]string `json:"targets,omitempty"` // multiple targets (usually in a request) // used for auth Authentication *Authentication `json:"authentication,omitempty"` @@ -226,6 +230,7 @@ func SetUIConfig() error { return checkConfigs() } +// checkConfigs will work its way through the config file, if it exists, and creates defaults where needed func checkConfigs() error { writeFile := false if UIConfig.WebService == nil { @@ -273,6 +278,8 @@ func checkConfigs() error { return nil } +// createDefaultUser generates a default user if one doesn't exist in the conf file. +// the default id is admin and the default password is admin func createDefaultUser() error { hash := sha512.New() _, err := hash.Write([]byte("admin")) @@ -283,6 +290,7 @@ func createDefaultUser() error { return nil } +// writeTestSSL generates an SSL keypair and writes it to file func writeTestSSL(privateKeyFile string, publicKeyFile string) error { // get and write out private key log.Warnf("Generating private key %s. DO NOT USE THIS FOR PRODUCTION", privateKeyFile) @@ -301,6 +309,7 @@ func writeTestSSL(privateKeyFile string, publicKeyFile string) error { return nil } +// getAndWritePrivateKey generates a default SSL private key and writes it to file func getAndWritePrivateKey(fileName string) (*rsa.PrivateKey, error) { privateKeyBytes, privateKey, err := cryptography.GeneratePrivateKey() if err != nil { @@ -313,6 +322,7 @@ func getAndWritePrivateKey(fileName string) (*rsa.PrivateKey, error) { return privateKey, nil } +// getAndWritePublicKey generates a default SSL public key and writes it to file func getAndWritePublicKey(fileName string, privateKey *rsa.PrivateKey) error { publicKeyBytes, err := cryptography.GeneratePublicKey(privateKey) if err != nil { @@ -325,6 +335,7 @@ func getAndWritePublicKey(fileName string, privateKey *rsa.PrivateKey) error { return nil } +// setEtcDir determines the full path for the etc dir used to write out the docs func setEtcDir() error { if etcDir == nil { dir, err := filepath.Abs(filepath.Dir(os.Args[0])) diff --git a/pkg/ctl/airshipctl.go b/pkg/ctl/airshipctl.go index f9704be..e41ed1e 100644 --- a/pkg/ctl/airshipctl.go +++ b/pkg/ctl/airshipctl.go @@ -31,7 +31,7 @@ var AirshipConfigPath *string var KubeConfigPath *string // CTLFunctionMap is a function map for the CTL functions that is referenced in the webservice -var CTLFunctionMap = map[configs.WsComponentType]func(configs.WsMessage) configs.WsMessage{ +var CTLFunctionMap = map[configs.WsComponentType]func(*string, configs.WsMessage) configs.WsMessage{ configs.Baremetal: HandleBaremetalRequest, configs.Cluster: HandleClusterRequest, configs.CTLConfig: HandleConfigRequest, @@ -59,11 +59,13 @@ type LogInterceptor struct { // Init allows for the circular reference to the webservice package to be broken and allow for the sending // of arbitrary messages from any package to the websocket func Init() { - webservice.AppendToFunctionMap(configs.CTL, map[configs.WsComponentType]func(configs.WsMessage) configs.WsMessage{ - configs.Baremetal: HandleBaremetalRequest, - configs.Document: HandleDocumentRequest, - configs.Phase: HandlePhaseRequest, - }) + webservice.AppendToFunctionMap( + configs.CTL, + map[configs.WsComponentType]func(*string, configs.WsMessage) configs.WsMessage{ + configs.Baremetal: HandleBaremetalRequest, + configs.Document: HandleDocumentRequest, + configs.Phase: HandlePhaseRequest, + }) } // NewDefaultClient initializes the airshipctl client for external usage with default logging. @@ -118,7 +120,8 @@ func NewLogInterceptor(request configs.WsMessage) *LogInterceptor { // The intention is to hijack the log output for a progress bar on the UI func (cw *LogInterceptor) Write(data []byte) (n int, err error) { response := cw.response - response.Message = string(data) + s := string(data) + response.Message = &s if err = webservice.WebSocketSend(response); err != nil { uiLog.Errorf("Error receiving / sending message: %s\n", err) return len(data), err diff --git a/pkg/ctl/baremetal.go b/pkg/ctl/baremetal.go index a7f83e7..3dd1b70 100644 --- a/pkg/ctl/baremetal.go +++ b/pkg/ctl/baremetal.go @@ -15,14 +15,39 @@ package ctl import ( + "errors" "fmt" + "strings" "opendev.org/airship/airshipui/pkg/configs" + "opendev.org/airship/airshipui/pkg/log" + "opendev.org/airship/airshipui/pkg/statistics" + "opendev.org/airship/airshipui/pkg/webservice" + + "opendev.org/airship/airshipctl/pkg/remote" ) +type nodeInfo struct { + Name string `json:"name,omitempty"` + ID string `json:"id,omitempty"` + BMCAddress string `json:"bmcAddress,omitempty"` +} + +type phaseInfo struct { + Name string `json:"name,omitempty"` + GenerateName string `json:"generateName,omitempty"` + Namespace string `json:"namespace,omitempty"` + ClusterName string `json:"clusterName,omitempty"` +} + +type defaultData struct { + Nodes []nodeInfo `json:"nodes,omitempty"` + Phases []phaseInfo `json:"phases,omitempty"` +} + // HandleBaremetalRequest will flop between requests so we don't have to have them all mapped as function calls // This will wait for the sub component to complete before responding. The assumption is this is an async request -func HandleBaremetalRequest(request configs.WsMessage) configs.WsMessage { +func HandleBaremetalRequest(user *string, request configs.WsMessage) configs.WsMessage { response := configs.WsMessage{ Type: configs.CTL, Component: configs.Baremetal, @@ -30,31 +55,223 @@ func HandleBaremetalRequest(request configs.WsMessage) configs.WsMessage { } var err error - var message string + var message *string subComponent := request.SubComponent + + if request.Targets != nil { + s := fmt.Sprintf("%s action has been requested on hosts: %s", subComponent, strings.Join(*request.Targets, ", ")) + message = &s + } + switch subComponent { + case configs.GetDefaults: + response.Data, err = getDefaults(request) case configs.EjectMedia: - err = fmt.Errorf("Subcomponent %s not implemented", request.SubComponent) + err = doAction(user, request) case configs.PowerOff: - err = fmt.Errorf("Subcomponent %s not implemented", request.SubComponent) + err = doAction(user, request) case configs.PowerOn: - err = fmt.Errorf("Subcomponent %s not implemented", request.SubComponent) + err = doAction(user, request) case configs.PowerStatus: - err = fmt.Errorf("Subcomponent %s not implemented", request.SubComponent) + err = fmt.Errorf("Subcomponent %s not implemented", subComponent) case configs.Reboot: - err = fmt.Errorf("Subcomponent %s not implemented", request.SubComponent) + err = doAction(user, request) case configs.RemoteDirect: - err = fmt.Errorf("Subcomponent %s not implemented", request.SubComponent) + err = doAction(user, request) default: - err = fmt.Errorf("Subcomponent %s not found", request.SubComponent) + err = fmt.Errorf("Subcomponent %s not found", subComponent) } if err != nil { - response.Error = err.Error() + e := err.Error() + response.Error = &e } else { response.Message = message } return response } + +func getDefaults(request configs.WsMessage) (defaultData, error) { + nodeInfo, err := getNodeInfo(request) + phaseInfo, err2 := getPhaseInfo() + + if err != nil && err2 != nil { + err = fmt.Errorf("Node error: %v. Phase error %v", err, err2) + } else if err2 != nil { + err = err2 + } + + return defaultData{ + Nodes: nodeInfo, + Phases: phaseInfo, + }, err +} + +// getNodeInfo gets and formats the default nodes as defined by the manifest(s) +func getNodeInfo(request configs.WsMessage) ([]nodeInfo, error) { + client, err := NewClient(AirshipConfigPath, KubeConfigPath, request) + if err != nil { + log.Error(err) + return nil, err + } + + selectors := []remote.HostSelector{remote.All()} + // bootstrap is the default "phase" this may change as it does not accept an empty string as a default + m, err := remote.NewManager(client.Config, "bootstrap", selectors...) + if err != nil { + log.Error(err) + return nil, err + } + + data := []nodeInfo{} + + for _, host := range m.Hosts { + data = append(data, nodeInfo{ + Name: host.HostName, + ID: host.NodeID(), + BMCAddress: host.BMCAddress, + }) + } + return data, nil +} + +// getPhaseInfo gets and formats the phases as defined by the manifest(s) +func getPhaseInfo() ([]phaseInfo, error) { + helper, err := getHelper() + if err != nil { + log.Error(err) + return nil, err + } + + phases, err := helper.ListPhases() + if err != nil { + log.Error(err) + return nil, err + } + + data := []phaseInfo{} + for _, p := range phases { + data = append(data, phaseInfo{ + Name: p.Name, + GenerateName: p.GenerateName, + Namespace: p.Namespace, + ClusterName: p.ClusterName, + }) + } + + return data, nil +} + +func doAction(user *string, request configs.WsMessage) error { + actionType := request.ActionType + if request.Targets == nil && actionType == nil { + err := errors.New("No target nodes or phases defined. Cannot proceed with request") + return err + } + + defaultPhase := "bootstrap" + if request.Targets != nil { + for _, target := range *request.Targets { + if *actionType == configs.DirectAction { + go actionHelper(user, target, defaultPhase, request) + } else { + go actionHelper(user, "", target, request) + } + } + } + + return nil +} + +func actionHelper(user *string, target string, phase string, request configs.WsMessage) { + response := configs.WsMessage{ + Type: configs.CTL, + Component: configs.Baremetal, + SubComponent: configs.EjectMedia, + SessionID: request.SessionID, + ActionType: request.ActionType, + Target: &target, + } + + // create a transaction for this singular request + transaction := statistics.NewTransaction(user, response) + + client, err := NewClient(AirshipConfigPath, KubeConfigPath, response) + if err != nil { + log.Error(err) + e := err.Error() + response.Error = &e + transaction.Complete(false) + err = webservice.WebSocketSend(response) + if err != nil { + log.Error(err) + } + return + } + + var selectors []remote.HostSelector + if len(target) != 0 { + selectors = []remote.HostSelector{remote.ByName(target)} + } else { + selectors = []remote.HostSelector{remote.All()} + } + m, err := remote.NewManager(client.Config, phase, selectors...) + if err != nil { + log.Error(err) + e := err.Error() + response.Error = &e + transaction.Complete(false) + err = webservice.WebSocketSend(response) + if err != nil { + log.Error(err) + } + return + } + + action := request.SubComponent + if len(m.Hosts) != 1 { + e := fmt.Sprintf("More than one node found cannot complete %s on %s", action, target) + log.Error(&e) + response.Error = &e + transaction.Complete(false) + err = webservice.WebSocketSend(response) + if err != nil { + log.Error(err) + } + return + } + + host := m.Hosts[0] + switch action { + case configs.EjectMedia: + err = host.EjectVirtualMedia(host.Context) + case configs.PowerOff: + err = host.SystemPowerOff(host.Context) + case configs.PowerOn: + err = host.SystemPowerOn(host.Context) + case configs.Reboot: + err = host.RebootSystem(host.Context) + } + + if err != nil { + log.Error(err) + e := err.Error() + response.Error = &e + transaction.Complete(false) + err = webservice.WebSocketSend(response) + if err != nil { + log.Error(err) + } + return + } + + s := fmt.Sprintf("%s on %s completed successfully", action, target) + response.Message = &s + transaction.Complete(true) + err = webservice.WebSocketSend(response) + if err != nil { + log.Error(err) + } +} diff --git a/pkg/ctl/cluster.go b/pkg/ctl/cluster.go index bb56d6f..194b7a7 100644 --- a/pkg/ctl/cluster.go +++ b/pkg/ctl/cluster.go @@ -22,7 +22,7 @@ import ( // HandleClusterRequest will flop between requests so we don't have to have them all mapped as function calls // This will wait for the sub component to complete before responding. The assumption is this is an async request -func HandleClusterRequest(request configs.WsMessage) configs.WsMessage { +func HandleClusterRequest(user *string, request configs.WsMessage) configs.WsMessage { response := configs.WsMessage{ Type: configs.CTL, Component: configs.Baremetal, @@ -30,7 +30,7 @@ func HandleClusterRequest(request configs.WsMessage) configs.WsMessage { } var err error - var message string + var message *string subComponent := request.SubComponent switch subComponent { @@ -45,7 +45,8 @@ func HandleClusterRequest(request configs.WsMessage) configs.WsMessage { } if err != nil { - response.Error = err.Error() + e := err.Error() + response.Error = &e } else { response.Message = message } diff --git a/pkg/ctl/config.go b/pkg/ctl/config.go index cf7d54b..8e4bcc4 100644 --- a/pkg/ctl/config.go +++ b/pkg/ctl/config.go @@ -22,7 +22,7 @@ import ( // HandleConfigRequest will flop between requests so we don't have to have them all mapped as function calls // This will wait for the sub component to complete before responding. The assumption is this is an async request -func HandleConfigRequest(request configs.WsMessage) configs.WsMessage { +func HandleConfigRequest(user *string, request configs.WsMessage) configs.WsMessage { response := configs.WsMessage{ Type: configs.CTL, Component: configs.Baremetal, @@ -30,7 +30,7 @@ func HandleConfigRequest(request configs.WsMessage) configs.WsMessage { } var err error - var message string + var message *string subComponent := request.SubComponent switch subComponent { @@ -59,7 +59,8 @@ func HandleConfigRequest(request configs.WsMessage) configs.WsMessage { } if err != nil { - response.Error = err.Error() + e := err.Error() + response.Error = &e } else { response.Message = message } diff --git a/pkg/ctl/document.go b/pkg/ctl/document.go index e4f691a..47b85aa 100644 --- a/pkg/ctl/document.go +++ b/pkg/ctl/document.go @@ -39,7 +39,7 @@ var ( ) // HandleDocumentRequest will flop between requests so we don't have to have them all mapped as function calls -func HandleDocumentRequest(request configs.WsMessage) configs.WsMessage { +func HandleDocumentRequest(user *string, request configs.WsMessage) configs.WsMessage { response := configs.WsMessage{ Type: configs.CTL, Component: configs.Document, @@ -47,12 +47,13 @@ func HandleDocumentRequest(request configs.WsMessage) configs.WsMessage { } var err error - var message string + var message *string var id string client, err := NewClient(AirshipConfigPath, KubeConfigPath, request) if err != nil { - response.Error = err.Error() + e := err.Error() + response.Error = &e return response } @@ -64,32 +65,36 @@ func HandleDocumentRequest(request configs.WsMessage) configs.WsMessage { case configs.YamlWrite: id = request.ID response.Name, response.YAML, err = client.writeYamlFile(id, request.YAML) - message = fmt.Sprintf("File '%s' saved successfully", response.Name) + s := fmt.Sprintf("File '%s' saved successfully", response.Name) + message = &s case configs.GetYaml: id = request.ID message = request.Message - response.Name, response.YAML, err = client.getYaml(id, message) + response.Name, response.YAML, err = client.getYaml(id, *message) case configs.GetPhaseTree: response.Data, err = client.GetPhaseTree() case configs.GetPhase: id = request.ID - message = "rendered" + s := "rendered" + message = &s response.Name, response.Details, response.YAML, err = client.GetPhase(id) case configs.GetDocumentsBySelector: id = request.ID - response.Data, err = GetDocumentsBySelector(request.ID, request.Message) + response.Data, err = GetDocumentsBySelector(request.ID, *request.Message) case configs.GetTarget: message = client.getTarget() case configs.GetExecutorDoc: id = request.ID - message = "rendered" + s := "rendered" + message = &s response.Name, response.YAML, err = client.GetExecutorDoc(id) default: err = fmt.Errorf("Subcomponent %s not found", request.SubComponent) } if err != nil { - response.Error = err.Error() + e := err.Error() + response.Error = &e } else { response.Message = message response.ID = id @@ -126,13 +131,16 @@ func (c *Client) GetExecutorDoc(id string) (string, string, error) { return title, base64.StdEncoding.EncodeToString(bytes), nil } -func (c *Client) getTarget() string { +func (c *Client) getTarget() *string { + var s string m, err := c.Config.CurrentContextManifest() if err != nil { - return "unknown" + s = "unknown" + return &s } - return filepath.Join(m.TargetPath, m.SubPath) + s = filepath.Join(m.TargetPath, m.SubPath) + return &s } func (c *Client) getPhaseDetails(id ifc.ID) (string, error) { @@ -301,14 +309,15 @@ func (c *Client) GetPhase(id string) (string, string, string, error) { return title, details, base64.StdEncoding.EncodeToString(buf.Bytes()), nil } -func (c *Client) docPull() (string, error) { - var message string +func (c *Client) docPull() (*string, error) { + var message *string cfgFactory := config.CreateFactory(AirshipConfigPath, KubeConfigPath) // 2nd arg is noCheckout, I assume we want to checkout the repo, // so setting to false err := pull.Pull(cfgFactory, false) if err == nil { - message = fmt.Sprintf("Success") + s := fmt.Sprintf("Success") + message = &s } return message, err diff --git a/pkg/ctl/document_test.go b/pkg/ctl/document_test.go index 6996a38..48de7f3 100644 --- a/pkg/ctl/document_test.go +++ b/pkg/ctl/document_test.go @@ -34,13 +34,15 @@ func TestHandleUnknownDocumentSubComponent(t *testing.T) { AirshipConfigPath = &acp KubeConfigPath = &kcp - response := HandleDocumentRequest(request) + user := "test" + response := HandleDocumentRequest(&user, request) + e := "Subcomponent fake_subcomponent not found" expected := configs.WsMessage{ Type: configs.CTL, Component: configs.Document, SubComponent: "fake_subcomponent", - Error: "Subcomponent fake_subcomponent not found", + Error: &e, } assert.Equal(t, expected, response) diff --git a/pkg/ctl/image.go b/pkg/ctl/image.go index 4a5c9fc..e8745d1 100644 --- a/pkg/ctl/image.go +++ b/pkg/ctl/image.go @@ -15,16 +15,15 @@ package ctl import ( + "errors" "fmt" - "opendev.org/airship/airshipctl/pkg/bootstrap/isogen" - "opendev.org/airship/airshipctl/pkg/config" "opendev.org/airship/airshipui/pkg/configs" ) // HandleImageRequest will flop between requests so we don't have to have them all mapped as function calls // This will wait for the sub component to complete before responding. The assumption is this is an async request -func HandleImageRequest(request configs.WsMessage) configs.WsMessage { +func HandleImageRequest(user *string, request configs.WsMessage) configs.WsMessage { response := configs.WsMessage{ Type: configs.CTL, Component: configs.Baremetal, @@ -32,11 +31,12 @@ func HandleImageRequest(request configs.WsMessage) configs.WsMessage { } var err error - var message string + var message *string client, err := NewClient(AirshipConfigPath, KubeConfigPath, request) if err != nil { - response.Error = err.Error() + e := err.Error() + response.Error = &e return response } @@ -54,7 +54,8 @@ func HandleImageRequest(request configs.WsMessage) configs.WsMessage { } if err != nil { - response.Error = err.Error() + e := err.Error() + response.Error = &e } else { response.Message = message } @@ -62,17 +63,7 @@ func HandleImageRequest(request configs.WsMessage) configs.WsMessage { return response } -func (c *Client) generateIso() (string, error) { - var message string - - cfgFactory := config.CreateFactory(AirshipConfigPath, KubeConfigPath) - - // setting "progress" to false since we don't need to see CLI - // progress bar in UI - err := isogen.GenerateBootstrapIso(cfgFactory, false) - if err == nil { - message = fmt.Sprintf("Success") - } - - return message, err +func (c *Client) generateIso() (*string, error) { + err := errors.New("Isogen is no longer available") + return nil, err } diff --git a/pkg/ctl/image_test.go b/pkg/ctl/image_test.go index 434240e..5a90724 100644 --- a/pkg/ctl/image_test.go +++ b/pkg/ctl/image_test.go @@ -34,13 +34,15 @@ func TestHandleUnknownBaremetalSubComponent(t *testing.T) { AirshipConfigPath = &acp KubeConfigPath = &kcp - response := HandleBaremetalRequest(request) + user := "test" + response := HandleBaremetalRequest(&user, request) + e := "Subcomponent fake_subcomponent not found" expected := configs.WsMessage{ Type: configs.CTL, Component: configs.Baremetal, SubComponent: "fake_subcomponent", - Error: "Subcomponent fake_subcomponent not found", + Error: &e, } assert.Equal(t, expected, response) diff --git a/pkg/ctl/phase.go b/pkg/ctl/phase.go index f98ae94..23ce347 100644 --- a/pkg/ctl/phase.go +++ b/pkg/ctl/phase.go @@ -26,7 +26,7 @@ import ( // HandlePhaseRequest will flop between requests so we don't have to have them all mapped as function calls // This will wait for the sub component to complete before responding. The assumption is this is an async request -func HandlePhaseRequest(request configs.WsMessage) configs.WsMessage { +func HandlePhaseRequest(user *string, request configs.WsMessage) configs.WsMessage { response := configs.WsMessage{ Type: configs.CTL, Component: configs.Document, // setting this to Document for now since that's handling phase requests @@ -34,12 +34,13 @@ func HandlePhaseRequest(request configs.WsMessage) configs.WsMessage { } var err error - var message string + var message *string var valid bool client, err := NewClient(AirshipConfigPath, KubeConfigPath, request) if err != nil { - response.Error = err.Error() + e := err.Error() + response.Error = &e return response } @@ -59,7 +60,8 @@ func HandlePhaseRequest(request configs.WsMessage) configs.WsMessage { } if err != nil { - response.Error = err.Error() + e := err.Error() + response.Error = &e } else { response.Message = message } @@ -70,12 +72,12 @@ func HandlePhaseRequest(request configs.WsMessage) configs.WsMessage { // this helper function will likely disappear once a clear workflow for // phase validation takes shape in UI. For now, it simply returns a // string message to be displayed as a toast in frontend client -func validateHelper(valid bool) string { +func validateHelper(valid bool) *string { msg := "invalid" if valid { msg = "valid" } - return msg + return &msg } // ValidatePhase validates the specified phase diff --git a/pkg/ctl/processor.go b/pkg/ctl/processor.go index e8dc648..8123211 100644 --- a/pkg/ctl/processor.go +++ b/pkg/ctl/processor.go @@ -118,12 +118,13 @@ func (p *UIEventProcessor) processClusterctlEvent(e events.ClusterctlEvent) { } func sendEventMessage(sessionID, eventType, message string) { + m := fmt.Sprintf("%s: %s", eventType, message) err := webservice.WebSocketSend(configs.WsMessage{ SessionID: sessionID, Type: configs.CTL, Component: configs.Document, // probably will change to configs.Phase soon SubComponent: configs.Run, - Message: fmt.Sprintf("%s: %s", eventType, message), + Message: &m, }) if err != nil { log.Errorf("Error sending message %s", err) diff --git a/pkg/ctl/secret.go b/pkg/ctl/secret.go index 04a95ab..ab7fba8 100644 --- a/pkg/ctl/secret.go +++ b/pkg/ctl/secret.go @@ -22,7 +22,7 @@ import ( // HandleSecretRequest will flop between requests so we don't have to have them all mapped as function calls // This will wait for the sub component to complete before responding. The assumption is this is an async request -func HandleSecretRequest(request configs.WsMessage) configs.WsMessage { +func HandleSecretRequest(user *string, request configs.WsMessage) configs.WsMessage { response := configs.WsMessage{ Type: configs.CTL, Component: configs.Baremetal, @@ -30,7 +30,7 @@ func HandleSecretRequest(request configs.WsMessage) configs.WsMessage { } var err error - var message string + var message *string subComponent := request.SubComponent switch subComponent { @@ -41,7 +41,8 @@ func HandleSecretRequest(request configs.WsMessage) configs.WsMessage { } if err != nil { - response.Error = err.Error() + e := err.Error() + response.Error = &e } else { response.Message = message } diff --git a/pkg/statistics/recorder.go b/pkg/statistics/recorder.go index 41de926..4f29d4d 100755 --- a/pkg/statistics/recorder.go +++ b/pkg/statistics/recorder.go @@ -31,8 +31,10 @@ type Transaction struct { Table configs.WsComponentType SubComponent configs.WsSubComponentType User *string + ActionType *string Target *string Started int64 + Recordable bool } var ( @@ -46,15 +48,23 @@ const ( tableCreate = `CREATE TABLE IF NOT EXISTS table ( subcomponent varchar(64) null, user varchar(64), - target varchar(64) null, + type text check(type in ('direct', 'phase')) null, + target text null, success tinyint(1) default 0, started timestamp, elapsed bigint, - stopped timestamp, - primary key (subcomponent, user, started, stopped))` + stopped timestamp)` // the prepared statement used for inserts // TODO (aschiefe): determine if we need to batch inserts - insert = "INSERT INTO table(subcomponent, user, target, success, started, elapsed, stopped) values(?,?,?,?,?,?,?)" + insert = `INSERT INTO table(subcomponent, + user, + type, + target, + success, + started, + elapsed, + stopped) + values(?,?,?,?,?,?,?,?)` ) // Init will create the database if it doesn't exist or open the existing database @@ -99,19 +109,21 @@ func createTables() error { } // NewTransaction establishes the transaction which will record -func NewTransaction(request configs.WsMessage, user *string) *Transaction { +func NewTransaction(user *string, request configs.WsMessage) *Transaction { return &Transaction{ Table: request.Component, SubComponent: request.SubComponent, + ActionType: request.ActionType, Target: request.Target, Started: time.Now().UnixNano() / 1000000, User: user, + Recordable: isRecordable(request), } } // Complete will put an entry into the statistics database for the transaction -func (transaction *Transaction) Complete(errorMessagePresent bool) { - if transaction.User != nil && transaction.isRecordable() { +func (transaction *Transaction) Complete(errorMessageNotPresent bool) { + if transaction.User != nil && transaction.Recordable { stmt, err := db.Prepare(strings.ReplaceAll(insert, "table", string(transaction.Table))) if err != nil { log.Error(err) @@ -122,7 +134,7 @@ func (transaction *Transaction) Complete(errorMessagePresent bool) { stopped := time.Now().UnixNano() / 1000000 success := 0 - if errorMessagePresent { + if errorMessageNotPresent { success = 1 } @@ -130,6 +142,7 @@ func (transaction *Transaction) Complete(errorMessagePresent bool) { defer writeMutex.Unlock() result, err := stmt.Exec(transaction.SubComponent, transaction.User, + transaction.ActionType, transaction.Target, success, started, @@ -152,16 +165,28 @@ func (transaction *Transaction) Complete(errorMessagePresent bool) { } // isRecordable will shuffle through the transaction and determine if we should write it to the database -func (transaction *Transaction) isRecordable() bool { +func isRecordable(request configs.WsMessage) bool { recordable := true - if transaction.Table == configs.Auth { + // don't record auth attempts + if request.Component == configs.Auth { recordable = false } - switch transaction.SubComponent { - case configs.GetTarget: - recordable = false - case configs.GetPhaseTree: + + // don't record default get data events + switch request.SubComponent { + case configs.GetTarget, + configs.GetDefaults, + configs.GetPhaseTree, + configs.GetPhase, + configs.GetYaml, + configs.GetDocumentsBySelector: recordable = false } + + // don't request actions taken against multiple targets, the individual action will be recorded + if request.Targets != nil { + recordable = false + } + return recordable } diff --git a/pkg/webservice/auth.go b/pkg/webservice/auth.go index afd3fd6..9c79e02 100755 --- a/pkg/webservice/auth.go +++ b/pkg/webservice/auth.go @@ -31,7 +31,7 @@ import ( var jwtKey = []byte("airshipUI_JWT_key") // The UI will either request authentication or validation, handle those situations here -func handleAuth(request configs.WsMessage) configs.WsMessage { +func handleAuth(_ *string, request configs.WsMessage) configs.WsMessage { response := configs.WsMessage{ Type: configs.UI, Component: configs.Auth, @@ -66,10 +66,10 @@ func handleAuth(request configs.WsMessage) configs.WsMessage { if err != nil { log.Error(err) - response.Error = err.Error() + e := err.Error() + response.Error = &e response.SubComponent = configs.Denied } - return response } diff --git a/pkg/webservice/websocket.go b/pkg/webservice/websocket.go index 03ed26d..82bc73b 100644 --- a/pkg/webservice/websocket.go +++ b/pkg/webservice/websocket.go @@ -47,7 +47,7 @@ var upgrader = websocket.Upgrader{ // this is a way to allow for arbitrary messages to be processed by the backend // the message of a specifc component is shunted to that subsystem for further processing -var functionMap = map[configs.WsRequestType]map[configs.WsComponentType]func(configs.WsMessage) configs.WsMessage{ +var funcMap = map[configs.WsRequestType]map[configs.WsComponentType]func(*string, configs.WsMessage) configs.WsMessage{ configs.UI: { configs.Keepalive: keepaliveReply, configs.Auth: handleAuth, @@ -58,8 +58,8 @@ var functionMap = map[configs.WsRequestType]map[configs.WsComponentType]func(con // It does however require them to implement an init function to append them // TODO: maybe some form of an interface to enforce this may be necessary? func AppendToFunctionMap(requestType configs.WsRequestType, - functions map[configs.WsComponentType]func(configs.WsMessage) configs.WsMessage) { - functionMap[requestType] = functions + functions map[configs.WsComponentType]func(*string, configs.WsMessage) configs.WsMessage) { + funcMap[requestType] = functions } // handle the origin request & upgrade to websocket @@ -107,11 +107,12 @@ func (session *session) onMessage() { } if err != nil { // deny the request if we get a bad token, this will force the UI to a login screen + e := "Invalid token, authentication denied" response := configs.WsMessage{ Type: configs.UI, Component: configs.Auth, SubComponent: configs.Denied, - Error: "Invalid token, authentication denied", + Error: &e, } if err = session.webSocketSend(response); err != nil { session.onError(err) @@ -119,17 +120,17 @@ func (session *session) onMessage() { } else { // This is the middleware to be able to record when a transaction starts and ends for the statistics recorder // It is possible for the backend to send messages without a valid user - transaction := statistics.NewTransaction(request, user) + transaction := statistics.NewTransaction(user, request) // look through the function map to find the type to handle the request - if reqType, ok := functionMap[request.Type]; ok { + if reqType, ok := funcMap[request.Type]; ok { // the function map may have a component (function) to process the request if component, ok := reqType[request.Component]; ok { - response := component(request) + response := component(user, request) if err = session.webSocketSend(response); err != nil { session.onError(err) } - go transaction.Complete(len(response.Error) == 0) + go transaction.Complete(response.Error == nil) } else { if err = session.webSocketSend(requestErrorHelper(fmt.Sprintf("Requested component: %s, not found", request.Component), request)); err != nil { @@ -164,7 +165,7 @@ func (session *session) onError(err error) { } // The UI will occasionally ping the server due to the websocket default timeout -func keepaliveReply(configs.WsMessage) configs.WsMessage { +func keepaliveReply(*string, configs.WsMessage) configs.WsMessage { return configs.WsMessage{ Type: configs.UI, Component: configs.Keepalive, @@ -176,7 +177,7 @@ func requestErrorHelper(err string, request configs.WsMessage) configs.WsMessage return configs.WsMessage{ Type: request.Type, Component: request.Component, - Error: err, + Error: &err, } }