Skip to content
This repository has been archived by the owner on Jan 24, 2023. It is now read-only.

Commit

Permalink
Merge pull request #384 from SUSE/kube-terminal
Browse files Browse the repository at this point in the history
Add support for a Kubernetes Terminal
  • Loading branch information
richard-cox committed Jun 26, 2020
2 parents 37f53d5 + 4c206ad commit 5c95d1c
Show file tree
Hide file tree
Showing 36 changed files with 1,278 additions and 15 deletions.
61 changes: 61 additions & 0 deletions build/tools/kube-terminal-dev.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env bash

# Colours
CYAN="\033[96m"
YELLOW="\033[93m"
RED="\033[91m"
RESET="\033[0m"
BOLD="\033[1m"

# Program Paths:
PROG=$(basename ${BASH_SOURCE[0]})
PROG_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
STRATOS_DIR="$( cd "${PROG_DIR}/../.." && pwd )"

echo "Creating Service Account"
SRC="${STRATOS_DIR}/deploy/kubernetes/console/templates/service-account.yaml"

TEMPFILE=$(mktemp)
cp $SRC $TEMPFILE
sed -i.bak '/\s*helm/d' $TEMPFILE
sed -i.bak '/\s*app\.kubernetes\.io\/version/d' $TEMPFILE
sed -i.bak '/\s*app\.kubernetes\.io\/instance/d' $TEMPFILE
sed -i.bak '/\s*{{-/d' $TEMPFILE

# Create a namespace
NS="stratos-dev"
kubectl get ns $NS > /dev/null 2>&1
if [ $? -ne 0 ]; then
kubectl create ns $NS
fi

kubectl apply -n $NS -f $TEMPFILE
USER=stratos-dev-admin-user
USER=stratos

# Service account should be created - now need to get token
SECRET=$(kubectl get -n $NS sa $USER -o json | jq -r '.secrets[0].name')
TOKEN=$(kubectl get -n $NS secret $SECRET -o json | jq -r '.data.token')
echo "Token secret: $SECRET"
TOKEN=$(echo $TOKEN | base64 -d -)
echo "Token $TOKEN"

rm -f $TEMPFILE
rm -f $TEMPFILE.bak

CFG=${STRATOS_DIR}/src/jetstream/config.properties
touch $CFG

echo -e "\n# Kubernetes Terminal Config for dev" >> $CFG
echo "STRATOS_KUBERNETES_NAMESPACE=stratos-dev" >> $CFG
echo "STRATOS_KUBERNETES_TERMINAL_IMAGE=splatform/stratos-kube-terminal:dev" >> $CFG
echo "KUBE_TERMINAL_SERVICE_ACCOUNT_TOKEN=$TOKEN" >> $CFG

MKUBE=$(minikube ip)
if [ $? -eq 0 ]; then
echo "KUBERNETES_SERVICE_HOST=$MKUBE" >> $CFG
echo "KUBERNETES_SERVICE_PORT=8443" >> $CFG
else
echo "KUBERNETES_SERVICE_HOST=" >> $CFG
echo "KUBERNETES_SERVICE_PORT=8443" >> $CFG
fi
4 changes: 4 additions & 0 deletions custom-src/deploy/kubernetes/__stratos.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@
value: "mongodb://{{ .Release.Name }}-fdbdoclayer:27016"
- name: SYNC_SERVER_URL
value: "http://{{ .Release.Name }}-chartsync:8080"
- name: STRATOS_KUBERNETES_NAMESPACE
value: "{{ .Release.Namespace }}"
- name: STRATOS_KUBERNETES_TERMINAL_IMAGE
value: "{{.Values.kube.registry.hostname}}/{{.Values.kube.organization}}/stratos-kube-terminal:{{.Values.consoleVersion}}"
{{- end }}
4 changes: 4 additions & 0 deletions custom-src/deploy/kubernetes/custom-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ function custom_image_build() {
# Build and push an image for the Helm Repo Sync Tool
log "-- Building/publishing Monocular Chart Repo Sync Tool"
patchAndPushImage stratos-chartsync Dockerfile "${STRATOS_PATH}/src/jetstream/plugins/monocular/chart-repo"

# Build and push an image for the Kubernetes Terminal
log "-- Building/publishing Kubernetes Terminal"
patchAndPushImage stratos-kube-terminal Dockerfile.kubeterminal "${STRATOS_PATH}/deploy/containers/kube-terminal"
}
1 change: 1 addition & 0 deletions custom-src/deploy/kubernetes/imagelist.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
stratos-kube-terminal:_VERSION_
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<app-page-header [breadcrumbs]="breadcrumbs$ | async">
<h1>Kubernetes Terminal</h1>
<div class="page-header-right">
<span>
<button mat-icon-button matTooltip="Disconnect" name="stop" *ngIf="sshViewer.isConnected" (click)="sshViewer.disconnect()" [disabled]="sshViewer.attemptingConnection">
<mat-icon>stop</mat-icon>
</button>
<button mat-icon-button matTooltip="Connect" name="start" *ngIf="!sshViewer.isConnected" (click)="sshViewer.reconnect()" [disabled]="sshViewer.attemptingConnection">
<mat-icon>play_arrow</mat-icon>
</button>
<button mat-icon-button matTooltip="Back" [routerLink]="kubeSummaryLink">
<mat-icon>close</mat-icon>
</button>
</span>
</div>
</app-page-header>

<app-ssh-viewer #sshViewer [errorMessage]="errorMessage" [sshStream]="messages" [sshInput]="sshInput" [connectionStatus]="connectionStatus"></app-ssh-viewer>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { createBasicStoreModule } from '@stratosui/store/testing';

import { ApplicationService } from '../../../../../cloud-foundry/src/features/applications/application.service';
import { ApplicationServiceMock } from '../../../../../cloud-foundry/test-framework/application-service-helper';
import { TabNavService } from '../../../../tab-nav.service';
import { CoreModule } from '../../../core/core.module';
import { SharedModule } from '../../../shared/shared.module';
import { KubeConsoleComponent } from './kube-console.component';

describe('KubeConsoleComponent', () => {
let component: KubeConsoleComponent;
let fixture: ComponentFixture<KubeConsoleComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [KubeConsoleComponent],
imports: [
CoreModule,
SharedModule,
RouterTestingModule,
createBasicStoreModule()
],
providers: [
{ provide: ApplicationService, useClass: ApplicationServiceMock },
TabNavService
],
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(KubeConsoleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { NEVER, Observable, Subject, Subscription } from 'rxjs';
import websocketConnect, { normalClosureMessage } from 'rxjs-websockets';
import { catchError, tap, switchMap, map } from 'rxjs/operators';

import { IHeaderBreadcrumb } from '../../../shared/components/page-header/page-header.types';
import { SshViewerComponent } from '../../../shared/components/ssh-viewer/ssh-viewer.component';
import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service';
import { BaseKubeGuid } from '../kubernetes-page.types';
import { KubernetesService } from '../services/kubernetes.service';


@Component({
selector: 'app-kube-console',
templateUrl: './kube-console.component.html',
styleUrls: ['./kube-console.component.scss'],
providers: [
{
provide: BaseKubeGuid,
useFactory: (activatedRoute: ActivatedRoute) => {
return {
guid: activatedRoute.snapshot.params.endpointId
};
},
deps: [
ActivatedRoute
]
},
KubernetesService,
KubernetesEndpointService,
]
})
export class KubeConsoleComponent implements OnInit {

public messages: Observable<string>;

public connectionStatus = new Subject<number>();

public sshInput: Subject<string>;

public errorMessage: string;

public connected: boolean;

public kubeSummaryLink: string;

public breadcrumbs$: Observable<IHeaderBreadcrumb[]>;

@ViewChild('sshViewer', { static: false }) sshViewer: SshViewerComponent;

constructor(
public kubeEndpointService: KubernetesEndpointService,
) { }

ngOnInit() {
this.connectionStatus.next(0);
const guid = this.kubeEndpointService.baseKube.guid;
this.kubeSummaryLink = `/kubernetes/${guid}/summary`;

if (!guid) {
this.messages = NEVER;
this.connectionStatus.next(0);
this.errorMessage = 'No Endpoint ID available';
} else {
const host = window.location.host;
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
const streamUrl = (
`${protocol}://${host}/pp/v1/kubeterminal/${guid}`
);
this.sshInput = new Subject<string>();
const connection = websocketConnect(streamUrl);

this.messages = connection.pipe(
tap(() => this.connectionStatus.next(1)),
switchMap(getResponse => getResponse(this.sshInput)),
catchError((e: Error) => {
if (e.message !== normalClosureMessage && !this.sshViewer.isConnected) {
this.errorMessage = 'Error launching Kubernetes Terminal';
}
return [];
}));

// Breadcrumbs
this.breadcrumbs$ = this.kubeEndpointService.endpoint$.pipe(
map(endpoint => ([{
breadcrumbs: [
{ value: endpoint.entity.name, routerLink: `/kubernetes/${endpoint.entity.guid}` },
]
}])
)
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import { KubernetesNamespacesTabComponent } from './tabs/kubernetes-namespaces-t
import { KubernetesNodesTabComponent } from './tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component';
import { KubernetesPodsTabComponent } from './tabs/kubernetes-pods-tab/kubernetes-pods-tab.component';
import { KubernetesSummaryTabComponent } from './tabs/kubernetes-summary-tab/kubernetes-summary.component';
import { KubeConsoleComponent } from './kube-terminal/kube-console.component';

/* tslint:disable:max-line-length */

Expand Down Expand Up @@ -142,6 +143,7 @@ import { KubernetesSummaryTabComponent } from './tabs/kubernetes-summary-tab/kub
NodePodCountComponent,
KubernetesServicePortsComponent,
KubernetesPodStatusComponent,
KubeConsoleComponent,
KubeServiceCardComponent,
KubernetesResourceViewerComponent,
KubeServiceCardComponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { KubernetesNodesTabComponent } from './tabs/kubernetes-nodes-tab/kuberne
import { KubernetesPodsTabComponent } from './tabs/kubernetes-pods-tab/kubernetes-pods-tab.component';
import { KubernetesSummaryTabComponent } from './tabs/kubernetes-summary-tab/kubernetes-summary.component';
import { KubedashConfigurationComponent } from './kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component';
import { KubeConsoleComponent } from './kube-terminal/kube-console.component';

const kubernetes: Routes = [{
path: '',
Expand Down Expand Up @@ -129,6 +130,13 @@ const kubernetes: Routes = [{
{
path: ':endpointId/dashboard-config',
component: KubedashConfigurationComponent,
},
{
path: ':endpointId/terminal',
component: KubeConsoleComponent,
data: {
uiNoMargin: true
}
}
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class KubernetesEndpointService {
kubeDashboardStatus$: Observable<KubeDashboardStatus>;
kubeDashboardLabel$: Observable<string>;
kubeDashboardConfigured$: Observable<boolean>;
kubeTerminalEnabled$: Observable<boolean>;

constructor(
public baseKube: BaseKubeGuid,
Expand Down Expand Up @@ -158,6 +159,11 @@ export class KubernetesEndpointService {
map(auth => auth.sessionData['plugin-config'].kubeDashboardEnabled === 'true')
);

this.kubeTerminalEnabled$ = this.store.select('auth').pipe(
filter(auth => !!auth.sessionData['plugin-config']),
map(auth => auth.sessionData['plugin-config'].kubeTerminalEnabled === 'true')
);

const kubeDashboardStatus$ = kubeEntityCatalog.dashboard.store.getEntityService(this.kubeGuid).waitForEntity$.pipe(
map(status => status.entity),
filter(status => !!status)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
<mat-icon>dashboard</mat-icon>
<span class="kube-details__button">View Dashboard</span>
</button>
<button *ngIf="kubeEndpointService.kubeTerminalEnabled$ | async" [routerLink]="kubeTerminalLink" mat-button>
<mat-icon>desktop_windows</mat-icon>
<span class="kube-details__button">Open Terminal</span>
</button>
</app-page-sub-nav>

<app-loading-page [isLoading]="isLoading$" [text]="'Retrieving Kubernetes details'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy {
source: SafeResourceUrl;

dashboardLink: string;
kubeTerminalLink: string;

public podCapacity$: Observable<ISimpleUsageChartData>;
public diskPressure$: Observable<ISimpleUsageChartData>;
public memoryPressure$: Observable<ISimpleUsageChartData>;
Expand Down Expand Up @@ -159,6 +161,7 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy {
warningText: `Nodes with unknown ready status found`
});
this.dashboardLink = `/kubernetes/${guid}/dashboard`;
this.kubeTerminalLink = `/kubernetes/${guid}/terminal`;

this.kubeNodeVersions$ = this.kubeEndpointService.getNodeKubeVersions(nodes$).pipe(startWith('-'));

Expand Down
1 change: 1 addition & 0 deletions deploy/ci/build-aio-image-canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ jobs:
tag: stratos/deploy/ci/tasks/build-images/canary-tag
tag_as_latest: false
labels_file: image-tag/image-labels
squash: true
build_args_file: image-tag/ui-build-args
build_args:
CANARY_BUILD: true
15 changes: 14 additions & 1 deletion deploy/ci/suse-console-dev-releases.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@ resources:
username: ((docker-username))
password: ((docker-password))
repository: ((docker-repository))/stratos-chartsync

- name: kube-terminal-image
type: docker-image
source:
username: ((docker-username))
password: ((docker-password))
repository: ((docker-repository))/stratos-kube-terminal

# Artifacts
- name: image-tag
type: s3
Expand Down Expand Up @@ -147,6 +153,13 @@ jobs:
tag: image-tag/v2-alpha-tag
patch_base_reg: ((patch-base-reg))
patch_base_tag: ((patch-base-tag))
- put: kube-terminal-image
params:
dockerfile: stratos/deploy/containers/kube-terminal/Dockerfile.kubeterminal
build: stratos/deploy/containers/kube-terminal
tag: image-tag/v2-alpha-tag
patch_base_reg: ((patch-base-reg))
patch_base_tag: ((patch-base-tag))
- do:
- put: ui-image
params:
Expand Down
12 changes: 10 additions & 2 deletions deploy/common-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function buildAndPublishImage {

# Proxy support
# Remove intermediate containers after a successful build
BUILD_ARGS="--rm=true --squash"
BUILD_ARGS="--rm=true"
RUN_ARGS=""
if [ -n "${http_proxy:-}" -o -n "${HTTP_PROXY:-}" ]; then
BUILD_ARGS="${BUILD_ARGS} --build-arg http_proxy=${http_proxy:-${HTTP_PROXY}}"
Expand All @@ -54,6 +54,15 @@ if [ -n "${https_proxy:-}" -o -n "${HTTPS_PROXY:-}" ]; then
RUN_ARGS="${RUN_ARGS} -e https_proxy=${https_proxy:-${HTTPS_PROXY}}"
fi

# Check if we can squash
CAN_SQUASH=$(docker info 2>&1 | grep "Experimental: true" -c | cat)
if [ "${CAN_SQUASH}" == "1" ]; then
BUILD_ARGS="${BUILD_ARGS} --squash"
echo "Images will be squashed"
else
echo "Images will NOT be squashed"
fi

# Use correct sed command for Mac
SED="sed -r"
unamestr=`uname`
Expand Down Expand Up @@ -104,7 +113,6 @@ function cleanup {
echo "-- Cleaning up ${STRATOS_PATH}"
rm -rf ${STRATOS_PATH}/dist
rm -rf ${STRATOS_PATH}/node_modules
rm -rf ${STRATOS_PATH}/bower_components
echo
echo "-- Cleaning up ${STRATOS_PATH}/deploy/containers/nginx/dist"
rm -rf ${STRATOS_PATH}/deploy/containers/nginx/dist
Expand Down
Loading

0 comments on commit 5c95d1c

Please sign in to comment.