Skip to content

Commit

Permalink
feat: allow selection of namespace in rollout dashboard (#1291)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrii Perenesenko <[email protected]>
Signed-off-by: Alexander Matyushentsev <[email protected]>

Co-authored-by: Alexander Matyushentsev <[email protected]>
  • Loading branch information
perenesenko and alexmt authored Jun 24, 2021
1 parent 96ba030 commit 6900988
Show file tree
Hide file tree
Showing 14 changed files with 323 additions and 226 deletions.
255 changes: 156 additions & 99 deletions pkg/apiclient/rollout/rollout.pb.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pkg/apiclient/rollout/rollout.proto
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ message RolloutWatchEvent {

message NamespaceInfo {
string namespace = 1;
repeated string availableNamespaces = 2;
}

message RolloutInfoList {
Expand Down
6 changes: 6 additions & 0 deletions pkg/apiclient/rollout/rollout.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -4130,6 +4130,12 @@
"properties": {
"namespace": {
"type": "string"
},
"availableNamespaces": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
Expand Down
101 changes: 53 additions & 48 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ import (
"k8s.io/client-go/dynamic"
kubeinformers "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
appslisters "k8s.io/client-go/listers/apps/v1"
corelisters "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"

"github.com/argoproj/argo-rollouts/pkg/apiclient/rollout"
Expand Down Expand Up @@ -69,42 +67,13 @@ const (

// ArgoRolloutsServer holds information about rollouts server
type ArgoRolloutsServer struct {
Options ServerOptions
NamespaceVC NamespaceViewController
stopCh chan struct{}
}

type NamespaceViewController struct {
namespace string

kubeInformerFactory kubeinformers.SharedInformerFactory
replicaSetLister appslisters.ReplicaSetNamespaceLister
podLister corelisters.PodNamespaceLister
cacheSyncs []cache.InformerSynced
}

func (vc *NamespaceViewController) Start(ctx context.Context) {
vc.kubeInformerFactory.Start(ctx.Done())
cache.WaitForCacheSync(ctx.Done(), vc.cacheSyncs...)
Options ServerOptions
stopCh chan struct{}
}

// NewServer creates an ArgoRolloutsServer
func NewServer(o ServerOptions) *ArgoRolloutsServer {
kubeInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(o.KubeClientset, 0, kubeinformers.WithNamespace(o.Namespace))

vc := NamespaceViewController{
namespace: o.Namespace,
kubeInformerFactory: kubeInformerFactory,
podLister: kubeInformerFactory.Core().V1().Pods().Lister().Pods(o.Namespace),
replicaSetLister: kubeInformerFactory.Apps().V1().ReplicaSets().Lister().ReplicaSets(o.Namespace),
}

vc.cacheSyncs = append(vc.cacheSyncs,
kubeInformerFactory.Apps().V1().ReplicaSets().Informer().HasSynced,
kubeInformerFactory.Core().V1().Pods().Informer().HasSynced,
)

return &ArgoRolloutsServer{Options: o, NamespaceVC: vc}
return &ArgoRolloutsServer{Options: o}
}

type spaFileSystem struct {
Expand Down Expand Up @@ -258,20 +227,27 @@ func (s *ArgoRolloutsServer) WatchRolloutInfo(q *rollout.RolloutInfoQuery, ws ro
return nil
}

func (s *ArgoRolloutsServer) ListReplicaSetsAndPods(ctx context.Context) ([]*appsv1.ReplicaSet, []*corev1.Pod, error) {
s.NamespaceVC.Start(ctx)
func (s *ArgoRolloutsServer) ListReplicaSetsAndPods(ctx context.Context, namespace string) ([]*appsv1.ReplicaSet, []*corev1.Pod, error) {

allReplicaSets, err := s.NamespaceVC.replicaSetLister.List(labels.Everything())
allReplicaSets, err := s.Options.KubeClientset.AppsV1().ReplicaSets(namespace).List(ctx, v1.ListOptions{})
if err != nil {
return nil, nil, err
}

allPods, err := s.NamespaceVC.podLister.List(labels.Everything())
allPods, err := s.Options.KubeClientset.CoreV1().Pods(namespace).List(ctx, v1.ListOptions{})
if err != nil {
return allReplicaSets, nil, err
return nil, nil, err
}

return allReplicaSets, allPods, nil
var allReplicaSetsP = make([]*appsv1.ReplicaSet, len(allReplicaSets.Items))
for i := range allReplicaSets.Items {
allReplicaSetsP[i] = &allReplicaSets.Items[i]
}
var allPodsP = make([]*corev1.Pod, len(allPods.Items))
for i := range allPods.Items {
allPodsP[i] = &allPods.Items[i]
}
return allReplicaSetsP, allPodsP, nil
}

// ListRollouts returns a list of all rollouts
Expand All @@ -283,7 +259,7 @@ func (s *ArgoRolloutsServer) ListRolloutInfos(ctx context.Context, q *rollout.Ro
return nil, err
}

allReplicaSets, allPods, err := s.ListReplicaSetsAndPods(ctx)
allReplicaSets, allPods, err := s.ListReplicaSetsAndPods(ctx, q.GetNamespace())
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -316,13 +292,19 @@ func (s *ArgoRolloutsServer) WatchRolloutInfos(q *rollout.RolloutInfoListQuery,
return
}
}
ctx := context.Background()
ctx := ws.Context()
rolloutIf := s.Options.RolloutsClientset.ArgoprojV1alpha1().Rollouts(q.GetNamespace())

allReplicaSets, allPods, err := s.ListReplicaSetsAndPods(ctx)
if err != nil {
return err
}
kubeInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(s.Options.KubeClientset, 0, kubeinformers.WithNamespace(q.Namespace))
podsLister := kubeInformerFactory.Core().V1().Pods().Lister().Pods(q.GetNamespace())
rsLister := kubeInformerFactory.Apps().V1().ReplicaSets().Lister().ReplicaSets(q.GetNamespace())
kubeInformerFactory.Start(ws.Context().Done())

cache.WaitForCacheSync(
ws.Context().Done(),
kubeInformerFactory.Core().V1().Pods().Informer().HasSynced,
kubeInformerFactory.Apps().V1().ReplicaSets().Informer().HasSynced,
)

watchIf, err := rolloutIf.Watch(ctx, v1.ListOptions{})
if err != nil {
Expand Down Expand Up @@ -355,6 +337,15 @@ L:
}
continue
}
allPods, err := podsLister.List(labels.Everything())
if err != nil {
return err
}
allReplicaSets, err := rsLister.List(labels.Everything())
if err != nil {
return err
}

// get shallow rollout info
ri := info.NewRolloutInfo(ro, allReplicaSets, allPods, nil, nil)
send(ri)
Expand All @@ -365,15 +356,29 @@ L:

func (s *ArgoRolloutsServer) RolloutToRolloutInfo(ro *v1alpha1.Rollout) (*rollout.RolloutInfo, error) {
ctx := context.Background()
allReplicaSets, allPods, err := s.ListReplicaSetsAndPods(ctx)
allReplicaSets, allPods, err := s.ListReplicaSetsAndPods(ctx, ro.Namespace)
if err != nil {
return nil, err
}
return info.NewRolloutInfo(ro, allReplicaSets, allPods, nil, nil), nil
}

func (s *ArgoRolloutsServer) GetNamespace(ctx context.Context, e *empty.Empty) (*rollout.NamespaceInfo, error) {
return &rollout.NamespaceInfo{Namespace: s.Options.Namespace}, nil
var m = make(map[string]bool)
var namespaces []string

rolloutList, err := s.Options.RolloutsClientset.ArgoprojV1alpha1().Rollouts("").List(ctx, v1.ListOptions{})
if err == nil {
for _, r := range rolloutList.Items {
ns := r.Namespace
if !m[ns] {
m[ns] = true
namespaces = append(namespaces, ns)
}
}
}

return &rollout.NamespaceInfo{Namespace: s.Options.Namespace, AvailableNamespaces: namespaces}, nil
}

func (s *ArgoRolloutsServer) PromoteRollout(ctx context.Context, q *rollout.PromoteRolloutRequest) (*v1alpha1.Rollout, error) {
Expand Down
70 changes: 46 additions & 24 deletions ui/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import * as React from 'react';
import {Key, KeybindingContext, KeybindingProvider} from 'react-keyhooks';
import {Redirect, Route, Router, Switch} from 'react-router-dom';
import './App.scss';
import {NamespaceContext, RolloutAPI} from './shared/context/api';
import {Modal} from './components/modal/modal';
import {Rollout} from './components/rollout/rollout';
import {RolloutsList} from './components/rollouts-list/rollouts-list';
import {Shortcut, Shortcuts} from './components/shortcuts/shortcuts';
import {NamespaceProvider} from './shared/context/api';
import {ThemeProvider} from 'argo-ux';

const bases = document.getElementsByTagName('base');
const base = bases.length > 0 ? bases[0].getAttribute('href') || '/' : '/';
export const history = createBrowserHistory({basename: base});

const Page = (props: {path: string; component: React.ReactNode; exact?: boolean; shortcuts?: Shortcut[]}) => {
const Page = (props: {path: string; component: React.ReactNode; exact?: boolean; shortcuts?: Shortcut[], changeNamespace: (val: string) => void}) => {
const {useKeybinding} = React.useContext(KeybindingContext);
const [showShortcuts, setShowShortcuts] = React.useState(false);
useKeybinding(
Expand All @@ -40,6 +40,7 @@ const Page = (props: {path: string; component: React.ReactNode; exact?: boolean;
<Route path={props.path} exact={props.exact}>
<React.Fragment>
<Header
changeNamespace={props.changeNamespace}
pageHasShortcuts={!!props.shortcuts}
showHelp={() => {
if (props.shortcuts) {
Expand All @@ -54,33 +55,54 @@ const Page = (props: {path: string; component: React.ReactNode; exact?: boolean;
);
};

export const NAMESPACE_KEY = 'namespace';
const init = window.localStorage.getItem(NAMESPACE_KEY);

const App = () => {
const [namespace, setNamespace] = React.useState(init);
const [availableNamespaces, setAvailableNamespaces] = React.useState([]);
React.useEffect(() => {
RolloutAPI.rolloutServiceGetNamespace().then((info) => {
if (!namespace) {
setNamespace(info.namespace);
}
setAvailableNamespaces(info.availableNamespaces);
});
}, []);
const changeNamespace = (val: string) => {
setNamespace(val);
window.localStorage.setItem(NAMESPACE_KEY, namespace);
};

return (
<ThemeProvider>
<NamespaceProvider>
<KeybindingProvider>
<Router history={history}>
<Switch>
<Redirect exact={true} path='/' to='/rollouts' />
{namespace && (
<NamespaceContext.Provider value={{namespace, availableNamespaces}}>
<KeybindingProvider>
<Router history={history}>
<Switch>
<Redirect exact={true} path='/' to='/rollouts' />

<Page
exact
path='/rollouts'
component={<RolloutsList />}
shortcuts={[
{key: '/', description: 'Search'},
{key: 'TAB', description: 'Search, navigate search items'},
{key: [faArrowLeft, faArrowRight, faArrowUp, faArrowDown], description: 'Navigate rollouts list'},
{key: ['SHIFT', 'H'], description: 'Show help menu', combo: true},
]}
/>
<Page path='/rollout/:name' component={<Rollout />} />
<Page
exact
path='/rollouts'
component={<RolloutsList />}
shortcuts={[
{key: '/', description: 'Search'},
{key: 'TAB', description: 'Search, navigate search items'},
{key: [faArrowLeft, faArrowRight, faArrowUp, faArrowDown], description: 'Navigate rollouts list'},
{key: ['SHIFT', 'H'], description: 'Show help menu', combo: true},
]}
changeNamespace={changeNamespace}
/>
<Page path='/rollout/:name' component={<Rollout />} changeNamespace={changeNamespace} />

<Redirect path='*' to='/' />
</Switch>
</Router>
</KeybindingProvider>
</NamespaceProvider>
<Redirect path='*' to='/' />
</Switch>
</Router>
</KeybindingProvider>
</NamespaceContext.Provider>
)}
</ThemeProvider>
);
};
Expand Down
9 changes: 8 additions & 1 deletion ui/src/app/components/header/header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,15 @@
display: flex;
align-items: center;
}
&__version {
&__label {
color: $shine;
margin: 0 15px;
margin: auto;
padding: 5px;
}
&__namespace {
color: black;
display: flex;
position: relative;
}
}
47 changes: 31 additions & 16 deletions ui/src/app/components/header/header.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,38 @@
import * as React from 'react';

import {ActionButton, Brand, InfoItemRow, ThemeToggle, Tooltip, useData, Header as GenericHeader} from 'argo-ux';
import {
ActionButton,
Brand,
InfoItemRow,
ThemeToggle,
Tooltip,
Header as GenericHeader,
Autocomplete,
ThemeDiv,
} from 'argo-ux';
import {useParams} from 'react-router';
import {RolloutNamespaceInfo, RolloutServiceApi} from '../../../models/rollout/generated';
import {RolloutAPIContext} from '../../shared/context/api';
import {NamespaceContext, RolloutAPIContext} from '../../shared/context/api';
import {faBook, faKeyboard} from '@fortawesome/free-solid-svg-icons';

import './header.scss';
import {Link} from 'react-router-dom';
import {Link, useHistory} from 'react-router-dom';

const Logo = () => <img src='assets/images/argo-icon-color-square.png' style={{width: '35px', height: '35px', margin: '0 8px'}} alt='Argo Logo' />;

export const Header = (props: {pageHasShortcuts: boolean; showHelp: () => void}) => {
const getNs = React.useCallback(() => new RolloutServiceApi().rolloutServiceGetNamespace(), []);
const [nsData, loading] = useData<RolloutNamespaceInfo>(getNs);
const [namespace, setNamespace] = React.useState('Unknown');
React.useEffect(() => {
if (!loading && nsData && nsData.namespace) {
setNamespace(nsData.namespace);
}
}, [nsData, loading]);
export const Header = (props: {pageHasShortcuts: boolean; changeNamespace: (val: string) => void; showHelp: () => void}) => {
const history = useHistory();
const namespaceInfo = React.useContext(NamespaceContext);
const {name} = useParams<{name: string}>();
const api = React.useContext(RolloutAPIContext);
const [version, setVersion] = React.useState('v?');
const [nsInput, setNsInput] = React.useState(namespaceInfo.namespace);
React.useEffect(() => {
const getVersion = async () => {
const v = await api.rolloutServiceVersion();
setVersion(v.rolloutsVersion);
};
getVersion();
});
}, []);
return (
<GenericHeader>
<Link to='/'>
Expand All @@ -51,8 +54,20 @@ export const Header = (props: {pageHasShortcuts: boolean; showHelp: () => void})
<ThemeToggle />
</Tooltip>
</span>
<InfoItemRow label={'NS:'} items={{content: namespace}} />
<div className='rollouts-header__version'>{version}</div>
{(namespaceInfo.availableNamespaces || []).length == 0 ? (
<InfoItemRow label={'NS:'} items={{content: namespaceInfo.namespace}} />
) : (
<ThemeDiv className='rollouts-header__namespace'>
<div className='rollouts-header__label'>NS:</div>
<Autocomplete items={namespaceInfo.availableNamespaces || []}
placeholder='Namespace'
onChange={(el) => setNsInput(el.target.value)}
onItemClick={(val) => { props.changeNamespace(val ? val : nsInput); history.push(`/rollouts`) } }
value={nsInput}
/>
</ThemeDiv>
)}
<div className='rollouts-header__label'>{version}</div>
</div>
</GenericHeader>
);
Expand Down
Loading

0 comments on commit 6900988

Please sign in to comment.