Skip to content

Commit 79384fc

Browse files
authored
various improvements (#118)
* STAC-24201: infer STS_CLUSTER_NAME from agent deployments * STAC-25034: strip trailing slashes from url before saving * STAC-24238: render correct urls for pv, pvcs * STAC-24259: show UNOBSERVED when agent is not installed in a cluster * STAC-24632: validate service token when saving configuration * STAC-25130: include role, rolebinding for users to read extension configuration (secret) * STAC-23610: open links to observability in new tab * STAC-23606: link to observability from health state of cluster * STAC-0: explain goal of regexp
1 parent e41310f commit 79384fc

12 files changed

Lines changed: 174 additions & 71 deletions

.changeset/metal-maps-obey.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"observability": patch
3+
---
4+
5+
various bugs
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
apiVersion: rbac.authorization.k8s.io/v1
2+
kind: Role
3+
metadata:
4+
name: suse-observability-extension-user
5+
namespace: suse-observability-extension
6+
rules:
7+
- apiGroups:
8+
- ""
9+
resources:
10+
- secrets
11+
verbs:
12+
- get
13+
- list
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
apiVersion: rbac.authorization.k8s.io/v1
2+
kind: RoleBinding
3+
metadata:
4+
name: suse-observability-extension-user
5+
namespace: suse-observability-extension
6+
roleRef:
7+
apiGroup: rbac.authorization.k8s.io
8+
kind: Role
9+
name: suse-observability-extension-user
10+
subjects:
11+
- apiGroup: rbac.authorization.k8s.io
12+
kind: Group
13+
name: system:authenticated

pkg/observability/components/ComponentHealth.vue

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
<script>
22
import { mapGetters } from "vuex";
3-
import { loadSuseObservabilitySettings } from "../modules/rancher";
3+
import {
4+
loadSuseObservabilitySettings,
5+
loadAgentStatus,
6+
AgentStatus,
7+
} from "../modules/rancher";
48
import {
59
ConnectionStatus,
610
FetchError,
@@ -31,17 +35,21 @@ export default {
3135
clusterId() {
3236
return this.currentCluster?.id;
3337
},
34-
35-
componentIdentifier() {
36-
const cluster = this.currentCluster?.spec.displayName;
37-
38-
return buildUrn(this.resource, cluster);
39-
},
4038
},
4139
async fetch() {
42-
const settings = await loadSuseObservabilitySettings(this.$store);
40+
const agentStatus = await loadAgentStatus(
41+
this.$store,
42+
this.currentCluster?.id,
43+
);
44+
if (agentStatus.status === AgentStatus.NotInstalled) {
45+
this.health = HEALTH_STATE_TYPES.UNOBSERVED;
46+
return;
47+
}
48+
const clusterName =
49+
agentStatus.clusterName ?? this.currentCluster.spec.displayName;
4350
44-
this.urn = this.componentIdentifier;
51+
const settings = await loadSuseObservabilitySettings(this.$store);
52+
this.urn = buildUrn(this.resource, clusterName);
4553
if (!this.urn || !settings) {
4654
return;
4755
}

pkg/observability/components/Dashboard/ConfigurationView.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ export default {
8181
},
8282
8383
async save(btnCb) {
84+
// strip all trailing slashes
85+
this.suseObservabilityURL = this.suseObservabilityURL.replace(/\/+$/, "");
8486
const conn = await checkConnection({
8587
apiURL: this.suseObservabilityURL,
8688
serviceToken: this.suseObservabilityServiceToken,
@@ -194,7 +196,7 @@ export default {
194196
<div class="banner-info">
195197
<p>{{ t("observability.dashboard.connected") }}</p>
196198
<!-- reserve a line when the url is an empty string so UI won't jump on change -->
197-
<a :href="`${suseObservabilityURL}/`">{{
199+
<a :href="`${suseObservabilityURL}/`" target="_blank">{{
198200
suseObservabilityURL || "&nbsp;"
199201
}}</a>
200202
</div>

pkg/observability/components/MonitorTab.vue

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import LiveDate from "@shell/components/formatter/LiveDate.vue";
33
import SortableTable from "@shell/components/SortableTable";
44
import { mapGetters } from "vuex";
55
6-
import { loadSuseObservabilitySettings } from "../modules/rancher";
6+
import {
7+
AgentStatus,
8+
loadAgentStatus,
9+
loadSuseObservabilitySettings,
10+
} from "../modules/rancher";
711
import {
812
loadComponent,
913
loadObservationStatus,
@@ -48,30 +52,6 @@ export default {
4852
hasMonitors() {
4953
return this.monitors?.length > 0;
5054
},
51-
52-
componentIdentifier() {
53-
const cluster = this.currentCluster?.spec.displayName;
54-
55-
if (!cluster) {
56-
return "";
57-
}
58-
59-
let identifier = `urn:kubernetes:/${cluster}`;
60-
61-
const resourceData = this.resource?.metadata
62-
? this.resource
63-
: this.currResourceData;
64-
65-
if (resourceData?.metadata?.namespace) {
66-
identifier += `:${resourceData.metadata.namespace}`;
67-
}
68-
69-
identifier += `:${mapKind(resourceData?.type?.toLowerCase())}/${
70-
resourceData?.metadata?.name
71-
}`;
72-
73-
return identifier;
74-
},
7555
},
7656
async fetch() {
7757
if (!this.resource?.metadata) {
@@ -89,11 +69,35 @@ export default {
8969
});
9070
}
9171
92-
const settings = await loadSuseObservabilitySettings(this.$store);
72+
const agentStatus = await loadAgentStatus(
73+
this.$store,
74+
this.currentCluster?.id,
75+
);
76+
if (agentStatus.status === AgentStatus.NotInstalled) {
77+
this.observationStatus = ObservationStatus.NotDeployed;
78+
return;
79+
}
80+
const clusterName =
81+
agentStatus.clusterName ?? this.currentCluster?.spec.displayName;
82+
83+
let identifier = `urn:kubernetes:/${clusterName}`;
84+
85+
const resourceData = this.resource?.metadata
86+
? this.resource
87+
: this.currResourceData;
88+
89+
if (resourceData?.metadata?.namespace) {
90+
identifier += `:${resourceData.metadata.namespace}`;
91+
}
92+
93+
identifier += `:${mapKind(resourceData?.type?.toLowerCase())}/${
94+
resourceData?.metadata?.name
95+
}`;
9396
94-
this.urn = this.componentIdentifier;
97+
this.urn = identifier;
9598
9699
try {
100+
const settings = await loadSuseObservabilitySettings(this.$store);
97101
const component = await loadComponent(settings, this.urn);
98102
this.monitors = component.syncedCheckStates;
99103

pkg/observability/components/ObservabilityClusterCard.vue

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@ export default {
3939
data() {
4040
return {
4141
observationStatus: ObservationStatus.Observed,
42-
agentStatus: AgentStatus.Installed,
42+
agentStatus: {
43+
status: AgentStatus.Installed,
44+
},
4345
snapshot: undefined,
4446
deviating: 0,
4547
critical: 0,
4648
isConfigured: true,
4749
installUrl: undefined,
50+
componentUrl: undefined,
4851
HEALTH_STATE_TYPES,
4952
ObservationStatus,
5053
AgentStatus,
@@ -62,17 +65,24 @@ export default {
6265
}
6366
this.installUrl = `${settings.url}/#/stackpacks/kubernetes-v2`;
6467
65-
this.observationStatus = await loadObservationStatus(
66-
this.resource.spec.displayName,
67-
settings,
68-
);
69-
if (this.observationStatus === ObservationStatus.NotDeployed) {
70-
this.agentStatus = await loadAgentStatus(this.$store, this.resource.id);
68+
this.agentStatus = await loadAgentStatus(this.$store, this.resource.id);
69+
if (this.agentStatus.status === AgentStatus.NotInstalled) {
70+
this.observationStatus = ObservationStatus.NotDeployed;
71+
return;
7172
}
73+
const clusterName =
74+
this.agentStatus.clusterName ?? this.resource.spec.displayName;
75+
76+
const componentIdentifier = `urn:cluster:/kubernetes:${clusterName}`;
77+
this.componentUrl = `${settings.url}/#/components/${encodeURIComponent(
78+
componentIdentifier,
79+
)}`;
80+
81+
this.observationStatus = await loadObservationStatus(clusterName, settings);
7282
7383
try {
7484
this.snapshot = await getSnapshot(
75-
`not healthstate in ("CLEAR", "UNKNOWN") AND label = "cluster-name:${this.resource.spec.displayName}"`,
85+
`not healthstate in ("CLEAR", "UNKNOWN") AND label = "cluster-name:${clusterName}"`,
7686
settings,
7787
);
7888
for (const component of this.snapshot.viewSnapshotResponse.components) {
@@ -121,10 +131,13 @@ export default {
121131
{{ t("observability.clusterCard.clusterIs") }}
122132
<HealthState class="state-badge" health="unobserved" color="grey" />
123133
</p>
124-
<div v-if="agentStatus !== AgentStatus.Installed" class="flex-text">
134+
<div
135+
v-if="agentStatus.status !== AgentStatus.Installed"
136+
class="flex-text"
137+
>
125138
<p>
126139
{{ t("observability.clusterCard.notObservedPrepend") }}
127-
<a :href="installUrl">{{
140+
<a :href="installUrl" target="_blank">{{
128141
t("observability.clusterCard.notObservedInstall")
129142
}}</a>
130143
{{ t("observability.clusterCard.notObservedPostpend") }}
@@ -133,7 +146,7 @@ export default {
133146
<div v-else>
134147
<p>
135148
{{ t("observability.clusterCard.noDataPrepend") }}
136-
<a :href="installUrl">{{
149+
<a :href="installUrl" target="_blank">{{
137150
t("observability.clusterCard.noDataInstall")
138151
}}</a>
139152
{{ t("observability.clusterCard.noDataPostpend") }}
@@ -143,7 +156,13 @@ export default {
143156
<div v-else>
144157
<p class="mb-20">
145158
{{ t("observability.clusterCard.clusterHealth") }}
146-
<HealthState health="observed" color="green" />
159+
<a
160+
:href="componentUrl"
161+
target="_blank"
162+
rel="nofollow noopener noreferrer"
163+
>
164+
<HealthState health="observed" color="green" />
165+
</a>
147166
</p>
148167
<div>
149168
<p>{{ t("observability.clusterCard.componentsHealth") }}</p>

pkg/observability/formatters/ComponentLinkedHealthState.vue

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
<script>
22
import { mapGetters } from "vuex";
33
import HealthState from "../components/Health/HealthState";
4-
import { loadSuseObservabilitySettings } from "../modules/rancher";
4+
import {
5+
loadSuseObservabilitySettings,
6+
loadAgentStatus,
7+
AgentStatus,
8+
} from "../modules/rancher";
59
import {
610
ConnectionStatus,
711
FetchError,
@@ -27,12 +31,6 @@ export default {
2731
computed: {
2832
...mapGetters(["currentCluster"]),
2933
30-
componentIdentifier() {
31-
const cluster = this.currentCluster?.spec.displayName;
32-
33-
return buildUrn(this.row, cluster);
34-
},
35-
3634
color() {
3735
switch (this.value) {
3836
case "active":
@@ -54,11 +52,23 @@ export default {
5452
},
5553
5654
async fetch() {
57-
const componentIdentifier = this.componentIdentifier;
55+
const agentStatus = await loadAgentStatus(
56+
this.$store,
57+
this.currentCluster?.id,
58+
);
59+
if (agentStatus.status === AgentStatus.NotInstalled) {
60+
this.data = {
61+
health: HEALTH_STATE_TYPES.UNOBSERVED,
62+
};
63+
this.isLoading = false;
64+
return;
65+
}
5866
67+
const clusterName =
68+
agentStatus.clusterName ?? this.currentCluster.spec.displayName;
69+
const componentIdentifier = buildUrn(this.row, clusterName);
5970
if (!componentIdentifier) {
6071
this.isLoading = false;
61-
6272
return;
6373
}
6474

pkg/observability/modules/rancher.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,15 @@ export enum AgentStatus {
125125
ConnectionError,
126126
}
127127

128+
export interface ObservabilityAgent {
129+
status: AgentStatus;
130+
clusterName?: string;
131+
}
132+
128133
export async function loadAgentStatus(
129134
store: any,
130135
clusterId: string,
131-
): Promise<AgentStatus> {
136+
): Promise<ObservabilityAgent> {
132137
try {
133138
const response = await store.dispatch(`cluster/request`, {
134139
url: `/k8s/clusters/${clusterId}/v1/apps.deployments`,
@@ -143,11 +148,23 @@ export async function loadAgentStatus(
143148
"stackstate-k8s-agent"),
144149
);
145150

146-
return deployments.length > 0
147-
? AgentStatus.Installed
148-
: AgentStatus.NotInstalled;
151+
if (deployments.length == 0) {
152+
return {
153+
status: AgentStatus.NotInstalled,
154+
};
155+
}
156+
const env = deployments[0].spec?.template.spec.containers[0].env;
157+
const clusterNames = env
158+
?.filter((envVar: any) => envVar.name === "STS_CLUSTER_NAME")
159+
.map((envVar: any) => envVar.value);
160+
return {
161+
status: AgentStatus.Installed,
162+
clusterName: clusterNames?.[0],
163+
};
149164
} catch (e) {
150-
return AgentStatus.ConnectionError;
165+
return {
166+
status: AgentStatus.ConnectionError,
167+
};
151168
}
152169
}
153170

pkg/observability/modules/suseObservability.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,17 @@ export async function checkConnection(
2929
const creds = token(credentials.serviceToken);
3030

3131
try {
32-
const resp = await fetch(`${credentials.apiURL}/api/server/info`, {
33-
credentials: "omit",
34-
mode: "cors",
35-
headers: {
36-
"Content-Type": "application/json",
37-
Authorization: creds,
32+
const resp = await fetch(
33+
`${credentials.apiURL}/api/user/authorization/for?permission=get-topology`,
34+
{
35+
credentials: "omit",
36+
mode: "cors",
37+
headers: {
38+
"Content-Type": "application/json",
39+
Authorization: creds,
40+
},
3841
},
39-
});
42+
);
4043
if (resp.ok) {
4144
return ConnectionStatus.Connected;
4245
} else {

0 commit comments

Comments
 (0)