diff --git a/server/version/version.go b/server/version/version.go index a151f23b16cb4..806cfcbd4a8f7 100644 --- a/server/version/version.go +++ b/server/version/version.go @@ -34,16 +34,15 @@ func (s *Server) Version(context.Context, *empty.Empty) (*version.VersionMessage } } if s.kustomizeVersion == "" { - kustomizeVersion, err := kustomize.Version() + kustomizeVersion, err := kustomize.Version(true) if err == nil { s.kustomizeVersion = kustomizeVersion } else { s.kustomizeVersion = err.Error() } - } if s.helmVersion == "" { - helmVersion, err := helm.Version() + helmVersion, err := helm.Version(true) if err == nil { s.helmVersion = helmVersion } else { diff --git a/ui/src/app/app.tsx b/ui/src/app/app.tsx index 68b3ce44f53d7..450627d169580 100644 --- a/ui/src/app/app.tsx +++ b/ui/src/app/app.tsx @@ -9,6 +9,7 @@ import applications from './applications'; import help from './help'; import login from './login'; import settings from './settings'; +import {VersionPanel} from './shared/components/version-info/version-info-panel'; import {Provider} from './shared/context'; import {services} from './shared/services'; import requests from './shared/services/requests'; @@ -52,7 +53,7 @@ const navItems = [ } ]; -const versionInfo = services.version.version(); +const versionLoader = services.version.version(); async function isExpiredSSO() { try { @@ -92,7 +93,7 @@ requests.onError.subscribe(async err => { } }); -export class App extends React.Component<{}, {popupProps: PopupProps; error: Error}> { +export class App extends React.Component<{}, {popupProps: PopupProps; showVersionPanel: boolean; error: Error}> { public static childContextTypes = { history: PropTypes.object, apis: PropTypes.object @@ -108,7 +109,7 @@ export class App extends React.Component<{}, {popupProps: PopupProps; error: Err constructor(props: {}) { super(props); - this.state = {popupProps: null, error: null}; + this.state = {popupProps: null, error: null, showVersionPanel: false}; this.popupManager = new PopupManager(); this.notificationsManager = new NotificationsManager(); this.navigationManager = new NavigationManager(history); @@ -186,11 +187,15 @@ export class App extends React.Component<{}, {popupProps: PopupProps; error: Err ( - versionInfo}> - {msg => ( - - {msg.Version} - + versionLoader}> + {version => ( + + + this.setState({showVersionPanel: true})}> + {version.Version} + + + )} )}> @@ -219,6 +224,7 @@ export class App extends React.Component<{}, {popupProps: PopupProps; error: Err + this.setState({showVersionPanel: false})} /> ); } diff --git a/ui/src/app/shared/components/version-info/version-info-panel.tsx b/ui/src/app/shared/components/version-info/version-info-panel.tsx new file mode 100644 index 0000000000000..757550e473c92 --- /dev/null +++ b/ui/src/app/shared/components/version-info/version-info-panel.tsx @@ -0,0 +1,111 @@ +import {DataLoader, SlidingPanel, Tooltip} from 'argo-ui'; +import * as React from 'react'; +import {VersionMessage} from '../../models'; + +interface VersionPanelProps { + isShown: boolean; + onClose: () => void; + version: Promise; +} + +type CopyState = 'success' | 'failed' | undefined; + +export class VersionPanel extends React.Component { + private readonly header = 'Argo CD Server Version'; + + constructor(props: VersionPanelProps) { + super(props); + this.state = {copyState: undefined}; + } + + public render() { + return ( + this.props.version}> + {version => { + return ( + this.props.onClose()} hasCloseButton={true} isNarrow={true}> +
{this.buildVersionTable(version)}
+
+ {this.getCopyButton(version)} +
+
+ ); + }} +
+ ); + } + + /** + * Formats the version data and renders the table rows. + */ + private buildVersionTable(version: VersionMessage): JSX.Element { + const formattedVersion = { + 'Argo CD': version.Version, + 'Build Date': version.BuildDate, + 'Go': version.GoVersion, + 'Compiler': version.Compiler, + 'Platform': version.Platform, + 'ksonnet': version.KsonnetVersion, + 'kustomize': version.KustomizeVersion, + 'Helm': version.HelmVersion, + 'kubectl': version.KubectlVersion + }; + + return ( + + {Object.entries(formattedVersion).map(([key, value]) => { + return ( +
+
+
+ {key} +
+
+ + {value} + +
+
+
+ ); + })} +
+ ); + } + + private getCopyButton(version: VersionMessage): JSX.Element { + let img: string; + let text: string; + if (this.state.copyState === 'success') { + img = 'fa-check'; + text = 'Copied'; + } else if (this.state.copyState === 'failed') { + img = 'fa-times'; + text = 'Copy Failed'; + } else { + img = 'fa-copy'; + text = 'Copy JSON'; + } + + return ( + + ); + } + + private async onCopy(version: VersionMessage): Promise { + const stringifiedVersion = JSON.stringify(version, undefined, 4); + try { + await navigator.clipboard.writeText(stringifiedVersion); + this.setState({copyState: 'success'}); + } catch (err) { + this.setState({copyState: 'failed'}); + } + + setTimeout(() => { + this.setState({copyState: undefined}); + }, 750); + } +} diff --git a/ui/src/app/shared/models.ts b/ui/src/app/shared/models.ts index fbdd059833a03..a8c166d8ddc51 100644 --- a/ui/src/app/shared/models.ts +++ b/ui/src/app/shared/models.ts @@ -697,6 +697,15 @@ export interface ApplicationSyncWindowState { export interface VersionMessage { Version: string; + BuildDate: string; + GoVersion: string; + Compiler: string; + Platform: string; + KsonnetVersion: string; + KustomizeVersion: string; + HelmVersion: string; + KubectlVersion: string; + JsonnetVersion: string; } export interface Token { diff --git a/util/helm/helm.go b/util/helm/helm.go index 67b662d3c1b98..5f44e33df9dac 100644 --- a/util/helm/helm.go +++ b/util/helm/helm.go @@ -86,8 +86,16 @@ func (h *helm) Dispose() { h.cmd.Close() } -func Version() (string, error) { - cmd := exec.Command("helm", "version", "--client") +func Version(shortForm bool) (string, error) { + executable := "helm" + cmdArgs := []string{"version", "--client"} + if shortForm { + cmdArgs = append(cmdArgs, "--short") + } + cmd := exec.Command(executable, cmdArgs...) + // example version output: + // long: "version.BuildInfo{Version:\"v3.3.1\", GitCommit:\"249e5215cde0c3fa72e27eb7a30e8d55c9696144\", GitTreeState:\"clean\", GoVersion:\"go1.14.7\"}" + // short: "v3.3.1+g249e521" version, err := executil.RunWithRedactor(cmd, redactor) if err != nil { return "", fmt.Errorf("could not get helm version: %s", err) diff --git a/util/helm/helm_test.go b/util/helm/helm_test.go index c31e67ca4db3b..27a79aad11d50 100644 --- a/util/helm/helm_test.go +++ b/util/helm/helm_test.go @@ -165,7 +165,7 @@ func TestHelmArgCleaner(t *testing.T) { } func TestVersion(t *testing.T) { - ver, err := Version() + ver, err := Version(false) assert.NoError(t, err) assert.NotEmpty(t, ver) } diff --git a/util/kustomize/kustomize.go b/util/kustomize/kustomize.go index 862e8e8f20c89..057fcfb638554 100644 --- a/util/kustomize/kustomize.go +++ b/util/kustomize/kustomize.go @@ -187,13 +187,35 @@ func IsKustomization(path string) bool { return false } -func Version() (string, error) { - cmd := exec.Command("kustomize", "version") - out, err := executil.Run(cmd) +func Version(shortForm bool) (string, error) { + executable := "kustomize" + cmdArgs := []string{"version"} + if shortForm { + cmdArgs = append(cmdArgs, "--short") + } + cmd := exec.Command(executable, cmdArgs...) + // example version output: + // long: "{Version:kustomize/v3.8.1 GitCommit:0b359d0ef0272e6545eda0e99aacd63aef99c4d0 BuildDate:2020-07-16T00:58:46Z GoOs:linux GoArch:amd64}" + // short: "{kustomize/v3.8.1 2020-07-16T00:58:46Z }" + version, err := executil.Run(cmd) if err != nil { return "", fmt.Errorf("could not get kustomize version: %s", err) } - return strings.TrimSpace(out), nil + version = strings.TrimSpace(version) + if shortForm { + // trim the curly braces + version = strings.TrimPrefix(version, "{") + version = strings.TrimSuffix(version, "}") + version = strings.TrimSpace(version) + + // remove double space in middle + version = strings.ReplaceAll(version, " ", " ") + + // remove extra 'kustomize/' before version + version = strings.TrimPrefix(version, "kustomize/") + + } + return version, nil } func getImageParameters(objs []*unstructured.Unstructured) []Image { diff --git a/util/kustomize/kustomize_test.go b/util/kustomize/kustomize_test.go index 9ad0c38ac2386..2996b2b7eeb1e 100644 --- a/util/kustomize/kustomize_test.go +++ b/util/kustomize/kustomize_test.go @@ -101,7 +101,7 @@ func TestParseKustomizeBuildOptions(t *testing.T) { } func TestVersion(t *testing.T) { - ver, err := Version() + ver, err := Version(false) assert.NoError(t, err) assert.NotEmpty(t, ver) }